Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorTomasz Maczukin <tomasz@maczukin.pl>2015-12-21 15:27:34 +0300
committerTomasz Maczukin <tomasz@maczukin.pl>2015-12-21 15:27:34 +0300
commit3cfd892f382d3784f614fea75f929c44fe838559 (patch)
treecb9aa9ab48ee02e8f00e8506fecc182d1e66b9ea /app
parent85ad95be741848fbf15a01789f065e001326cefa (diff)
parent4b4cbf0ce4925e22a635e4432e7ac8602199fa5b (diff)
Merge branch 'master' into fix/visibility-level-setting-in-forked-projects
* master: (723 commits) Bump Rack Attack to v4.3.1 for security fix Remove duplicate entry in the changelog Remove extra spaces after branchname Fix merge-request-reopen button title Add branch and tag operation to tree dropdown Use gitlab-shell 2.6.9 Clarify Windows shell executor artifact upload support Fix feature specs: we always show the build status if ci_commit is present Do not display project group/name when issue and MR are in same project Don't create CI status for refs that doesn't have .gitlab-ci.yml, even if the builds are enabled Use gitlab-workhorse 0.5.1 Fix ci_projects migration by using the value only from latest row [ci skip] Revert sidebar position for issue and merge request Add info on using private Docker registries in CI [ci skip] Upgrade Poltergeist to 1.8.1. #4131 Fix ux issue with "This issue will be closed automatically" message Move MR Builds tab next to Commits Api support for requesting starred projects for user Fix Rubocop complain. Fix merge widget JS for buttons ... Conflicts: app/models/project.rb
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/icon-link.pngbin726 -> 1128 bytes
-rw-r--r--app/assets/javascripts/api.js.coffee31
-rw-r--r--app/assets/javascripts/application.js.coffee26
-rw-r--r--app/assets/javascripts/awards_handler.coffee18
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js.coffee48
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee2
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee80
-rw-r--r--app/assets/javascripts/flash.js.coffee16
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee10
-rw-r--r--app/assets/javascripts/issue.js.coffee8
-rw-r--r--app/assets/javascripts/issues.js.coffee2
-rw-r--r--app/assets/javascripts/markdown_preview.js.coffee87
-rw-r--r--app/assets/javascripts/merge_request.js.coffee8
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee29
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee9
-rw-r--r--app/assets/javascripts/new_commit_form.js.coffee6
-rw-r--r--app/assets/javascripts/notes.js.coffee47
-rw-r--r--app/assets/javascripts/project.js.coffee27
-rw-r--r--app/assets/javascripts/project_select.js.coffee39
-rw-r--r--app/assets/javascripts/sidebar.js.coffee1
-rw-r--r--app/assets/javascripts/user.js.coffee6
-rw-r--r--app/assets/javascripts/users_select.js.coffee28
-rw-r--r--app/assets/stylesheets/framework.scss3
-rw-r--r--app/assets/stylesheets/framework/blocks.scss13
-rw-r--r--app/assets/stylesheets/framework/callout.scss5
-rw-r--r--app/assets/stylesheets/framework/common.scss57
-rw-r--r--app/assets/stylesheets/framework/files.scss5
-rw-r--r--app/assets/stylesheets/framework/forms.scss21
-rw-r--r--app/assets/stylesheets/framework/header.scss8
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss15
-rw-r--r--app/assets/stylesheets/framework/layout.scss11
-rw-r--r--app/assets/stylesheets/framework/lists.scss52
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss9
-rw-r--r--app/assets/stylesheets/framework/mobile.scss3
-rw-r--r--app/assets/stylesheets/framework/pagination.scss4
-rw-r--r--app/assets/stylesheets/framework/panels.scss20
-rw-r--r--app/assets/stylesheets/framework/selects.scss62
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss191
-rw-r--r--app/assets/stylesheets/framework/tables.scss2
-rw-r--r--app/assets/stylesheets/framework/timeline.scss3
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss11
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/pages/awards.scss86
-rw-r--r--app/assets/stylesheets/pages/builds.scss5
-rw-r--r--app/assets/stylesheets/pages/commit.scss19
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss33
-rw-r--r--app/assets/stylesheets/pages/editor.scss46
-rw-r--r--app/assets/stylesheets/pages/groups.scss7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss144
-rw-r--r--app/assets/stylesheets/pages/issues.scss36
-rw-r--r--app/assets/stylesheets/pages/login.scss1
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss52
-rw-r--r--app/assets/stylesheets/pages/note_form.scss10
-rw-r--r--app/assets/stylesheets/pages/notes.scss10
-rw-r--r--app/assets/stylesheets/pages/profile.scss6
-rw-r--r--app/assets/stylesheets/pages/projects.scss27
-rw-r--r--app/assets/stylesheets/pages/snippets.scss50
-rw-r--r--app/assets/stylesheets/pages/status.scss17
-rw-r--r--app/assets/stylesheets/pages/ui_dev_kit.scss5
-rw-r--r--app/assets/stylesheets/pages/wiki.scss5
-rw-r--r--app/controllers/abuse_reports_controller.rb2
-rw-r--r--app/controllers/admin/application_settings_controller.rb6
-rw-r--r--app/controllers/admin/builds_controller.rb23
-rw-r--r--app/controllers/admin/impersonation_controller.rb16
-rw-r--r--app/controllers/admin/runner_projects_controller.rb35
-rw-r--r--app/controllers/admin/runners_controller.rb63
-rw-r--r--app/controllers/autocomplete_controller.rb51
-rw-r--r--app/controllers/ci/admin/application_controller.rb10
-rw-r--r--app/controllers/ci/admin/application_settings_controller.rb31
-rw-r--r--app/controllers/ci/admin/builds_controller.rb18
-rw-r--r--app/controllers/ci/admin/events_controller.rb9
-rw-r--r--app/controllers/ci/admin/projects_controller.rb19
-rw-r--r--app/controllers/ci/admin/runner_projects_controller.rb34
-rw-r--r--app/controllers/ci/admin/runners_controller.rb73
-rw-r--r--app/controllers/ci/application_controller.rb18
-rw-r--r--app/controllers/ci/lints_controller.rb2
-rw-r--r--app/controllers/ci/projects_controller.rb11
-rw-r--r--app/controllers/ci/runner_projects_controller.rb34
-rw-r--r--app/controllers/concerns/global_milestones.rb2
-rw-r--r--app/controllers/dashboard/snippets_controller.rb3
-rw-r--r--app/controllers/groups/milestones_controller.rb2
-rw-r--r--app/controllers/passwords_controller.rb6
-rw-r--r--app/controllers/profiles_controller.rb1
-rw-r--r--app/controllers/projects/application_controller.rb8
-rw-r--r--app/controllers/projects/blob_controller.rb12
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/builds_controller.rb11
-rw-r--r--app/controllers/projects/ci_services_controller.rb49
-rw-r--r--app/controllers/projects/ci_settings_controller.rb36
-rw-r--r--app/controllers/projects/ci_web_hooks_controller.rb45
-rw-r--r--app/controllers/projects/commit_controller.rb16
-rw-r--r--app/controllers/projects/graphs_controller.rb32
-rw-r--r--app/controllers/projects/hooks_controller.rb8
-rw-r--r--app/controllers/projects/issues_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests_controller.rb86
-rw-r--r--app/controllers/projects/notes_controller.rb41
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/projects/protected_branches_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb41
-rw-r--r--app/controllers/projects/runner_projects_controller.rb26
-rw-r--r--app/controllers/projects/runners_controller.rb15
-rw-r--r--app/controllers/projects/services_controller.rb4
-rw-r--r--app/controllers/projects/tags_controller.rb2
-rw-r--r--app/controllers/projects/triggers_controller.rb9
-rw-r--r--app/controllers/projects/variables_controller.rb5
-rw-r--r--app/controllers/projects_controller.rb6
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/finders/milestones_finder.rb2
-rw-r--r--app/finders/projects_finder.rb10
-rw-r--r--app/helpers/application_helper.rb18
-rw-r--r--app/helpers/blob_helper.rb52
-rw-r--r--app/helpers/branches_helper.rb2
-rw-r--r--app/helpers/button_helper.rb58
-rw-r--r--app/helpers/ci/gitlab_helper.rb17
-rw-r--r--app/helpers/ci/projects_helper.rb36
-rw-r--r--app/helpers/ci_badge_helper.rb13
-rw-r--r--app/helpers/ci_status_helper.rb43
-rw-r--r--app/helpers/clipboard_helper.rb8
-rw-r--r--app/helpers/commits_helper.rb4
-rw-r--r--app/helpers/diff_helper.rb4
-rw-r--r--app/helpers/emails_helper.rb2
-rw-r--r--app/helpers/external_wiki_helper.rb2
-rw-r--r--app/helpers/gitlab_markdown_helper.rb61
-rw-r--r--app/helpers/gitlab_routing_helper.rb2
-rw-r--r--app/helpers/graph_helper.rb10
-rw-r--r--app/helpers/icons_helper.rb22
-rw-r--r--app/helpers/issues_helper.rb35
-rw-r--r--app/helpers/labels_helper.rb2
-rw-r--r--app/helpers/merge_requests_helper.rb17
-rw-r--r--app/helpers/milestones_helper.rb2
-rw-r--r--app/helpers/namespaces_helper.rb6
-rw-r--r--app/helpers/nav_helper.rb8
-rw-r--r--app/helpers/page_layout_helper.rb3
-rw-r--r--app/helpers/projects_helper.rb27
-rw-r--r--app/helpers/runners_helper.rb2
-rw-r--r--app/helpers/selects_helper.rb27
-rw-r--r--app/helpers/tree_helper.rb20
-rw-r--r--app/helpers/triggers_helper.rb4
-rw-r--r--app/helpers/visibility_level_helper.rb64
-rw-r--r--app/mailers/base_mailer.rb4
-rw-r--r--app/mailers/ci/emails/builds.rb17
-rw-r--r--app/mailers/ci/notify.rb46
-rw-r--r--app/mailers/emails/builds.rb15
-rw-r--r--app/mailers/emails/projects.rb88
-rw-r--r--app/mailers/notify.rb7
-rw-r--r--app/models/ability.rb12
-rw-r--r--app/models/application_setting.rb35
-rw-r--r--app/models/broadcast_message.rb8
-rw-r--r--app/models/ci/application_setting.rb33
-rw-r--r--app/models/ci/build.rb65
-rw-r--r--app/models/ci/commit.rb36
-rw-r--r--app/models/ci/event.rb27
-rw-r--r--app/models/ci/project.rb192
-rw-r--r--app/models/ci/project_status.rb31
-rw-r--r--app/models/ci/runner.rb6
-rw-r--r--app/models/ci/runner_project.rb4
-rw-r--r--app/models/ci/service.rb105
-rw-r--r--app/models/ci/trigger.rb2
-rw-r--r--app/models/ci/variable.rb4
-rw-r--r--app/models/ci/web_hook.rb44
-rw-r--r--app/models/commit.rb50
-rw-r--r--app/models/commit_range.rb110
-rw-r--r--app/models/commit_status.rb62
-rw-r--r--app/models/concerns/issuable.rb20
-rw-r--r--app/models/concerns/mentionable.rb53
-rw-r--r--app/models/concerns/participable.rb29
-rw-r--r--app/models/concerns/referable.rb23
-rw-r--r--app/models/concerns/strip_attribute.rb34
-rw-r--r--app/models/concerns/taskable.rb33
-rw-r--r--app/models/concerns/token_authenticatable.rb44
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/global_milestone.rb31
-rw-r--r--app/models/hooks/project_hook.rb1
-rw-r--r--app/models/hooks/web_hook.rb40
-rw-r--r--app/models/issue.rb12
-rw-r--r--app/models/label.rb6
-rw-r--r--app/models/lfs_object.rb24
-rw-r--r--app/models/lfs_objects_project.rb11
-rw-r--r--app/models/merge_request.rb85
-rw-r--r--app/models/milestone.rb9
-rw-r--r--app/models/namespace.rb18
-rw-r--r--app/models/note.rb48
-rw-r--r--app/models/project.rb106
-rw-r--r--app/models/project_services/bamboo_service.rb13
-rw-r--r--app/models/project_services/buildkite_service.rb2
-rw-r--r--app/models/project_services/builds_email_service.rb90
-rw-r--r--app/models/project_services/ci/hip_chat_message.rb73
-rw-r--r--app/models/project_services/ci/hip_chat_service.rb93
-rw-r--r--app/models/project_services/ci/mail_service.rb84
-rw-r--r--app/models/project_services/ci/slack_message.rb92
-rw-r--r--app/models/project_services/ci/slack_service.rb81
-rw-r--r--app/models/project_services/drone_ci_service.rb33
-rw-r--r--app/models/project_services/external_wiki_service.rb6
-rw-r--r--app/models/project_services/flowdock_service.rb2
-rw-r--r--app/models/project_services/gemnasium_service.rb2
-rw-r--r--app/models/project_services/gitlab_ci_service.rb72
-rw-r--r--app/models/project_services/hipchat_service.rb49
-rw-r--r--app/models/project_services/slack_service.rb29
-rw-r--r--app/models/project_services/slack_service/base_message.rb3
-rw-r--r--app/models/project_services/slack_service/build_message.rb82
-rw-r--r--app/models/project_services/teamcity_service.rb10
-rw-r--r--app/models/repository.rb85
-rw-r--r--app/models/sent_notification.rb7
-rw-r--r--app/models/service.rb21
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/user.rb35
-rw-r--r--app/models/users_star_project.rb2
-rw-r--r--app/services/ci/create_builds_service.rb6
-rw-r--r--app/services/ci/create_commit_service.rb28
-rw-r--r--app/services/ci/create_trigger_request_service.rb6
-rw-r--r--app/services/ci/event_service.rb31
-rw-r--r--app/services/ci/image_for_build_service.rb4
-rw-r--r--app/services/ci/register_build_service.rb9
-rw-r--r--app/services/ci/test_hook_service.rb7
-rw-r--r--app/services/create_branch_service.rb5
-rw-r--r--app/services/create_commit_builds_service.rb42
-rw-r--r--app/services/create_release_service.rb31
-rw-r--r--app/services/create_tag_service.rb10
-rw-r--r--app/services/delete_branch_service.rb4
-rw-r--r--app/services/files/base_service.rb4
-rw-r--r--app/services/git_hooks_service.rb28
-rw-r--r--app/services/git_push_service.rb1
-rw-r--r--app/services/git_tag_push_service.rb1
-rw-r--r--app/services/gravatar_service.rb4
-rw-r--r--app/services/issuable_base_service.rb30
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb18
-rw-r--r--app/services/merge_requests/merge_when_build_succeeds_service.rb55
-rw-r--r--app/services/merge_requests/refresh_service.rb8
-rw-r--r--app/services/merge_requests/update_service.rb4
-rw-r--r--app/services/notes/create_service.rb13
-rw-r--r--app/services/notification_service.rb76
-rw-r--r--app/services/projects/create_service.rb6
-rw-r--r--app/services/projects/fork_service.rb15
-rw-r--r--app/services/system_note_service.rb34
-rw-r--r--app/services/update_release_service.rb29
-rw-r--r--app/uploaders/artifact_uploader.rb6
-rw-r--r--app/validators/color_validator.rb20
-rw-r--r--app/validators/email_validator.rb18
-rw-r--r--app/validators/line_code_validator.rb12
-rw-r--r--app/validators/namespace_name_validator.rb10
-rw-r--r--app/validators/namespace_validator.rb50
-rw-r--r--app/validators/url_validator.rb36
-rw-r--r--app/views/admin/application_settings/_form.html.haml4
-rw-r--r--app/views/admin/builds/_build.html.haml73
-rw-r--r--app/views/admin/builds/index.html.haml50
-rw-r--r--app/views/admin/labels/_form.html.haml2
-rw-r--r--app/views/admin/labels/_label.html.haml2
-rw-r--r--app/views/admin/labels/edit.html.haml8
-rw-r--r--app/views/admin/labels/new.html.haml6
-rw-r--r--app/views/admin/runners/_runner.html.haml (renamed from app/views/ci/admin/runners/_runner.html.haml)12
-rw-r--r--app/views/admin/runners/index.html.haml (renamed from app/views/ci/admin/runners/index.html.haml)22
-rw-r--r--app/views/admin/runners/show.html.haml (renamed from app/views/ci/admin/runners/show.html.haml)41
-rw-r--r--app/views/admin/runners/update.js.haml (renamed from app/views/ci/admin/runners/update.js.haml)0
-rw-r--r--app/views/admin/users/_head.html.haml2
-rw-r--r--app/views/admin/users/_projects.html.haml13
-rw-r--r--app/views/admin/users/edit.html.haml3
-rw-r--r--app/views/admin/users/projects.html.haml2
-rw-r--r--app/views/ci/admin/application_settings/_form.html.haml24
-rw-r--r--app/views/ci/admin/application_settings/show.html.haml3
-rw-r--r--app/views/ci/admin/builds/_build.html.haml34
-rw-r--r--app/views/ci/admin/builds/index.html.haml28
-rw-r--r--app/views/ci/admin/events/index.html.haml18
-rw-r--r--app/views/ci/admin/projects/_project.html.haml29
-rw-r--r--app/views/ci/admin/projects/index.html.haml16
-rw-r--r--app/views/ci/admin/runner_projects/index.html.haml57
-rw-r--r--app/views/ci/commits/_commit.html.haml5
-rw-r--r--app/views/ci/shared/_guide.html.haml8
-rw-r--r--app/views/ci/user_sessions/new.html.haml7
-rw-r--r--app/views/dashboard/_projects_head.html.haml5
-rw-r--r--app/views/dashboard/groups/index.html.haml4
-rw-r--r--app/views/dashboard/issues.html.haml20
-rw-r--r--app/views/dashboard/merge_requests.html.haml12
-rw-r--r--app/views/dashboard/milestones/_milestone.html.haml11
-rw-r--r--app/views/dashboard/milestones/index.html.haml10
-rw-r--r--app/views/dashboard/milestones/show.html.haml44
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/dashboard/projects/starred.html.haml2
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/explore/projects/_filter.html.haml2
-rw-r--r--app/views/explore/projects/index.html.haml2
-rw-r--r--app/views/explore/projects/starred.html.haml2
-rw-r--r--app/views/explore/projects/trending.html.haml2
-rw-r--r--app/views/groups/edit.html.haml6
-rw-r--r--app/views/groups/group_members/_group_member.html.haml6
-rw-r--r--app/views/groups/group_members/index.html.haml57
-rw-r--r--app/views/groups/group_members/update.js.haml2
-rw-r--r--app/views/groups/issues.html.haml27
-rw-r--r--app/views/groups/merge_requests.html.haml19
-rw-r--r--app/views/groups/milestones/index.html.haml24
-rw-r--r--app/views/groups/milestones/new.html.haml3
-rw-r--r--app/views/groups/milestones/show.html.haml47
-rw-r--r--app/views/groups/new.html.haml8
-rw-r--r--app/views/help/ui.html.haml48
-rw-r--r--app/views/import/bitbucket/status.html.haml1
-rw-r--r--app/views/import/fogbugz/new.html.haml1
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml1
-rw-r--r--app/views/import/fogbugz/status.html.haml1
-rw-r--r--app/views/import/github/status.html.haml1
-rw-r--r--app/views/import/gitlab/status.html.haml1
-rw-r--r--app/views/import/gitorious/status.html.haml1
-rw-r--r--app/views/import/google_code/new.html.haml3
-rw-r--r--app/views/import/google_code/new_user_map.html.haml17
-rw-r--r--app/views/import/google_code/status.html.haml1
-rw-r--r--app/views/layouts/_page.html.haml10
-rw-r--r--app/views/layouts/admin.html.haml4
-rw-r--r--app/views/layouts/ci/_nav_admin.html.haml35
-rw-r--r--app/views/layouts/ci/_nav_project.html.haml12
-rw-r--r--app/views/layouts/ci/_page.html.haml10
-rw-r--r--app/views/layouts/ci/admin.html.haml11
-rw-r--r--app/views/layouts/ci/application.html.haml11
-rw-r--r--app/views/layouts/devise.html.haml4
-rw-r--r--app/views/layouts/errors.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml12
-rw-r--r--app/views/layouts/nav/_admin.html.haml41
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml18
-rw-r--r--app/views/layouts/nav/_explore.html.haml8
-rw-r--r--app/views/layouts/nav/_group.html.haml14
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml6
-rw-r--r--app/views/layouts/nav/_profile.html.haml20
-rw-r--r--app/views/layouts/nav/_project.html.haml50
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml33
-rw-r--r--app/views/notify/build_fail_email.html.haml (renamed from app/views/ci/notify/build_fail_email.html.haml)6
-rw-r--r--app/views/notify/build_fail_email.text.erb (renamed from app/views/ci/notify/build_fail_email.text.erb)2
-rw-r--r--app/views/notify/build_success_email.html.haml (renamed from app/views/ci/notify/build_success_email.html.haml)6
-rw-r--r--app/views/notify/build_success_email.text.erb (renamed from app/views/ci/notify/build_success_email.text.erb)2
-rw-r--r--app/views/notify/repository_push_email.html.haml28
-rw-r--r--app/views/notify/repository_push_email.text.haml24
-rw-r--r--app/views/profiles/accounts/show.html.haml24
-rw-r--r--app/views/profiles/applications.html.haml84
-rw-r--r--app/views/profiles/keys/_form.html.haml7
-rw-r--r--app/views/profiles/keys/_key_table.html.haml16
-rw-r--r--app/views/profiles/keys/index.html.haml4
-rw-r--r--app/views/profiles/keys/new.html.haml2
-rw-r--r--app/views/profiles/notifications/show.html.haml6
-rw-r--r--app/views/profiles/preferences/show.html.haml2
-rw-r--r--app/views/profiles/show.html.haml10
-rw-r--r--app/views/projects/_commit_button.html.haml4
-rw-r--r--app/views/projects/_home_panel.html.haml17
-rw-r--r--app/views/projects/_last_commit.html.haml2
-rw-r--r--app/views/projects/_md_preview.html.haml19
-rw-r--r--app/views/projects/_readme.html.haml25
-rw-r--r--app/views/projects/blame/show.html.haml3
-rw-r--r--app/views/projects/blob/_actions.html.haml16
-rw-r--r--app/views/projects/blob/_blob.html.haml6
-rw-r--r--app/views/projects/blob/_download.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml25
-rw-r--r--app/views/projects/blob/_new_dir.html.haml14
-rw-r--r--app/views/projects/blob/_upload.html.haml7
-rw-r--r--app/views/projects/blob/edit.html.haml6
-rw-r--r--app/views/projects/blob/new.html.haml5
-rw-r--r--app/views/projects/blob/show.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml22
-rw-r--r--app/views/projects/branches/_commit.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/branches/new.html.haml16
-rw-r--r--app/views/projects/builds/index.html.haml16
-rw-r--r--app/views/projects/builds/show.html.haml20
-rw-r--r--app/views/projects/ci_services/_form.html.haml54
-rw-r--r--app/views/projects/ci_services/edit.html.haml2
-rw-r--r--app/views/projects/ci_services/index.html.haml23
-rw-r--r--app/views/projects/ci_settings/_form.html.haml120
-rw-r--r--app/views/projects/ci_settings/_no_runners.html.haml8
-rw-r--r--app/views/projects/ci_settings/edit.html.haml6
-rw-r--r--app/views/projects/ci_web_hooks/index.html.haml94
-rw-r--r--app/views/projects/commit/_builds.html.haml68
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml12
-rw-r--r--app/views/projects/commit/builds.html.haml68
-rw-r--r--app/views/projects/commit/show.html.haml5
-rw-r--r--app/views/projects/commit_statuses/_commit_status.html.haml21
-rw-r--r--app/views/projects/commits/_commit.html.haml8
-rw-r--r--app/views/projects/commits/_head.html.haml5
-rw-r--r--app/views/projects/commits/show.atom.builder2
-rw-r--r--app/views/projects/compare/show.html.haml4
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml9
-rw-r--r--app/views/projects/deploy_keys/new.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml25
-rw-r--r--app/views/projects/edit.html.haml96
-rw-r--r--app/views/projects/empty.html.haml15
-rw-r--r--app/views/projects/graphs/_head.html.haml2
-rw-r--r--app/views/projects/graphs/ci/_overall.haml11
-rw-r--r--app/views/projects/graphs/languages.html.haml32
-rw-r--r--app/views/projects/hooks/index.html.haml9
-rw-r--r--app/views/projects/issues/_closed_by_box.html.haml5
-rw-r--r--app/views/projects/issues/_discussion.html.haml33
-rw-r--r--app/views/projects/issues/_form.html.haml8
-rw-r--r--app/views/projects/issues/_issue.html.haml41
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml26
-rw-r--r--app/views/projects/issues/edit.html.haml6
-rw-r--r--app/views/projects/issues/new.html.haml4
-rw-r--r--app/views/projects/issues/show.html.haml95
-rw-r--r--app/views/projects/issues/update.js.haml4
-rw-r--r--app/views/projects/labels/_form.html.haml10
-rw-r--r--app/views/projects/labels/_label.html.haml2
-rw-r--r--app/views/projects/labels/edit.html.haml8
-rw-r--r--app/views/projects/labels/index.html.haml1
-rw-r--r--app/views/projects/labels/new.html.haml6
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml27
-rw-r--r--app/views/projects/merge_requests/_form.html.haml3
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml71
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml14
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml30
-rw-r--r--app/views/projects/merge_requests/_show.html.haml77
-rw-r--r--app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml2
-rw-r--r--app/views/projects/merge_requests/edit.html.haml2
-rw-r--r--app/views/projects/merge_requests/merge.js.haml8
-rw-r--r--app/views/projects/merge_requests/new.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_builds.html.haml1
-rw-r--r--app/views/projects/merge_requests/show/_commits.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml12
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml8
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml15
-rw-r--r--app/views/projects/merge_requests/show/_participants.html.haml4
-rw-r--r--app/views/projects/merge_requests/update.js.haml4
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml39
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml18
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml68
-rw-r--r--app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml26
-rw-r--r--app/views/projects/milestones/_form.html.haml10
-rw-r--r--app/views/projects/milestones/_milestone.html.haml8
-rw-r--r--app/views/projects/milestones/edit.html.haml6
-rw-r--r--app/views/projects/milestones/index.html.haml15
-rw-r--r--app/views/projects/milestones/new.html.haml6
-rw-r--r--app/views/projects/milestones/show.html.haml87
-rw-r--r--app/views/projects/network/_head.html.haml9
-rw-r--r--app/views/projects/network/show.html.haml3
-rw-r--r--app/views/projects/new.html.haml43
-rw-r--r--app/views/projects/notes/_edit_form.html.haml5
-rw-r--r--app/views/projects/notes/_form.html.haml9
-rw-r--r--app/views/projects/notes/_note.html.haml2
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml2
-rw-r--r--app/views/projects/project_members/_group_members.html.haml13
-rw-r--r--app/views/projects/project_members/_project_member.html.haml4
-rw-r--r--app/views/projects/project_members/_team.html.haml16
-rw-r--r--app/views/projects/project_members/index.html.haml45
-rw-r--r--app/views/projects/protected_branches/index.html.haml5
-rw-r--r--app/views/projects/releases/edit.html.haml2
-rw-r--r--app/views/projects/repositories/_download_archive.html.haml4
-rw-r--r--app/views/projects/repositories/_feed.html.haml2
-rw-r--r--app/views/projects/runners/_runner.html.haml6
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml10
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml2
-rw-r--r--app/views/projects/runners/edit.html.haml4
-rw-r--r--app/views/projects/services/_form.html.haml7
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/snippets/edit.html.haml2
-rw-r--r--app/views/projects/snippets/new.html.haml2
-rw-r--r--app/views/projects/snippets/show.html.haml4
-rw-r--r--app/views/projects/tags/_tag.html.haml12
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/new.html.haml23
-rw-r--r--app/views/projects/tags/show.html.haml8
-rw-r--r--app/views/projects/tree/_tree_content.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml18
-rw-r--r--app/views/projects/triggers/index.html.haml8
-rw-r--r--app/views/projects/variables/show.html.haml6
-rw-r--r--app/views/projects/wikis/_form.html.haml16
-rw-r--r--app/views/projects/wikis/_main_links.html.haml11
-rw-r--r--app/views/projects/wikis/_nav.html.haml25
-rw-r--r--app/views/projects/wikis/_new.html.haml4
-rw-r--r--app/views/projects/wikis/edit.html.haml24
-rw-r--r--app/views/projects/wikis/git_access.html.haml2
-rw-r--r--app/views/projects/wikis/pages.html.haml7
-rw-r--r--app/views/projects/wikis/show.html.haml12
-rw-r--r--app/views/shared/_clone_panel.html.haml24
-rw-r--r--app/views/shared/_confirm_modal.html.haml5
-rw-r--r--app/views/shared/_file_highlight.html.haml5
-rw-r--r--app/views/shared/_group_form.html.haml2
-rw-r--r--app/views/shared/_issues.html.haml7
-rw-r--r--app/views/shared/_merge_requests.html.haml7
-rw-r--r--app/views/shared/_milestone_expired.html.haml5
-rw-r--r--app/views/shared/_new_commit_form.html.haml18
-rw-r--r--app/views/shared/_new_project_item_select.html.haml20
-rw-r--r--app/views/shared/_project_limit.html.haml8
-rw-r--r--app/views/shared/_service_settings.html.haml9
-rw-r--r--app/views/shared/issuable/_context.html.haml50
-rw-r--r--app/views/shared/issuable/_filter.html.haml16
-rw-r--r--app/views/shared/issuable/_form.html.haml54
-rw-r--r--app/views/shared/issuable/_participants.html.haml5
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml83
-rw-r--r--app/views/shared/snippets/_form.html.haml7
-rw-r--r--app/views/shared/snippets/_header.html.haml47
-rw-r--r--app/views/sherlock/transactions/_general.html.haml6
-rw-r--r--app/views/snippets/edit.html.haml2
-rw-r--r--app/views/snippets/new.html.haml2
-rw-r--r--app/views/snippets/show.html.haml4
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/views/votes/_votes_block.html.haml3
-rw-r--r--app/workers/build_email_worker.rb19
-rw-r--r--app/workers/ci/hip_chat_notifier_worker.rb19
-rw-r--r--app/workers/ci/slack_notifier_worker.rb10
-rw-r--r--app/workers/ci/web_hook_worker.rb9
-rw-r--r--app/workers/email_receiver_worker.rb2
-rw-r--r--app/workers/emails_on_push_worker.rb2
-rw-r--r--app/workers/merge_worker.rb13
-rw-r--r--app/workers/repository_import_worker.rb3
-rw-r--r--app/workers/stuck_ci_builds_worker.rb6
501 files changed, 5296 insertions, 5078 deletions
diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png
index 60021d5ac47..7d89da97e11 100644
--- a/app/assets/images/icon-link.png
+++ b/app/assets/images/icon-link.png
Binary files differ
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 9e5d594c861..746fa3cea87 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -2,6 +2,8 @@
groups_path: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.json"
namespaces_path: "/api/:version/namespaces.json"
+ group_projects_path: "/api/:version/groups/:id/projects.json"
+ projects_path: "/api/:version/projects.json"
group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path)
@@ -44,6 +46,35 @@
).done (namespaces) ->
callback(namespaces)
+ # Return projects list. Filtered by query
+ projects: (query, callback) ->
+ url = Api.buildUrl(Api.projects_path)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ search: query
+ per_page: 20
+ dataType: "json"
+ ).done (projects) ->
+ callback(projects)
+
+ # Return group projects list. Filtered by query
+ groupProjects: (group_id, query, callback) ->
+ url = Api.buildUrl(Api.group_projects_path)
+ url = url.replace(':id', group_id)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ search: query
+ per_page: 20
+ dataType: "json"
+ ).done (projects) ->
+ callback(projects)
+
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 945ffb660e6..1539eba0faa 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -135,17 +135,25 @@ $ ->
), 1
# Initialize tooltips
- $('body').tooltip({
- selector: '.has_tooltip, [data-toggle="tooltip"], .page-sidebar-collapsed .nav-sidebar a'
+ $('body').tooltip(
+ selector: '.has_tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
- if $el.attr('id') == 'js-shortcuts-home'
- # Place the logo tooltip on the right when collapsed, bottom when expanded
- $el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom'
- else
- # Otherwise use the data-placement attribute, or 'bottom' if undefined
- $el.data('placement') or 'bottom'
- })
+ $el.data('placement') || 'bottom'
+ )
+
+ $('.header-logo .home').tooltip(
+ placement: (_, el) ->
+ $el = $(el)
+ if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
+ container: 'body'
+ )
+
+ $('.page-with-sidebar').tooltip(
+ selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
+ placement: 'right'
+ container: 'body'
+ )
# Form submitter
$('.trigger-submit').on 'change', ->
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index ae42e390c43..3ff9ba77dfc 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,11 +1,13 @@
class @AwardsHandler
- constructor: (@post_emoji_url, @noteable_type, @noteable_id) ->
+ constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
addAward: (emoji) ->
+ emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
addAwardToEmojiBar: (emoji, custom_path = '') ->
+ emoji = @normilizeEmojiName(emoji)
if @exist(emoji)
if @isActive(emoji)
@decrementCounter(emoji)
@@ -73,14 +75,14 @@ class @AwardsHandler
getImage: (emoji, custom_path) ->
if custom_path
- $(".awards-menu li").first().html().replace(/emoji\/.*\.png/, custom_path)
+ $("<img>").attr({src: custom_path, width: 20, height: 20}).wrap("<div>").parent().html()
else
$("li[data-emoji='" + emoji + "']").html()
postEmoji: (emoji, callback) ->
$.post @post_emoji_url, { note: {
- note: emoji
+ note: ":" + emoji + ":"
noteable_type: @noteable_type
noteable_id: @noteable_id
}},(data) ->
@@ -88,4 +90,12 @@ class @AwardsHandler
callback.call()
findEmojiIcon: (emoji) ->
- $(".icon[data-emoji='" + emoji + "']") \ No newline at end of file
+ $(".icon[data-emoji='" + emoji + "']")
+
+ scrollToAwards: ->
+ $('body, html').animate({
+ scrollTop: $('.awards').offset().top - 80
+ }, 200)
+
+ normilizeEmojiName: (emoji) ->
+ @aliases[emoji] || emoji
diff --git a/app/assets/javascripts/copy_to_clipboard.js.coffee b/app/assets/javascripts/copy_to_clipboard.js.coffee
index ec4b80cca6f..24301e01b10 100644
--- a/app/assets/javascripts/copy_to_clipboard.js.coffee
+++ b/app/assets/javascripts/copy_to_clipboard.js.coffee
@@ -1,21 +1,37 @@
#= require clipboard
-$ ->
- clipboard = new Clipboard '.js-clipboard-trigger',
- text: (trigger) ->
- $target = $(trigger.nextElementSibling || trigger.previousElementSibling)
- $target.data('clipboard-text') || $target.text().trim()
+genericSuccess = (e) ->
+ showTooltip(e.trigger, 'Copied!')
+
+ # Clear the selection and blur the trigger so it loses its border
+ e.clearSelection()
+ $(e.trigger).blur()
- clipboard.on 'success', (e) ->
- $(e.trigger).
- tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!').
- tooltip('show')
+# Safari doesn't support `execCommand`, so instead we inform the user to
+# copy manually.
+#
+# See http://clipboardjs.com/#browser-support
+genericError = (e) ->
+ if /Mac/i.test(navigator.userAgent)
+ key = '&#8984;' # Command
+ else
+ key = 'Ctrl'
- # Clear the selection and blur the trigger so it loses its border
- e.clearSelection()
- $(e.trigger).blur()
+ showTooltip(e.trigger, "Press #{key}-C to copy")
- # Manually hide the tooltip after 1 second
- setTimeout(->
- $(e.trigger).tooltip('hide')
- , 1000)
+showTooltip = (target, title) ->
+ $(target).
+ tooltip(
+ container: 'body'
+ html: 'true'
+ placement: 'auto bottom'
+ title: title
+ trigger: 'manual'
+ ).
+ tooltip('show').
+ one('mouseleave', -> $(this).tooltip('hide'))
+
+$ ->
+ clipboard = new Clipboard '[data-clipboard-target], [data-clipboard-text]'
+ clipboard.on 'success', genericSuccess
+ clipboard.on 'error', genericError
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 4059fc39c67..599b4c49540 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -83,7 +83,7 @@ class Dispatcher
when 'projects:project_members:index'
new ProjectMembers()
new UsersSelect()
- when 'groups:new', 'groups:edit', 'admin:groups:edit'
+ when 'groups:new', 'groups:edit', 'admin:groups:edit', 'admin:groups:new'
new GroupAvatar()
when 'projects:tree:show'
new TreeView()
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index 6f789e668af..30a35a04339 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -1,3 +1,5 @@
+#= require markdown_preview
+
class @DropzoneInput
constructor: (form) ->
Dropzone.autoDiscover = false
@@ -11,17 +13,14 @@ class @DropzoneInput
uploadProgress = $("<div class=\"div-dropzone-progress\"></div>")
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_uploads_path = window.project_uploads_path or null
- markdown_preview_path = window.markdown_preview_path or null
max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) =>
handlePaste(event)
- form_textarea.on "input", ->
- hideReferencedUsers()
- form_textarea.on "blur", ->
- renderMarkdown()
+
+ $(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
@@ -34,42 +33,6 @@ class @DropzoneInput
"opacity": 0
"display": "none"
- # Preview button
- $(document).off "click", ".js-md-preview-button"
- $(document).on "click", ".js-md-preview-button", (e) ->
- ###
- Shows the Markdown preview.
-
- Lets the server render GFM into Html and displays it.
- ###
- e.preventDefault()
- form = $(this).closest("form")
- # toggle tabs
- form.find(".js-md-write-button").parent().removeClass "active"
- form.find(".js-md-preview-button").parent().addClass "active"
-
- # toggle content
- form.find(".md-write-holder").hide()
- form.find(".md-preview-holder").show()
-
- renderMarkdown()
-
- # Write button
- $(document).off "click", ".js-md-write-button"
- $(document).on "click", ".js-md-write-button", (e) ->
- ###
- Shows the Markdown textarea.
- ###
- e.preventDefault()
- form = $(this).closest("form")
- # toggle tabs
- form.find(".js-md-write-button").parent().addClass "active"
- form.find(".js-md-preview-button").parent().removeClass "active"
-
- # toggle content
- form.find(".md-write-holder").show()
- form.find(".md-preview-holder").hide()
-
dropzone = form_dropzone.dropzone(
url: project_uploads_path
dictDefaultMessage: ""
@@ -136,41 +99,6 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea")
- hideReferencedUsers = ->
- referencedUsers = form.find(".referenced-users")
- referencedUsers.hide()
-
- renderReferencedUsers = (users) ->
- referencedUsers = form.find(".referenced-users")
-
- if referencedUsers.length
- if users.length >= 10
- referencedUsers.show()
- referencedUsers.find(".js-referenced-users-count").text users.length
- else
- referencedUsers.hide()
-
- renderMarkdown = ->
- preview = form.find(".js-md-preview")
- mdText = form.find(".markdown-area").val()
- if mdText.trim().length is 0
- preview.text "Nothing to preview."
- hideReferencedUsers()
- else
- preview.text "Loading..."
- $.ajax(
- type: "POST",
- url: markdown_preview_path,
- data: {
- text: mdText
- },
- dataType: "json"
- ).success (data) ->
- preview.html data.body
- preview.syntaxHighlight()
-
- renderReferencedUsers data.references.users
-
formatLink = (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index b39ab0c4475..5de012e409f 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,12 +1,16 @@
class @Flash
constructor: (message, type)->
- flash = $(".flash-container")
- flash.html("")
+ @flash = $(".flash-container")
+ @flash.html("")
- $('<div/>',
+ innerDiv = $('<div/>',
class: "flash-#{type}",
text: message
- ).appendTo(".flash-container")
+ )
+ innerDiv.appendTo(".flash-container")
- flash.click -> $(@).fadeOut()
- flash.show()
+ @flash.click -> $(@).fadeOut()
+ @flash.show()
+
+ pinTo: (selector) ->
+ @flash.detach().appendTo(selector)
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index c4d3e619f5e..02232698bc2 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -5,9 +5,9 @@ class @IssuableContext
new UsersSelect()
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
- $(".context .inline-update").on "change", "select", ->
+ $(".issuable-sidebar .inline-update").on "change", "select", ->
$(this).submit()
- $(".context .inline-update").on "change", ".js-assignee", ->
+ $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
$('.issuable-details').waitForImages ->
@@ -21,3 +21,9 @@ class @IssuableContext
@top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@bottom = $('.footer').outerHeight(true)
+
+ $(".edit-link").click (e) ->
+ block = $(@).parents('.block')
+ block.find('.selectbox').show()
+ block.find('.value').hide()
+ block.find('.js-select2').select2("open")
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 603a16da1ce..eff80bf63bb 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -10,12 +10,12 @@ class @Issue
@initTaskList()
initTaskList: ->
- $('.issue-details .js-task-list-container').taskList('enable')
- $(document).on 'tasklist:changed', '.issue-details .js-task-list-container', @updateTaskList
+ $('.detail-page-description .js-task-list-container').taskList('enable')
+ $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
disableTaskList: ->
- $('.issue-details .js-task-list-container').taskList('disable')
- $(document).off 'tasklist:changed', '.issue-details .js-task-list-container'
+ $('.detail-page-description .js-task-list-container').taskList('disable')
+ $(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
# TODO (rspeicher): Make the issue description inline-editable like a note so
# that we can re-use its form here
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index 40bb9e9cb0c..ac9e022e727 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -29,7 +29,7 @@
$('#filter_issue_search').val($('#issue_search').val())
initSelects: ->
- $("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true)
+ $("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
diff --git a/app/assets/javascripts/markdown_preview.js.coffee b/app/assets/javascripts/markdown_preview.js.coffee
new file mode 100644
index 00000000000..98fc8f17340
--- /dev/null
+++ b/app/assets/javascripts/markdown_preview.js.coffee
@@ -0,0 +1,87 @@
+# MarkdownPreview
+#
+# Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
+# and showing a warning when more than `x` users are referenced.
+#
+class @MarkdownPreview
+ # Minimum number of users referenced before triggering a warning
+ referenceThreshold: 10
+
+ showPreview: (form) ->
+ preview = form.find('.js-md-preview')
+ mdText = form.find('textarea.markdown-area').val()
+
+ if mdText.trim().length == 0
+ preview.text('Nothing to preview.')
+ @hideReferencedUsers(form)
+ else
+ preview.text('Loading...')
+ @renderMarkdown mdText, (response) =>
+ preview.html(response.body)
+ preview.syntaxHighlight()
+ @renderReferencedUsers(response.references.users, form)
+
+ renderMarkdown: (text, success) ->
+ return unless window.markdown_preview_path
+
+ $.ajax
+ type: 'POST'
+ url: window.markdown_preview_path
+ data: { text: text }
+ dataType: 'json'
+ success: success
+
+ hideReferencedUsers: (form) ->
+ referencedUsers = form.find('.referenced-users')
+ referencedUsers.hide()
+
+ renderReferencedUsers: (users, form) ->
+ referencedUsers = form.find('.referenced-users')
+
+ if referencedUsers.length
+ if users.length >= @referenceThreshold
+ referencedUsers.show()
+ referencedUsers.find('.js-referenced-users-count').text(users.length)
+ else
+ referencedUsers.hide()
+
+markdownPreview = new MarkdownPreview()
+
+previewButtonSelector = '.js-md-preview-button'
+writeButtonSelector = '.js-md-write-button'
+
+$.fn.setupMarkdownPreview = ->
+ $form = $(this)
+
+ form_textarea = $form.find('textarea.markdown-area')
+
+ form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form)
+ form_textarea.on 'blur', -> markdownPreview.showPreview($form)
+
+$(document).on 'click', previewButtonSelector, (e) ->
+ e.preventDefault()
+
+ $form = $(this).closest('form')
+
+ # toggle tabs
+ $form.find(writeButtonSelector).parent().removeClass('active')
+ $form.find(previewButtonSelector).parent().addClass('active')
+
+ # toggle content
+ $form.find('.md-write-holder').hide()
+ $form.find('.md-preview-holder').show()
+
+ markdownPreview.showPreview($form)
+
+$(document).on 'click', writeButtonSelector, (e) ->
+ e.preventDefault()
+
+ $form = $(this).closest('form')
+
+ # toggle tabs
+ $form.find(writeButtonSelector).parent().addClass('active')
+ $form.find(previewButtonSelector).parent().removeClass('active')
+
+ # toggle content
+ $form.find('.md-write-holder').show()
+ $form.find('.md-preview-holder').hide()
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index b21cb7904b5..9047587db81 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -40,12 +40,12 @@ class @MergeRequest
this.$('.all-commits').removeClass 'hide'
initTaskList: ->
- $('.merge-request-details .js-task-list-container').taskList('enable')
- $(document).on 'tasklist:changed', '.merge-request-details .js-task-list-container', @updateTaskList
+ $('.detail-page-description .js-task-list-container').taskList('enable')
+ $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
disableTaskList: ->
- $('.merge-request-details .js-task-list-container').taskList('disable')
- $(document).off 'tasklist:changed', '.merge-request-details .js-task-list-container'
+ $('.detail-page-description .js-task-list-container').taskList('disable')
+ $(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
# TODO (rspeicher): Make the merge request description inline-editable like a
# note so that we can re-use its form here
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 593a8f42130..9e2dc1250c9 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -43,6 +43,7 @@
#
class @MergeRequestTabs
diffsLoaded: false
+ buildsLoaded: false
commitsLoaded: false
constructor: (@opts = {}) ->
@@ -54,6 +55,12 @@ class @MergeRequestTabs
bindEvents: ->
$(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown
+ $(document).on 'click', '.js-show-tab', @showTab
+
+ showTab: (event) =>
+ event.preventDefault()
+
+ @activateTab $(event.target).data('action')
tabShown: (event) =>
$target = $(event.target)
@@ -63,12 +70,14 @@ class @MergeRequestTabs
@loadCommits($target.attr('href'))
else if action == 'diffs'
@loadDiff($target.attr('href'))
+ else if action == 'builds'
+ @loadBuilds($target.attr('href'))
@setCurrentAction(action)
scrollToElement: (container) ->
if window.location.hash
- $el = $("#{container} #{window.location.hash}")
+ $el = $("div#{container} #{window.location.hash}")
$('body').scrollTo($el.offset().top) if $el.length
# Activate a tab based on the current action
@@ -101,7 +110,7 @@ class @MergeRequestTabs
action = 'notes' if action == 'show'
# Remove a trailing '/commits' or '/diffs'
- new_state = @_location.pathname.replace(/\/(commits|diffs)(\.html)?\/?$/, '')
+ new_state = @_location.pathname.replace(/\/(commits|diffs|builds)(\.html)?\/?$/, '')
# Append the new action if we're on a tab other than 'notes'
unless action == 'notes'
@@ -124,7 +133,7 @@ class @MergeRequestTabs
@_get
url: "#{source}.json"
success: (data) =>
- document.getElementById('commits').innerHTML = data.html
+ document.querySelector("div#commits").innerHTML = data.html
$('.js-timeago').timeago()
@commitsLoaded = true
@scrollToElement("#commits")
@@ -135,10 +144,22 @@ class @MergeRequestTabs
@_get
url: "#{source}.json" + @_location.search
success: (data) =>
- document.getElementById('diffs').innerHTML = data.html
+ document.querySelector("div#diffs").innerHTML = data.html
+ $('div#diffs .js-syntax-highlight').syntaxHighlight()
@diffsLoaded = true
@scrollToElement("#diffs")
+ loadBuilds: (source) ->
+ return if @buildsLoaded
+
+ @_get
+ url: "#{source}.json"
+ success: (data) =>
+ document.querySelector("div#builds").innerHTML = data.html
+ $('.js-timeago').timeago()
+ @buildsLoaded = true
+ @scrollToElement("#builds")
+
# Show or hide the loading spinner
#
# status - Boolean, true to show, false to hide
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 3176e5a8965..c4b63966fe7 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -10,17 +10,20 @@ class @MergeRequestWidget
constructor: (@opts) ->
modal = $('#modal_merge_info').modal(show: false)
- mergeInProgress: ->
+ mergeInProgress: (deleteSourceBranch = false)->
$.ajax
type: 'GET'
url: $('.merge-request').data('url')
success: (data) =>
if data.state == "merged"
- location.reload()
+ urlSuffix = if deleteSourceBranch then '?delete_source=true' else ''
+
+ window.location.href = window.location.href + urlSuffix
else if data.merge_error
$('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
else
- setTimeout(merge_request_widget.mergeInProgress, 2000)
+ callback = -> merge_request_widget.mergeInProgress(deleteSourceBranch)
+ setTimeout(callback, 2000)
dataType: 'json'
getMergeStatus: ->
diff --git a/app/assets/javascripts/new_commit_form.js.coffee b/app/assets/javascripts/new_commit_form.js.coffee
index 2e561dea3e1..3c7b776155f 100644
--- a/app/assets/javascripts/new_commit_form.js.coffee
+++ b/app/assets/javascripts/new_commit_form.js.coffee
@@ -3,7 +3,7 @@ class @NewCommitForm
@newBranch = form.find('.js-new-branch')
@originalBranch = form.find('.js-original-branch')
@createMergeRequest = form.find('.js-create-merge-request')
- @createMergeRequestFormGroup = form.find('.js-create-merge-request-form-group')
+ @createMergeRequestContainer = form.find('.js-create-merge-request-container')
@renderDestination()
@newBranch.keyup @renderDestination
@@ -12,10 +12,10 @@ class @NewCommitForm
different = @newBranch.val() != @originalBranch.val()
if different
- @createMergeRequestFormGroup.show()
+ @createMergeRequestContainer.show()
@createMergeRequest.prop('checked', true) unless @wasDifferent
else
- @createMergeRequestFormGroup.hide()
+ @createMergeRequestContainer.hide()
@createMergeRequest.prop('checked', false)
@wasDifferent = different
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 7de7632201d..35dc7829da2 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -111,6 +111,12 @@ class @Notes
Note: for rendering inline notes use renderDiscussionNote
###
renderNote: (note) ->
+ unless note.valid
+ if note.award
+ flash = new Flash('You have already used this award emoji!', 'alert')
+ flash.pinTo('.header-content')
+ return
+
# render note if it not present in loaded list
# or skip if rendered
if @isNewNote(note) && !note.award
@@ -122,6 +128,7 @@ class @Notes
if note.award
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
+ awards_handler.scrollToAwards()
###
Check if note does not exists on page
@@ -141,6 +148,8 @@ class @Notes
@note_ids.push(note.id)
form = $("form[rel='" + note.discussion_id + "']")
row = form.closest("tr")
+ note_html = $(note.html)
+ note_html.syntaxHighlight()
# is this the first note of discussion?
if row.is(".js-temp-notes-holder")
@@ -151,14 +160,16 @@ class @Notes
row.next().find(".note").remove()
# Add note to 'Changes' page discussions
- $(".notes[rel='" + note.discussion_id + "']").append note.html
+ $(".notes[rel='" + note.discussion_id + "']").append note_html
# Init discussion on 'Discussion' page if it is merge request page
if $('body').attr('data-page').indexOf('projects:merge_request') == 0
- $('ul.main-notes-list').append(note.discussion_with_diff_html)
+ discussion_html = $(note.discussion_with_diff_html)
+ discussion_html.syntaxHighlight()
+ $('ul.main-notes-list').append(discussion_html)
else
# append new note to all matching discussions
- $(".notes[rel='" + note.discussion_id + "']").append note.html
+ $(".notes[rel='" + note.discussion_id + "']").append note_html
# cleanup after successfully creating a diff/discussion note
@removeDiscussionNoteForm(form)
@@ -279,7 +290,7 @@ class @Notes
$html.find('.js-task-list-container').taskList('enable')
# Find the note's `li` element by ID and replace it with the updated HTML
- $note_li = $("#note_#{note.id}")
+ $note_li = $('.note-row-' + note.id)
$note_li.replaceWith($html)
###
@@ -339,18 +350,26 @@ class @Notes
###
removeNote: ->
note = $(this).closest(".note")
- notes = note.closest(".notes")
+ note_id = note.attr('id')
+
+ $('.note[id="' + note_id + '"]').each ->
+ note = $(this)
+ notes = note.closest(".notes")
+ count = notes.closest(".notes_holder").find(".discussion-notes-count")
- # check if this is the last note for this line
- if notes.find(".note").length is 1
+ # check if this is the last note for this line
+ if notes.find(".note").length is 1
- # for discussions
- notes.closest(".discussion").remove()
+ # for discussions
+ notes.closest(".discussion").remove()
- # for diff lines
- notes.closest("tr").remove()
+ # for diff lines
+ notes.closest("tr").remove()
+ else
+ # update notes count
+ count.get(0).lastChild.nodeValue = " #{notes.children().length - 1}"
- note.remove()
+ note.remove()
###
Called in response to clicking the delete attachment link
@@ -362,8 +381,8 @@ class @Notes
note = $(this).closest(".note")
note.find(".note-attachment").remove()
note.find(".note-body > .note-text").show()
- note.find(".js-note-attachment-delete").hide()
- note.find(".note-edit-form").hide()
+ note.find(".note-header").show()
+ note.find(".current-note-edit-form").remove()
###
Called when clicking on the "reply" button for a diff line.
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 0ea8fffce07..1f221945c06 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -1,13 +1,22 @@
class @Project
constructor: ->
- # Git clone panel switcher
- cloneHolder = $('.git-clone-holder')
- if cloneHolder.length
- $('a, button', cloneHolder).click ->
- $('a, button', cloneHolder).removeClass 'active'
- $(@).addClass 'active'
- $('#project_clone', cloneHolder).val $(@).data 'clone'
- $(".clone").text("").append $(@).data 'clone'
+ # Git protocol switcher
+ $('.js-protocol-switch').click ->
+ return if $(@).hasClass('active')
+
+
+ # Remove the active class for all buttons (ssh, http, kerberos if shown)
+ $('.active').not($(@)).removeClass('active');
+ # Add the active class for the clicked button
+ $(@).toggleClass('active')
+
+ url = $(@).data('clone')
+
+ # Update the input field
+ $('#project_clone').val(url)
+
+ # Update the command line instructions
+ $('.clone').text(url)
# Ref switcher
$('.project-refs-select').on 'change', ->
@@ -39,4 +48,4 @@ class @Project
when 4 then label = ' On Mention '
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
- $(@).parent().addClass 'active' \ No newline at end of file
+ $(@).parent().addClass 'active'
diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee
new file mode 100644
index 00000000000..0ae274f3363
--- /dev/null
+++ b/app/assets/javascripts/project_select.js.coffee
@@ -0,0 +1,39 @@
+class @ProjectSelect
+ constructor: ->
+ $('.ajax-project-select').each (i, select) ->
+ @groupId = $(select).data('group-id')
+ @includeGroups = $(select).data('include-groups')
+
+ placeholder = "Search for project"
+ placeholder += " or group" if @includeGroups
+
+ $(select).select2
+ placeholder: placeholder
+ minimumInputLength: 0
+ query: (query) =>
+ finalCallback = (projects) ->
+ data = { results: projects }
+ query.callback(data)
+
+ if @includeGroups
+ projectsCallback = (projects) ->
+ groupsCallback = (groups) ->
+ data = groups.concat(projects)
+ finalCallback(data)
+
+ Api.groups query.term, false, groupsCallback
+ else
+ projectsCallback = finalCallback
+
+ if @groupId
+ Api.groupProjects @groupId, query.term, projectsCallback
+ else
+ Api.projects query.term, projectsCallback
+
+ id: (project) ->
+ project.web_url
+
+ text: (project) ->
+ project.name_with_namespace || project.name
+
+ dropdownCssClass: "ajax-project-dropdown"
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index fb08016fbae..ae59480af9e 100644
--- a/app/assets/javascripts/sidebar.js.coffee
+++ b/app/assets/javascripts/sidebar.js.coffee
@@ -5,6 +5,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
+ $('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
)
diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee
index d0d81f96921..ec4271b092c 100644
--- a/app/assets/javascripts/user.js.coffee
+++ b/app/assets/javascripts/user.js.coffee
@@ -2,3 +2,9 @@ class @User
constructor: ->
$('.profile-groups-avatars').tooltip("placement": "top")
new ProjectsList()
+
+ $('.hide-project-limit-message').on 'click', (e) ->
+ path = '/'
+ $.cookie('hide_project_limit_message', 'false', { path: path })
+ $(@).parents('.project-limit-message').remove()
+ e.preventDefault()
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 9157562a5c5..12abf806bfa 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -32,17 +32,15 @@ class @UsersSelect
if showNullUser
nullUser = {
name: 'Unassigned',
- avatar: null,
- username: 'none',
id: 0
}
data.results.unshift(nullUser)
if showAnyUser
+ name = showAnyUser
+ name = 'Any User' if name == true
anyUser = {
- name: 'Any',
- avatar: null,
- username: 'none',
+ name: name,
id: null
}
data.results.unshift(anyUser)
@@ -50,7 +48,6 @@ class @UsersSelect
if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
emailUser = {
name: "Invite \"#{query.term}\"",
- avatar: null,
username: query.term,
id: query.term
}
@@ -58,11 +55,8 @@ class @UsersSelect
query.callback(data)
- initSelection: (element, callback) =>
- id = $(element).val()
- if id != "" && id != "0"
- @user(id, callback)
-
+ initSelection: (args...) =>
+ @initSelection(args...)
formatResult: (args...) =>
@formatResult(args...)
formatSelection: (args...) =>
@@ -71,16 +65,24 @@ class @UsersSelect
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id == "0"
+ nullUser = { name: 'Unassigned' }
+ callback(nullUser)
+ else if id != ""
+ @user(id, callback)
+
formatResult: (user) ->
if user.avatar_url
avatar = user.avatar_url
else
avatar = gon.default_avatar_url
- "<div class='user-result'>
+ "<div class='user-result #{'no-username' unless user.username}'>
<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
<div class='user-name'>#{user.name}</div>
- <div class='user-username'>#{user.username}</div>
+ <div class='user-username'>#{user.username || ""}</div>
</div>"
formatSelection: (user) ->
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 1ec9d2fd84f..48a4971c8fc 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -1,9 +1,9 @@
@import "framework/fonts";
@import "framework/variables";
@import "framework/mixins";
-@import "framework/layout";
@import 'framework/tw_bootstrap_variables';
@import 'framework/tw_bootstrap';
+@import "framework/layout";
@import "framework/avatar.scss";
@import "framework/blocks.scss";
@@ -25,6 +25,7 @@
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
@import "framework/pagination.scss";
+@import "framework/panels.scss";
@import "framework/selects.scss";
@import "framework/sidebar.scss";
@import "framework/tables.scss";
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 1635df9c97b..a62c0f62a4c 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -68,6 +68,10 @@
.oneline {
line-height: 42px;
}
+
+ > p:last-child {
+ margin-bottom: 0;
+ }
}
.cover-block {
@@ -112,5 +116,14 @@
position: absolute;
top: 10px;
right: 10px;
+
+ &.left {
+ left: 10px;
+ right: auto;
+ }
}
}
+
+.block-connector {
+ margin-top: -1px;
+}
diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss
index f3ce4e3c219..20a9bfb9816 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -7,8 +7,8 @@
/* Common styles for all types */
.bs-callout {
- margin: 20px 0;
- padding: 20px;
+ margin: $gl-padding 0;
+ padding: $gl-padding;
border-left: 3px solid $border-color;
color: $text-color;
background: $background-color;
@@ -42,4 +42,3 @@
border-color: #5cA64d;
color: #3c763d;
}
-
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 40f4beb1968..7562ef6d24b 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -7,7 +7,7 @@
/** COMMON CLASSES **/
.prepend-top-10 { margin-top:10px }
-.prepend-top-default { margin-top: $gl-padding; }
+.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top:20px }
.prepend-left-10 { margin-left:10px }
.prepend-left-20 { margin-left:20px }
@@ -52,6 +52,10 @@ pre {
}
}
+hr {
+ margin: $gl-padding 0;
+}
+
.dropdown-menu > li > a {
text-shadow: none;
}
@@ -64,7 +68,7 @@ pre {
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background: $gl-primary;
- color: #FFF
+ color: #FFF;
}
.str-truncated {
@@ -329,7 +333,7 @@ table {
}
.well {
- margin-bottom: 0;
+ margin-bottom: $gl-padding;
}
.search_box {
@@ -337,10 +341,6 @@ table {
text-align: center;
}
-.task-status {
- margin-left: 10px;
-}
-
#nprogress .spinner {
top: 15px !important;
right: 10px !important;
@@ -379,9 +379,8 @@ table {
text-align: center;
margin-top: 5px;
margin-bottom: $gl-padding;
- height: 56px;
+ height: auto;
margin-top: -$gl-padding;
- padding-top: $gl-padding;
&.no-bottom {
margin-bottom: 0;
@@ -390,6 +389,23 @@ table {
&.no-top {
margin-top: 0;
}
+
+ li a {
+ display: inline-block;
+ padding-top: $gl-padding;
+ padding-bottom: 11px;
+ margin-bottom: -1px;
+ }
+
+ &.bottom-border {
+ border-bottom: 1px solid $border-color;
+ height: 57px;
+ }
+
+ &.wide {
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ }
}
.center-middle-menu {
@@ -433,3 +449,26 @@ table {
.space-right {
margin-right: 10px;
}
+
+.alert, .progress {
+ margin-bottom: $gl-padding;
+}
+
+.new-project-item-select-holder {
+ display: inline-block;
+ position: relative;
+
+ .new-project-item-select {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 250px !important;
+ visibility: hidden;
+ }
+}
+
+.content-separator {
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ border-top: 1px solid $border-color;
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 35db00281e5..cbfd4bc29b6 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -8,7 +8,6 @@
border: none;
border-top: 1px solid #E7E9EE;
border-bottom: 1px solid #E7E9EE;
- margin-bottom: 1em;
&.readme-holder {
border-bottom: 0;
@@ -22,10 +21,9 @@
position: relative;
background: $background-color;
border-bottom: 1px solid $border-color;
- text-shadow: 0 1px 1px #fff;
margin: 0;
text-align: left;
- padding: 10px 15px;
+ padding: 10px $gl-padding;
.file-actions {
float: right;
@@ -171,4 +169,3 @@
}
}
}
-
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 0edfe24f195..032d343df44 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -22,9 +22,10 @@ input[type='text'].danger {
}
.form-actions {
- padding: 17px 20px 18px;
- margin-top: 18px;
- margin-bottom: 18px;
+ margin: -$gl-padding;
+ margin-top: 0;
+ margin-bottom: -$gl-padding;
+ padding: $gl-padding;
background-color: $background-color;
border-top: 1px solid $border-color;
}
@@ -73,6 +74,8 @@ label {
.form-control {
@include box-shadow(none);
+ height: 42px;
+ padding: 8px $gl-padding;
}
.wiki-content {
@@ -88,7 +91,19 @@ label {
}
.input-group {
+ .select2-container {
+ display: table-cell;
+ width: 200px !important;
+ }
.input-group-addon {
background-color: #f7f8fa;
}
+ .input-group-addon:not(:first-child):not(:last-child) {
+ border-left: 0;
+ border-right: 0;
+ }
+}
+
+.help-block {
+ margin-bottom: 0;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 02ea91602e8..4dbbb56104b 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -6,15 +6,17 @@ header {
transition-duration: .3s;
&.navbar-empty {
+ height: 58px;
background: #FFF;
border-bottom: 1px solid #EEE;
.center-logo {
- margin: 8px 0;
+ margin: 11px 0;
text-align: center;
- img {
- height: 32px;
+ #tanuki-logo, img {
+ width: 36px;
+ height: 36px;
}
}
}
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 93377e45e70..fba67ba0b64 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -4,31 +4,32 @@
*
*/
-.issue-box {
+.status-box {
@include border-radius(2px);
- display: inline-block;
- padding: 10px $gl-padding;
+ display: block;
+ float: left;
+ padding: 0 $gl-padding;
font-weight: normal;
margin-right: 10px;
font-size: $gl-font-size;
- &.issue-box-closed {
+ &.status-box-closed {
background-color: $gl-danger;
color: #FFF;
}
- &.issue-box-merged {
+ &.status-box-merged {
background-color: $gl-primary;
color: #FFF;
}
- &.issue-box-open {
+ &.status-box-open {
background-color: #019875;
color: #FFF;
}
- &.issue-box-expired {
+ &.status-box-expired {
background: #cea61b;
color: #FFF;
}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index b91c15d8910..aa5acb93cc5 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -2,9 +2,13 @@ html {
overflow-y: scroll;
&.touch .tooltip { display: none !important; }
+}
+
+body {
+ background-color: #EAEBEC !important;
- body {
- padding-top: $header-height;
+ &.navless {
+ background-color: white !important;
}
}
@@ -18,7 +22,8 @@ html {
}
.navless-container {
- margin-top: 30px;
+ margin-top: $header-height;
+ padding-top: $gl-padding * 2;
}
.container-limited {
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 45f3b5849bf..1c74e525a60 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -7,7 +7,7 @@
padding: 0;
list-style: none;
- li {
+ > li {
padding: 10px 15px;
min-height: 20px;
border-bottom: 1px solid #eee;
@@ -72,13 +72,6 @@
}
}
-ol, ul {
- &.styled {
- li {
- padding: 2px;
- }
- }
-}
/** light list with border-bottom between li **/
ul.bordered-list {
@@ -95,8 +88,14 @@ ul.bordered-list {
}
}
-li.task-list-item {
- list-style-type: none;
+ul.task-list {
+ li.task-list-item {
+ list-style-type: none;
+ }
+
+ ul:not(.task-list) {
+ padding-left: 1.3em;
+ }
}
ul.content-list {
@@ -127,3 +126,36 @@ ul.content-list {
}
}
+.panel > .content-list {
+ li {
+ margin: 0;
+ }
+}
+
+ul.controls {
+ padding-top: 1px;
+ float: right;
+ list-style: none;
+
+ .btn {
+ padding: 10px 14px;
+ }
+
+ > li {
+ float: left;
+ margin-right: 10px;
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ .author_link {
+ display: inline-block;
+
+ .avatar-inline {
+ margin-left: 0;
+ margin-right: 0;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index cc660529cb4..4a00a197d9a 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -73,11 +73,8 @@
}
.referenced-users {
- padding: 10px 0;
- color: #999;
- margin-left: 10px;
- margin-top: 1px;
- margin-right: 130px;
+ color: #4c4e54;
+ padding-top: 10px;
}
.md-preview-holder {
@@ -90,7 +87,7 @@
.new_note,
.edit_note,
-.issuable-description,
+.detail-page-description,
.milestone-description,
.wiki-content,
.merge-request-form {
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index cea47fba192..6f44c323732 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -82,9 +82,6 @@
}
.center-top-menu {
- height: 45px;
- margin-bottom: 30px;
-
li a {
font-size: 14px;
padding: 19px 10px;
diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss
index 6677f94dafd..2cd30491bf5 100644
--- a/app/assets/stylesheets/framework/pagination.scss
+++ b/app/assets/stylesheets/framework/pagination.scss
@@ -32,3 +32,7 @@
}
}
}
+
+.panel > .gl-pagination {
+ margin: 0;
+}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
new file mode 100644
index 00000000000..57b9451b264
--- /dev/null
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -0,0 +1,20 @@
+.panel {
+ margin-bottom: $gl-padding;
+
+ .panel-heading {
+ padding: 7px $gl-padding;
+ }
+
+ .panel-body {
+ padding: $gl-padding;
+
+ .form-actions {
+ margin: -$gl-padding;
+ margin-top: $gl-padding;
+ }
+ }
+}
+
+.container-blank .panel .panel-heading {
+ line-height: 42px !important;
+}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 78fff58d232..af145191bc8 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -15,6 +15,16 @@
border-left: none;
padding-top: 5px;
}
+
+ .select2-chosen {
+ color: $gl-text-color;
+ }
+
+ &.select2-default {
+ .select2-chosen {
+ color: #999;
+ }
+ }
}
}
@@ -23,6 +33,7 @@
border: 1px solid #e7e9ed;
}
+
.select2-drop {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
@include border-radius (0px);
@@ -48,17 +59,38 @@
color: #313236;
}
+.select2-container-multi {
+ .select2-choices {
+ @include border-radius(2px);
+ border-color: $input-border;
+ background: white;
+ padding-left: $gl-padding / 2;
+
+ .select2-search-field input {
+ padding: $gl-padding / 2;
+ font-size: 13px;
+ height: auto;
+ font-family: inherit;
+ font-size: inherit;
+ }
-.select2-container-multi .select2-choices {
- @include border-radius(2px);
- border-color: #CCC;
-}
-
-.select2-container-multi .select2-choices .select2-search-field input {
- padding: 8px 14px;
- font-size: 13px;
- line-height: 18px;
- height: auto;
+ .select2-search-choice {
+ margin: 8px 0 0 8px;
+ background: white;
+ box-shadow: none;
+ border-color: $input-border;
+ color: $gl-text-color;
+ line-height: 15px;
+
+ .select2-search-choice-close {
+ top: 5px;
+ }
+
+ &.select2-search-choice-focus {
+ border-color: $gl-text-color;
+ }
+ }
+ }
}
.select2-drop-active {
@@ -123,10 +155,16 @@
}
.user-result {
+ min-height: 24px;
+
.user-image {
float: left;
}
- .user-name {
+
+ &.no-username {
+ .user-name {
+ line-height: 24px;
+ }
}
}
@@ -143,4 +181,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 c1b0129c866..458af76cb75 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,4 +1,7 @@
.page-with-sidebar {
+ padding-top: $header-height;
+ transition-duration: .3s;
+
.sidebar-wrapper {
position: fixed;
top: 0;
@@ -14,19 +17,15 @@
.sidebar-wrapper {
z-index: 99;
background: $background-color;
- transition-duration: .3s;
}
.content-wrapper {
- min-height: 100vh;
width: 100%;
padding: 20px;
- background: #EAEBEC;
.container-fluid {
background: #FFF;
padding: $gl-padding;
- min-height: 90vh;
&.container-blank {
background: none;
@@ -36,6 +35,83 @@
}
}
+.sidebar-wrapper {
+ .header-logo {
+ border-bottom: 1px solid transparent;
+ float: left;
+ height: $header-height;
+ width: $sidebar_width;
+ position: fixed;
+ z-index: 999;
+ overflow: hidden;
+ transition-duration: .3s;
+
+ a {
+ float: left;
+ height: $header-height;
+ width: 100%;
+ padding: 11px 0 11px 22px;
+ overflow: hidden;
+ outline: none;
+ transition-duration: .3s;
+
+ img {
+ width: 36px;
+ height: 36px;
+ }
+
+ #tanuki-logo, img {
+ float: left;
+ }
+
+ .gitlab-text-container {
+ width: 230px;
+
+ h3 {
+ width: 158px;
+ float: left;
+ margin: 0;
+ margin-left: 14px;
+ font-size: 19px;
+ line-height: 41px;
+ font-weight: normal;
+ }
+ }
+ }
+
+ &:hover {
+ background-color: #EEE;
+ }
+ }
+
+ .sidebar-user {
+ padding: 9px 22px;
+ position: fixed;
+ bottom: 40px;
+ width: $sidebar_width;
+ overflow: hidden;
+ transition-duration: .3s;
+
+ .username {
+ margin-left: 10px;
+ width: $sidebar_width - 2 * 10px;
+ font-size: 16px;
+ line-height: 34px;
+ }
+ }
+}
+
+
+.tanuki-shape {
+ transition: all 0.8s;
+
+ &:hover {
+ fill: rgb(255, 255, 255);
+ transition: all 0.1s;
+ }
+}
+
+
.nav-sidebar {
margin-top: 14 + $header-height;
margin-bottom: 100px;
@@ -62,7 +138,7 @@
color: $gray;
display: block;
text-decoration: none;
- padding-left: 22px;
+ padding-left: 23px;
font-weight: normal;
outline: none;
@@ -86,6 +162,10 @@
padding: 0px 8px;
@include border-radius(6px);
}
+
+ &.back-link i {
+ transition-duration: .3s;
+ }
}
}
}
@@ -101,7 +181,6 @@
@mixin expanded-sidebar {
padding-left: $sidebar_width;
- transition-duration: .3s;
.sidebar-wrapper {
width: $sidebar_width;
@@ -115,16 +194,15 @@
&.back-link {
i {
- visibility: hidden;
+ opacity: 0;
}
}
}
}
}
-@mixin folded-sidebar {
- padding-left: 60px;
- transition-duration: .3s;
+@mixin collapsed-sidebar {
+ padding-left: $sidebar_collapsed_width;
.sidebar-wrapper {
width: $sidebar_collapsed_width;
@@ -133,7 +211,7 @@
width: $sidebar_collapsed_width;
a {
- padding-left: 12px;
+ padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container {
display: none;
@@ -144,9 +222,13 @@
.nav-sidebar {
width: $sidebar_collapsed_width;
- li a {
- span {
- display: none;
+ li {
+ width: auto;
+
+ a {
+ span {
+ display: none;
+ }
}
}
}
@@ -156,7 +238,7 @@
}
.sidebar-user {
- padding-left: 12px;
+ padding-left: ($sidebar_collapsed_width - 36) / 2;
width: $sidebar_collapsed_width;
.username {
@@ -187,11 +269,11 @@
@media (max-width: $screen-md-max) {
.page-sidebar-collapsed {
- @include folded-sidebar;
+ @include collapsed-sidebar;
}
.page-sidebar-expanded {
- @include folded-sidebar;
+ @include collapsed-sidebar;
}
.collapse-nav {
@@ -201,83 +283,10 @@
@media(min-width: $screen-md-max) {
.page-sidebar-collapsed {
- @include folded-sidebar;
+ @include collapsed-sidebar;
}
.page-sidebar-expanded {
@include expanded-sidebar;
}
}
-
-.sidebar-user {
- padding: 9px 22px;
- position: fixed;
- bottom: 40px;
- width: $sidebar_width;
- overflow: hidden;
- transition-duration: .3s;
-
- .username {
- margin-left: 10px;
- width: $sidebar_width - 2 * 10px;
- font-size: 16px;
- line-height: 34px;
- }
-}
-
-.sidebar-wrapper {
- .header-logo {
- border-bottom: 1px solid transparent;
- float: left;
- height: $header-height;
- width: $sidebar_width;
- overflow: hidden;
- transition-duration: .3s;
-
- a {
- float: left;
- height: $header-height;
- width: 100%;
- padding: 10px 22px;
- overflow: hidden;
- outline: none;
-
- img {
- width: 36px;
- height: 36px;
- }
-
- #tanuki-logo, img {
- float: left;
- }
-
- .gitlab-text-container {
- width: 230px;
-
- h3 {
- width: 158px;
- float: left;
- margin: 0;
- margin-left: 14px;
- font-size: 19px;
- line-height: 41px;
- font-weight: normal;
- }
- }
- }
-
- &:hover {
- background-color: #EEE;
- }
- }
-}
-
-
-.tanuki-shape {
- transition: all 0.8s;
-
- &:hover {
- fill: rgb(255, 255, 255);
- transition: all 0.1s;
- }
-}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 66e16e8df75..793ab3d9bb9 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -6,6 +6,8 @@
table {
&.table {
+ margin-bottom: $gl-padding;
+
.dropdown-menu a {
text-decoration: none;
}
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index eb53c4153d3..ff41e26ed8a 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -10,8 +10,7 @@
margin-left: -$gl-padding;
margin-right: -$gl-padding;
color: $gl-gray;
- border-bottom: 1px solid #ECEEF1;
- border-right: 1px solid #ECEEF1;
+ border-bottom: 1px solid $border-white-light;
&:target {
background: $hover;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 50c0cf61f4e..94f0ed761df 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -190,6 +190,10 @@
.btn {
min-width: 124px;
}
+
+ .btn-clipboard {
+ min-width: 0px;
+ }
}
&.panel-small {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index ba0312ba0db..c3e4ad0ad00 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -181,6 +181,10 @@ body {
line-height: 1.3;
font-size: 1.25em;
font-weight: 600;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
}
.page-title-empty {
@@ -216,6 +220,7 @@ pre {
.monospace {
font-family: $monospace_font;
+ font-size: 90%;
}
code {
@@ -256,3 +261,9 @@ textarea.js-gfm-input {
.strikethrough {
text-decoration: line-through;
}
+
+h1, h2, h3, h4 {
+ small {
+ color: $gl-gray;
+ }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 91954683c3e..2ef40a6e517 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -19,7 +19,7 @@ $border-color: #dce0e6;
$table-border-color: #eef0f2;
$background-color: #F7F8FA;
$header-height: 58px;
-$fixed-layout-width: 1200px;
+$fixed-layout-width: 1280px;
$gl-gray: #7f8fa4;
$gl-padding: 16px;
$gl-avatar-size: 46px;
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
new file mode 100644
index 00000000000..041b811a606
--- /dev/null
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -0,0 +1,86 @@
+.awards {
+ @include clearfix;
+ line-height: 34px;
+
+ .award {
+ @include border-radius(5px);
+
+ border: 1px solid;
+ padding: 0px 10px;
+ float: left;
+ margin-right: 5px;
+ border-color: $border-color;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #dce0e5;
+ }
+
+ &.active {
+ border-color: $border-gray-light;
+ background-color: $gray-light;
+
+ &:hover {
+ background-color: #dce0e5;
+ }
+
+ .counter {
+ font-weight: bold;
+ }
+ }
+
+ .icon {
+ float: left;
+ margin-right: 10px;
+ }
+
+ .counter {
+ float: left;
+ }
+ }
+
+ .awards-controls {
+ margin-left: 10px;
+ float: left;
+
+ .add-award {
+ font-size: 24px;
+ color: $gl-gray;
+ position: relative;
+ top: 2px;
+
+ &:hover,
+ &:link {
+ text-decoration: none;
+ }
+ }
+
+ .awards-menu {
+ padding: $gl-padding;
+ min-width: 214px;
+
+ > li {
+ cursor: pointer;
+ width: 30px;
+ height: 30px;
+ text-align: center;
+ @include border-radius(5px);
+
+ img {
+ margin-bottom: 2px;
+ }
+
+ &:hover {
+ background-color: #ccc;
+ }
+ }
+ }
+ }
+
+ .awards-menu{
+ li {
+ float: left;
+ margin: 3px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index da9965f007a..3c2997c1d5a 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -67,9 +67,4 @@
color: #3084bb !important;
}
}
-
- .build-top-menu {
- margin-top: 0;
- margin-bottom: 2px;
- }
}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index a0e5f7554ed..17245d3be7b 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -2,10 +2,6 @@
display: block;
}
-.commit-title{
- margin-bottom: 10px;
-}
-
.commit-author, .commit-committer{
display: block;
color: #999;
@@ -41,6 +37,8 @@
.commit-box {
.commit-title {
margin: 0;
+ font-size: 23px;
+ color: #313236;
}
.commit-description {
@@ -108,16 +106,3 @@
z-index: 2;
}
}
-
-.commit-ci-menu {
- padding: 0;
- margin: 0;
- list-style: none;
- margin-top: 5px;
- height: 56px;
- margin: -16px;
- padding: 16px;
- text-align: center;
- margin-top: 0px;
- margin-bottom: 2px;
-}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
new file mode 100644
index 00000000000..0f3463a9144
--- /dev/null
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -0,0 +1,33 @@
+.detail-page-header {
+ margin: -$gl-padding;
+ padding: 7px $gl-padding;
+ margin-bottom: 0px;
+ border-bottom: 1px solid $border-color;
+ color: #5c5d5e;
+ font-size: 16px;
+ line-height: 42px;
+
+ .author {
+ color: #5c5d5e;
+ }
+
+ .identifier {
+ color: #5c5d5e;
+ }
+}
+
+.detail-page-description {
+ .title {
+ margin: 0;
+ font-size: 23px;
+ color: #313236;
+ }
+
+ .description {
+ margin-top: 6px;
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index e2c521af91e..39d916cd336 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -19,48 +19,38 @@
color: #B94A48;
}
}
- .commit-button-annotation {
- display: inline-block;
- margin: 0;
- padding: 2px;
-
- > * {
- float: left;
- }
-
- .message {
- display: inline-block;
- margin: 5px 8px 0 8px;
- }
- }
.file-title {
@extend .monospace;
+
+ line-height: 42px;
+ padding-top: 7px;
+ padding-bottom: 7px;
}
.editor-ref {
background: $background-color;
- padding: 11px 15px;
+ padding-right: $gl-padding;
border-right: 1px solid $border-color;
- display: inline-block;
- margin: -5px -5px;
+ display: block;
+ float: left;
margin-right: 10px;
}
.editor-file-name {
- .new-file-name {
- display: inline-block;
- width: 450px;
- }
+ @extend .monospace;
+
+ float: left;
+ margin-right: 10px;
+ }
- .form-control {
- margin-top: -3px;
- }
+ .new-file-name {
+ display: inline-block;
+ width: 450px;
+ float: left;
}
- .form-actions {
- margin: -$gl-padding;
- margin-top: 0;
- padding: $gl-padding
+ .select2 {
+ float: right;
}
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 07a38a19fad..263993f59a5 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -1,8 +1,3 @@
-.new-group-member-holder {
- margin-top: 50px;
- padding-top: 20px;
-}
-
.member-search-form {
float: left;
}
@@ -15,4 +10,4 @@
.form-control {
height: 42px;
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 3a08ee70bc7..9da273a0b6b 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -24,20 +24,6 @@
}
}
-.issuable-context-title {
- margin-bottom: 5px;
-
- .avatar {
- margin-left: 0;
- }
-
- label {
- color: $gl-gray;
- font-weight: normal;
- margin-right: 4px;
- }
-}
-
.project-issuable-filter {
.controls {
float: right;
@@ -50,33 +36,11 @@
}
.issuable-details {
- .page-title {
- margin-top: -15px;
- padding: 10px 0;
- margin-bottom: 0;
- color: #5c5d5e;
- font-size: 16px;
-
- .author {
- color: #5c5d5e;
- }
+ section {
+ border-right: 1px solid $border-white-light;
- .issue-id {
- color: #5c5d5e;
- }
- }
-
- .issue-title {
- margin: 0;
- font-size: 23px;
- color: #313236;
- }
-
- .description {
- margin-top: 6px;
-
- p:last-child {
- margin-bottom: 0;
+ .issuable-discussion {
+ margin-right: 1px;
}
}
}
@@ -89,83 +53,65 @@
}
}
-.cross-project-reference {
- text-align: center;
- width: 100%;
-
- .slead {
- padding: 5px;
- }
-
- span, button {
- background-color: $background-color;
+.issuable-show-labels {
+ a {
+ margin-right: 5px;
+ margin-bottom: 5px;
+ display: inline-block;
+ .color-label {
+ padding: 6px 10px;
+ }
}
}
-.awards {
- @include clearfix;
- line-height: 34px;
- margin: 2px 0;
-
- .award {
- @include border-radius(5px);
+.issuable-sidebar {
+ .block {
+ @include clearfix;
+ padding: $gl-padding 0;
+ border-bottom: 1px solid #F0F0F0;
- border: 1px solid;
- padding: 0px 10px;
- float: left;
- margin: 0 5px;
- border-color: $border-color;
- cursor: pointer;
+ &:last-child {
+ border: none;
+ }
+ }
- &.active {
- border-color: $border-gray-light;
- background-color: $gray-light;
+ .title {
+ color: $gl-text-color;
+ margin-bottom: 8px;
- .counter {
- font-weight: bold;
- }
+ .avatar {
+ margin-left: 0;
}
- .icon {
- float: left;
- margin-right: 10px;
+ label {
+ font-weight: normal;
+ margin-right: 4px;
}
- .counter {
- float: left;
+ .edit-link {
+ color: $gl-gray;
}
}
- .awards-controls {
- margin-left: 10px;
- float: left;
+ .cross-project-reference {
+ font-weight: bold;
+ color: $gl-link-color;
- .add-award {
- font-size: 24px;
- color: $gl-gray;
- position: relative;
- top: 2px;
-
- &:hover,
- &:link {
- text-decoration: none;
- }
+ button {
+ float: right;
}
+ }
- .awards-menu {
- padding: $gl-padding;
- min-width: 214px;
+ .selectbox {
+ display: none
+ }
- > li {
- margin: 5px;
- }
- }
+ .btn-clipboard {
+ color: $gl-gray;
}
- .awards-menu{
- li {
- float: left;
- margin: 3px;
- }
+ .participants .avatar {
+ margin-top: 6px;
+ margin-right: 2px;
}
}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 41c069f0ad3..a02a3a72e79 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -56,21 +56,30 @@
}
}
-.issue-show-labels {
- a {
- margin-right: 5px;
- margin-bottom: 5px;
- display: inline-block;
- .color-label {
- padding: 6px 10px;
- }
- }
-}
-
form.edit-issue {
margin: 0;
}
+.merge-requests-title {
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.merge-request-id {
+ display: inline-block;
+ width: 3em;
+}
+
+.merge-request-info {
+ padding-left: 5px;
+}
+
+.merge-request-status {
+ color: $gl-gray;
+ font-size: 15px;
+ font-weight: bold;
+}
+
.merge-request,
.issue {
&.today {
@@ -132,11 +141,6 @@ 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/login.scss b/app/assets/stylesheets/pages/login.scss
index 83b866c3a64..f9c6f1b39f9 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -19,6 +19,7 @@
h1:first-child {
font-weight: normal;
margin-bottom: 30px;
+ margin-top: 0;
}
img {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 08e4bcdf529..82effde0bf3 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -4,7 +4,6 @@
*/
.mr-state-widget {
background: #F7F8FA;
- margin-bottom: 20px;
color: $gl-gray;
border: 1px solid #dce0e6;
@include border-radius(2px);
@@ -19,6 +18,7 @@
.accept-merge-holder {
.accept-action {
display: inline-block;
+ float: left;
.accept_merge_request {
&.ci-pending,
@@ -37,14 +37,15 @@
.accept-control {
display: inline-block;
+ float: left;
margin: 0;
margin-left: 20px;
padding: 5px;
+ padding-top: 12px;
line-height: 20px;
&.right {
float: right;
- padding-top: 12px;
a {
color: $gl-gray;
}
@@ -82,12 +83,16 @@
&.ci-error {
color: $gl-danger;
}
+
+ a.monospace {
+ color: inherit;
+ }
}
.mr-widget-body,
.ci_widget,
.mr-widget-footer {
- padding: 15px;
+ padding: $gl-padding;
}
.normal {
@@ -116,28 +121,6 @@
}
}
-.merge-request .merge-request-tabs {
- @include nav-menu;
- margin: -$gl-padding;
- padding: $gl-padding;
- text-align: center;
- margin-bottom: 1px;
-}
-
-// Mobile
-@media (max-width: 480px) {
- .merge-request .merge-request-tabs {
- margin: 0;
- padding: 0;
-
- li {
- a {
- padding: 0;
- }
- }
- }
-}
-
.mr_source_commit,
.mr_target_commit {
.commit {
@@ -155,7 +138,7 @@
font-family: $monospace_font;
font-weight: bold;
overflow: hidden;
- font-size: 14px;
+ font-size: 90%;
margin: 0 3px;
}
@@ -192,27 +175,12 @@
line-height: 1.1;
}
-.merge-request-form-info {
- padding-top: 15px;
-}
-
// hide mr close link for inline diff comment form
.diff-file .close-mr-link,
.diff-file .reopen-mr-link {
display: none;
}
-.merge-request-show-labels {
- a {
- margin-right: 5px;
- margin-bottom: 5px;
- display: inline-block;
- .color-label {
- padding: 6px 10px;
- }
- }
-}
-
.merge-request-form .select2-container {
width: 250px !important;
}
@@ -223,7 +191,7 @@
.btn-clipboard {
@extend .pull-right;
- margin-right: 18px;
+ margin-right: 20px;
margin-top: 5px;
position: absolute;
right: 0;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 268fc995aa7..4cf1a28c459 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -7,6 +7,7 @@
}
.reply-btn {
@extend .btn-primary;
+ margin: 10px $gl-padding;
}
.diff-file .diff-content {
tr.line_holder:hover {
@@ -38,9 +39,8 @@
}
.new_note, .edit_note {
- .buttons {
- margin-top: 8px;
- margin-bottom: 3px;
+ .note-form-actions {
+ margin-top: $gl-padding;
}
.note-preview-holder {
@@ -79,8 +79,7 @@
padding: $gl-padding;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
- border-right: 1px solid #ECEEF1;
- border-top: 1px solid #ECEEF1;
+ border-top: 1px solid $border-color;
margin-bottom: -$gl-padding;
}
@@ -150,7 +149,6 @@
.discussion-reply-holder {
background: $background-color;
- padding: 10px 15px;
border-top: 1px solid $border-color;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 1980fe0d458..4dff87abaa4 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -109,13 +109,9 @@ ul.notes {
}
}
- // Reduce left padding of first task list ul element
- ul.task-list:first-child {
- padding-left: 10px;
-
- // sub-tasks should be padded normally
- ul {
- padding-left: 20px;
+ ul.task-list {
+ ul:not(.task-list) {
+ padding-left: 1.3em;
}
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 1d6ca0dfc13..95fc26a608a 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -5,12 +5,6 @@
}
}
-.btn-build-token {
- float: left;
- padding: 6px 20px;
- margin-right: 12px;
-}
-
.profile-avatar-form-option {
hr {
margin: 10px 0;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index d3b10040022..2ded32dba12 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -5,7 +5,7 @@
font-weight: normal;
}
}
-.no-ssh-key-message {
+.no-ssh-key-message, .project-limit-message {
background-color: #f28d35;
margin-bottom: 16px;
}
@@ -18,10 +18,6 @@
}
}
-.project-edit-content {
- padding: 7px;
-}
-
.project-name-holder {
.help-inline {
vertical-align: top;
@@ -30,12 +26,6 @@
}
.project-home-panel {
- text-align: center;
- background: #f7f8fa;
- margin: -$gl-padding;
- padding: $gl-padding;
- padding: 44px 0 17px 0;
-
.project-identicon-holder {
margin-bottom: 16px;
@@ -90,7 +80,12 @@
}
.visibility-level-label {
+ @extend .btn;
+ @extend .btn-gray;
+
color: $gray;
+ cursor: default;
+
i {
color: inherit;
}
@@ -100,7 +95,6 @@
display: inline-table;
position: relative;
top: 17px;
- margin-bottom: 44px;
}
.project-repo-buttons {
@@ -178,6 +172,11 @@
&:active {
outline: none;
}
+
+ &.btn-clipboard {
+ padding-left: 15px;
+ padding-right: 15px;
+ }
}
.active {
@@ -366,7 +365,7 @@ table.table.protected-branches-list tr.no-border {
.project-stats {
text-align: center;
- margin-top: 15px;
+ margin-top: $gl-padding;
margin-bottom: 0;
padding-top: 10px;
padding-bottom: 4px;
@@ -552,4 +551,4 @@ pre.light-well {
z-index: 100;
position: relative;
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 242783a7b7e..1430d01859d 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -27,56 +27,28 @@
}
.snippet-holder {
- .snippet-details {
- .page-title {
- margin-top: -15px;
- padding: 10px 0;
- margin-bottom: 0;
- color: #5c5d5e;
- font-size: 16px;
-
- .author {
- color: #5c5d5e;
- }
-
- .snippet-id {
- color: #5c5d5e;
- }
- }
-
- .snippet-title {
- margin: 0;
- font-size: 23px;
- color: #313236;
- }
-
- @media (max-width: $screen-md-max) {
- .new-snippet-link {
- display: none;
- }
- }
-
- @media (max-width: $screen-sm-max) {
- .creator,
- .page-title .btn-close {
- display: none;
- }
- }
- }
+ margin-bottom: -$gl-padding;
.file-holder {
border-top: 0;
}
-}
+ .file-actions {
+ .btn-clipboard {
+ @extend .btn;
+ }
+ }
+}
.snippet-box {
@include border-radius(2px);
- display: inline-block;
- padding: 10px $gl-padding;
+ display: block;
+ float: left;
+ padding: 0 $gl-padding;
font-weight: normal;
margin-right: 10px;
font-size: $gl-font-size;
border: 1px solid;
+ line-height: 40px;
}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index a7d3b2197f1..4b6ef035673 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -35,3 +35,20 @@
border-color: $gl-warning;
}
}
+
+.ci-status-icon-success {
+ @extend .cgreen;
+}
+.ci-status-icon-failed {
+ @extend .cred;
+}
+.ci-status-icon-running,
+.ci-status-icon-pending {
+ // These are standard text color
+}
+.ci-status-icon-canceled,
+.ci-status-icon-disabled,
+.ci-status-icon-not-found,
+.ci-status-icon-skipped {
+ @extend .cgray;
+}
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index 277afa1db9e..185f3622e64 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -1,9 +1,6 @@
.gitlab-ui-dev-kit {
> h2 {
- font-size: 27px;
- border-bottom: 1px solid #CCC;
- color: #666;
- margin: 30px 0;
+ margin: 35px 0 20px;
font-weight: bold;
}
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index dfaeba41cf6..cdf514197cb 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -4,3 +4,8 @@
margin-right: auto;
padding-right: 7px;
}
+
+.wiki-last-edit-by {
+ font-size: 80%;
+ font-weight: normal;
+}
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 2f4054eaa11..20bc5173f1d 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -10,7 +10,7 @@ class AbuseReportsController < ApplicationController
if @abuse_report.save
if current_application_settings.admin_notification_email.present?
- AbuseReportMailer.delay.notify(@abuse_report.id)
+ AbuseReportMailer.notify(@abuse_report.id).deliver_later
end
message = "Thank you for your report. A GitLab administrator will look into it shortly."
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index a9bcfc7456a..9dd16f8c735 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -13,6 +13,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
end
+ def reset_runners_token
+ @application_setting.reset_runners_registration_token!
+ flash[:notice] = 'New runners registration token has been generated!'
+ redirect_to admin_runners_path
+ end
+
private
def set_application_setting
diff --git a/app/controllers/admin/builds_controller.rb b/app/controllers/admin/builds_controller.rb
new file mode 100644
index 00000000000..83d9684c706
--- /dev/null
+++ b/app/controllers/admin/builds_controller.rb
@@ -0,0 +1,23 @@
+class Admin::BuildsController < Admin::ApplicationController
+ def index
+ @scope = params[:scope]
+ @all_builds = Ci::Build
+ @builds = @all_builds.order('created_at DESC')
+ @builds =
+ case @scope
+ when 'all'
+ @builds
+ when 'finished'
+ @builds.finished
+ else
+ @builds.running_or_pending.reverse_order
+ end
+ @builds = @builds.page(params[:page]).per(30)
+ end
+
+ def cancel_all
+ Ci::Build.running_or_pending.each(&:cancel)
+
+ redirect_to admin_builds_path
+ end
+end
diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb
index 0382402afa6..bf98af78615 100644
--- a/app/controllers/admin/impersonation_controller.rb
+++ b/app/controllers/admin/impersonation_controller.rb
@@ -5,14 +5,20 @@ class Admin::ImpersonationController < Admin::ApplicationController
before_action :authorize_impersonator!
def create
- session[:impersonator_id] = current_user.username
- session[:impersonator_return_to] = request.env['HTTP_REFERER']
+ if @user.blocked?
+ flash[:alert] = "You cannot impersonate a blocked user"
- warden.set_user(user, scope: 'user')
+ redirect_to admin_user_path(@user)
+ else
+ session[:impersonator_id] = current_user.username
+ session[:impersonator_return_to] = admin_user_path(@user)
+
+ warden.set_user(user, scope: 'user')
- flash[:alert] = "You are impersonating #{user.username}."
+ flash[:alert] = "You are impersonating #{user.username}."
- redirect_to root_path
+ redirect_to root_path
+ end
end
def destroy
diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb
new file mode 100644
index 00000000000..d25619d94e0
--- /dev/null
+++ b/app/controllers/admin/runner_projects_controller.rb
@@ -0,0 +1,35 @@
+class Admin::RunnerProjectsController < Admin::ApplicationController
+ before_action :project, only: [:create]
+
+ def index
+ @runner_projects = project.runner_projects.all
+ @runner_project = project.runner_projects.new
+ end
+
+ def create
+ @runner = Ci::Runner.find(params[:runner_project][:runner_id])
+
+ if @runner.assign_to(@project, current_user)
+ redirect_to admin_runner_path(@runner)
+ else
+ redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
+ end
+ end
+
+ def destroy
+ rp = Ci::RunnerProject.find(params[:id])
+ runner = rp.runner
+ rp.destroy
+
+ redirect_to admin_runner_path(runner)
+ end
+
+ private
+
+ def project
+ @project = Project.find_with_namespace(
+ [params[:namespace_id], '/', params[:project_id]].join('')
+ )
+ @project || render_404
+ end
+end
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
new file mode 100644
index 00000000000..a701d49b844
--- /dev/null
+++ b/app/controllers/admin/runners_controller.rb
@@ -0,0 +1,63 @@
+class Admin::RunnersController < Admin::ApplicationController
+ before_action :runner, except: :index
+
+ def index
+ @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.online.count
+ end
+
+ def show
+ @builds = @runner.builds.order('id DESC').first(30)
+ @projects =
+ if params[:search].present?
+ ::Project.search(params[:search])
+ else
+ Project.all
+ end
+ @projects = @projects.where.not(id: @runner.projects.select(:id)) if @runner.projects.any?
+ @projects = @projects.page(params[:page]).per(30)
+ end
+
+ def update
+ @runner.update_attributes(runner_params)
+
+ respond_to do |format|
+ format.js
+ format.html { redirect_to admin_runner_path(@runner) }
+ end
+ end
+
+ def destroy
+ @runner.destroy
+
+ redirect_to admin_runners_path
+ end
+
+ def resume
+ if @runner.update_attributes(active: true)
+ redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
+ else
+ redirect_to admin_runners_path, alert: 'Runner was not updated.'
+ end
+ end
+
+ def pause
+ if @runner.update_attributes(active: false)
+ redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
+ else
+ redirect_to admin_runners_path, alert: 'Runner was not updated.'
+ end
+ end
+
+ private
+
+ def runner
+ @runner ||= Ci::Runner.find(params[:id])
+ end
+
+ def runner_params
+ params.require(:runner).permit(:token, :description, :tag_list, :active)
+ end
+end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 202e9da9eee..77c8dafc012 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -1,41 +1,15 @@
class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users]
+ before_action :find_users, only: [:users]
def users
- begin
- @users =
- if params[:project_id].present?
- project = Project.find(params[:project_id])
-
- if can?(current_user, :read_project, project)
- project.team.users
- end
- elsif params[:group_id]
- group = Group.find(params[:group_id])
-
- if can?(current_user, :read_group, group)
- group.users
- end
- elsif current_user
- User.all
- end
- rescue ActiveRecord::RecordNotFound
- if current_user
- return render json: {}, status: 404
- end
- end
-
- if @users.nil? && current_user.nil?
- authenticate_user!
- end
-
@users ||= User.none
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
@users = @users.reorder(:name)
@users = @users.page(params[:page]).per(PER_PAGE)
- unless params[:search].present?
+ if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user] && current_user
@users = [*@users, current_user].uniq
@@ -49,4 +23,25 @@ class AutocompleteController < ApplicationController
@user = User.find(params[:id])
render json: @user, only: [:name, :username, :id], methods: [:avatar_url]
end
+
+ private
+
+ def find_users
+ @users =
+ if params[:project_id].present?
+ project = Project.find(params[:project_id])
+ return render_404 unless can?(current_user, :read_project, project)
+
+ project.team.users
+ elsif params[:group_id].present?
+ group = Group.find(params[:group_id])
+ return render_404 unless can?(current_user, :read_group, group)
+
+ group.users
+ elsif current_user
+ User.all
+ else
+ User.none
+ end
+ end
end
diff --git a/app/controllers/ci/admin/application_controller.rb b/app/controllers/ci/admin/application_controller.rb
deleted file mode 100644
index 4ec2dc9c2cf..00000000000
--- a/app/controllers/ci/admin/application_controller.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Ci
- module Admin
- class ApplicationController < Ci::ApplicationController
- before_action :authenticate_user!
- before_action :authenticate_admin!
-
- layout "ci/admin"
- end
- end
-end
diff --git a/app/controllers/ci/admin/application_settings_controller.rb b/app/controllers/ci/admin/application_settings_controller.rb
deleted file mode 100644
index 71e253fac67..00000000000
--- a/app/controllers/ci/admin/application_settings_controller.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module Ci
- class Admin::ApplicationSettingsController < Ci::Admin::ApplicationController
- before_action :set_application_setting
-
- def show
- end
-
- def update
- if @application_setting.update_attributes(application_setting_params)
- redirect_to ci_admin_application_settings_path,
- notice: 'Application settings saved successfully'
- else
- render :show
- end
- end
-
- private
-
- def set_application_setting
- @application_setting = Ci::ApplicationSetting.current
- @application_setting ||= Ci::ApplicationSetting.create_from_defaults
- end
-
- def application_setting_params
- params.require(:application_setting).permit(
- :all_broken_builds,
- :add_pusher,
- )
- end
- end
-end
diff --git a/app/controllers/ci/admin/builds_controller.rb b/app/controllers/ci/admin/builds_controller.rb
deleted file mode 100644
index 38abfdeafbf..00000000000
--- a/app/controllers/ci/admin/builds_controller.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module Ci
- class Admin::BuildsController < Ci::Admin::ApplicationController
- def index
- @scope = params[:scope]
- @builds = Ci::Build.order('created_at DESC').page(params[:page]).per(30)
-
- @builds =
- case @scope
- when "pending"
- @builds.pending
- when "running"
- @builds.running
- else
- @builds
- end
- end
- end
-end
diff --git a/app/controllers/ci/admin/events_controller.rb b/app/controllers/ci/admin/events_controller.rb
deleted file mode 100644
index 5939efff980..00000000000
--- a/app/controllers/ci/admin/events_controller.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Ci
- class Admin::EventsController < Ci::Admin::ApplicationController
- EVENTS_PER_PAGE = 50
-
- def index
- @events = Ci::Event.admin.order('created_at DESC').page(params[:page]).per(EVENTS_PER_PAGE)
- end
- end
-end
diff --git a/app/controllers/ci/admin/projects_controller.rb b/app/controllers/ci/admin/projects_controller.rb
deleted file mode 100644
index 5bbd0ce7396..00000000000
--- a/app/controllers/ci/admin/projects_controller.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Ci
- class Admin::ProjectsController < Ci::Admin::ApplicationController
- def index
- @projects = Ci::Project.ordered_by_last_commit_date.page(params[:page]).per(30)
- end
-
- def destroy
- project.destroy
-
- redirect_to ci_projects_url
- end
-
- protected
-
- def project
- @project ||= Ci::Project.find(params[:id])
- end
- end
-end
diff --git a/app/controllers/ci/admin/runner_projects_controller.rb b/app/controllers/ci/admin/runner_projects_controller.rb
deleted file mode 100644
index e7de6eb12ca..00000000000
--- a/app/controllers/ci/admin/runner_projects_controller.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Ci
- class Admin::RunnerProjectsController < Ci::Admin::ApplicationController
- layout 'ci/project'
-
- def index
- @runner_projects = project.runner_projects.all
- @runner_project = project.runner_projects.new
- end
-
- def create
- @runner = Ci::Runner.find(params[:runner_project][:runner_id])
-
- if @runner.assign_to(project, current_user)
- redirect_to ci_admin_runner_path(@runner)
- else
- redirect_to ci_admin_runner_path(@runner), alert: 'Failed adding runner to project'
- end
- end
-
- def destroy
- rp = Ci::RunnerProject.find(params[:id])
- runner = rp.runner
- rp.destroy
-
- redirect_to ci_admin_runner_path(runner)
- end
-
- private
-
- def project
- @project ||= Ci::Project.find(params[:project_id])
- end
- end
-end
diff --git a/app/controllers/ci/admin/runners_controller.rb b/app/controllers/ci/admin/runners_controller.rb
deleted file mode 100644
index 0cafad27418..00000000000
--- a/app/controllers/ci/admin/runners_controller.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-module Ci
- class Admin::RunnersController < Ci::Admin::ApplicationController
- before_action :runner, except: :index
-
- def index
- @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.online.count
- end
-
- def show
- @builds = @runner.builds.order('id DESC').first(30)
- @projects = Ci::Project.all
- if params[:search].present?
- @gl_projects = ::Project.search(params[:search])
- @projects = @projects.where(gitlab_id: @gl_projects.select(:id))
- end
- @projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any?
- @projects = @projects.joins(:gl_project)
- @projects = @projects.page(params[:page]).per(30)
- end
-
- def update
- @runner.update_attributes(runner_params)
-
- respond_to do |format|
- format.js
- format.html { redirect_to ci_admin_runner_path(@runner) }
- end
- end
-
- def destroy
- @runner.destroy
-
- redirect_to ci_admin_runners_path
- end
-
- def resume
- if @runner.update_attributes(active: true)
- redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
- else
- redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
- end
- end
-
- def pause
- if @runner.update_attributes(active: false)
- redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
- else
- redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
- end
- end
-
- def assign_all
- Ci::Project.unassigned(@runner).all.each do |project|
- @runner.assign_to(project, current_user)
- end
-
- redirect_to ci_admin_runner_path(@runner), notice: "Runner was assigned to all projects"
- end
-
- private
-
- def runner
- @runner ||= Ci::Runner.find(params[:id])
- end
-
- def runner_params
- params.require(:runner).permit(:token, :description, :tag_list, :active)
- end
- end
-end
diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb
index 848f2b4e314..c420b59c3a2 100644
--- a/app/controllers/ci/application_controller.rb
+++ b/app/controllers/ci/application_controller.rb
@@ -4,24 +4,16 @@ module Ci
"app/helpers/ci"
end
- helper_method :gl_project
-
private
- def authenticate_token!
- unless project.valid_token?(params[:token])
- return head(403)
- end
- end
-
def authorize_access_project!
- unless can?(current_user, :read_project, gl_project)
+ unless can?(current_user, :read_project, project)
return page_404
end
end
def authorize_manage_builds!
- unless can?(current_user, :manage_builds, gl_project)
+ unless can?(current_user, :manage_builds, project)
return page_404
end
end
@@ -31,7 +23,7 @@ module Ci
end
def authorize_manage_project!
- unless can?(current_user, :admin_project, gl_project)
+ unless can?(current_user, :admin_project, project)
return page_404
end
end
@@ -58,9 +50,5 @@ module Ci
count: count
}
end
-
- def gl_project
- ::Project.find(@project.gitlab_id)
- end
end
end
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index a4f6aff49b4..7ed78ff8e98 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -1,5 +1,5 @@
module Ci
- class LintsController < Ci::ApplicationController
+ class LintsController < ApplicationController
before_action :authenticate_user!
def show
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index 8406399fb60..3004c2d27f0 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -3,13 +3,12 @@ module Ci
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)
+ redirect_to namespace_project_path(project.namespace, project)
end
# Project status badge
@@ -20,16 +19,10 @@ module Ci
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
end
- def toggle_shared_runners
- project.toggle!(:shared_runners_enabled)
-
- redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project)
- end
-
protected
def project
- @project ||= Ci::Project.find(params[:id])
+ @project ||= Project.find_by(ci_id: params[:id].to_i)
end
def no_cache
diff --git a/app/controllers/ci/runner_projects_controller.rb b/app/controllers/ci/runner_projects_controller.rb
deleted file mode 100644
index 9d555313369..00000000000
--- a/app/controllers/ci/runner_projects_controller.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Ci
- class RunnerProjectsController < Ci::ApplicationController
- before_action :authenticate_user!
- before_action :project
- before_action :authorize_manage_project!
-
- def create
- @runner = Ci::Runner.find(params[:runner_project][:runner_id])
-
- return head(403) unless current_user.ci_authorized_runners.include?(@runner)
-
- path = runners_path(@project.gl_project)
-
- if @runner.assign_to(project, current_user)
- redirect_to path
- else
- redirect_to path, alert: 'Failed adding runner to project'
- end
- end
-
- def destroy
- runner_project = project.runner_projects.find(params[:id])
- runner_project.destroy
-
- redirect_to runners_path(@project.gl_project)
- end
-
- private
-
- def project
- @project ||= Ci::Project.find(params[:project_id])
- end
- end
-end
diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb
index b428249acd3..3e4c0e63601 100644
--- a/app/controllers/concerns/global_milestones.rb
+++ b/app/controllers/concerns/global_milestones.rb
@@ -2,8 +2,10 @@ module GlobalMilestones
extend ActiveSupport::Concern
def milestones
+ epoch = DateTime.parse('1970-01-01')
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
+ @milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
@milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
end
diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb
index f4354c6d8ca..b3594d82530 100644
--- a/app/controllers/dashboard/snippets_controller.rb
+++ b/app/controllers/dashboard/snippets_controller.rb
@@ -1,6 +1,7 @@
class Dashboard::SnippetsController < Dashboard::ApplicationController
def index
- @snippets = SnippetsFinder.new.execute(current_user,
+ @snippets = SnippetsFinder.new.execute(
+ current_user,
filter: :by_user,
user: current_user,
scope: params[:scope]
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 10233222ee1..0c2a350bc39 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def milestone_path(title)
- group_milestone_path(@group, title.parameterize, title: title)
+ group_milestone_path(@group, title.to_slug.to_s, title: title)
end
def projects
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 2025158d065..f74daff3bd0 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -40,7 +40,9 @@ class PasswordsController < Devise::PasswordsController
def throttle_reset
return unless resource && resource.recently_sent_password_reset?
- redirect_to new_password_path(resource_name),
- alert: I18n.t('devise.passwords.recently_reset')
+ # Throttle reset attempts, but return a normal message to
+ # avoid user enumeration attack.
+ redirect_to new_user_session_path,
+ notice: I18n.t('devise.passwords.send_paranoid_instructions')
end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 8da7b4d50ea..28803164fcf 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController
:email,
:hide_no_password,
:hide_no_ssh_key,
+ :hide_project_limit,
:linkedin,
:location,
:name,
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index d3f926b62bc..dd32d509191 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -21,18 +21,14 @@ class Projects::ApplicationController < ApplicationController
unless @repository.branch_names.include?(@ref)
redirect_to(
namespace_project_tree_path(@project.namespace, @project, @ref),
- notice: "This action is not allowed unless you are on top of a branch"
+ notice: "This action is not allowed unless you are on a branch"
)
end
end
private
- def ci_enabled
+ def builds_enabled
return render_404 unless @project.builds_enabled?
end
-
- def ci_project
- @ci_project ||= @project.ensure_gitlab_ci_project
- end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 31a33bfd237..62163682936 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -162,12 +162,20 @@ class Projects::BlobController < Projects::ApplicationController
end
def sanitized_new_branch_name
- @new_branch ||= sanitize(strip_tags(params[:new_branch]))
+ sanitize(strip_tags(params[:new_branch]))
end
def editor_variables
@current_branch = @ref
- @new_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
+
+ @new_branch =
+ if params[:new_branch].present?
+ sanitized_new_branch_name
+ elsif ::Gitlab::GitAccess.new(current_user, @project).can_push_to_branch?(@ref)
+ @ref
+ else
+ @repository.next_patch_branch
+ end
@file_path =
if action_name.to_s == 'create'
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 3ac0a75fa70..3c2849a7601 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -3,7 +3,7 @@ class Projects::BranchesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
- before_action :authorize_push_code!, only: [:create, :destroy]
+ before_action :authorize_push_code!, only: [:new, :create, :destroy]
def index
@sort = params[:sort] || 'name'
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 4638f77b887..26ba12520c7 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,5 +1,4 @@
class Projects::BuildsController < Projects::ApplicationController
- before_action :ci_project
before_action :build, except: [:index, :cancel_all]
before_action :authorize_manage_builds!, except: [:index, :show, :status]
@@ -9,7 +8,7 @@ class Projects::BuildsController < Projects::ApplicationController
def index
@scope = params[:scope]
- @all_builds = project.ci_builds
+ @all_builds = project.builds
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
@@ -24,13 +23,13 @@ class Projects::BuildsController < Projects::ApplicationController
end
def cancel_all
- @project.ci_builds.running_or_pending.each(&:cancel)
+ @project.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 = @project.ci_commits.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id)
@commit = @build.commit
@@ -77,7 +76,7 @@ class Projects::BuildsController < Projects::ApplicationController
private
def build
- @build ||= ci_project.builds.unscoped.find_by!(id: params[:id])
+ @build ||= project.builds.unscoped.find_by!(id: params[:id])
end
def artifacts_file
@@ -85,7 +84,7 @@ class Projects::BuildsController < Projects::ApplicationController
end
def build_path(build)
- namespace_project_build_path(build.gl_project.namespace, build.gl_project, build)
+ namespace_project_build_path(build.project.namespace, build.project, build)
end
def authorize_manage_builds!
diff --git a/app/controllers/projects/ci_services_controller.rb b/app/controllers/projects/ci_services_controller.rb
deleted file mode 100644
index 550a019e8e2..00000000000
--- a/app/controllers/projects/ci_services_controller.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-class Projects::CiServicesController < Projects::ApplicationController
- before_action :ci_project
- before_action :authorize_admin_project!
-
- layout "project_settings"
-
- def index
- @ci_project.build_missing_services
- @services = @ci_project.services.reload
- end
-
- def edit
- service
- end
-
- def update
- 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.ci_builds.last
-
- 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_back_or_default(options: message)
- end
-
- private
-
- def service
- @service ||= @ci_project.services.find { |service| service.to_param == params[:id] }
- end
-
- def service_params
- params.require(:service).permit(
- :type, :active, :webhook, :notify_only_broken_builds,
- :email_recipients, :email_only_broken_builds, :email_add_pusher,
- :hipchat_token, :hipchat_room, :hipchat_server
- )
- end
-end
diff --git a/app/controllers/projects/ci_settings_controller.rb b/app/controllers/projects/ci_settings_controller.rb
deleted file mode 100644
index a263242a850..00000000000
--- a/app/controllers/projects/ci_settings_controller.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-class Projects::CiSettingsController < Projects::ApplicationController
- before_action :ci_project
- before_action :authorize_admin_project!
-
- layout "project_settings"
-
- def edit
- end
-
- def update
- if ci_project.update_attributes(project_params)
- Ci::EventService.new.change_project_settings(current_user, ci_project)
-
- redirect_to edit_namespace_project_ci_settings_path(project.namespace, project), notice: 'Project was successfully updated.'
- else
- render action: "edit"
- end
- end
-
- def destroy
- ci_project.destroy
- Ci::EventService.new.remove_project(current_user, ci_project)
- project.gitlab_ci_service.update_attributes(active: false)
-
- redirect_to project_path(project), notice: "CI was disabled for this project"
- end
-
- protected
-
- def project_params
- params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
- :polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients,
- :email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
- { variables_attributes: [:id, :key, :value, :_destroy] })
- end
-end
diff --git a/app/controllers/projects/ci_web_hooks_controller.rb b/app/controllers/projects/ci_web_hooks_controller.rb
deleted file mode 100644
index a2d470d4a69..00000000000
--- a/app/controllers/projects/ci_web_hooks_controller.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-class Projects::CiWebHooksController < Projects::ApplicationController
- before_action :ci_project
- before_action :authorize_admin_project!
-
- layout "project_settings"
-
- def index
- @web_hooks = @ci_project.web_hooks
- @web_hook = Ci::WebHook.new
- end
-
- def create
- @web_hook = @ci_project.web_hooks.new(web_hook_params)
- @web_hook.save
-
- if @web_hook.valid?
- redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
- else
- @web_hooks = @ci_project.web_hooks.select(&:persisted?)
- render :index
- end
- end
-
- def test
- Ci::TestHookService.new.execute(hook, current_user)
-
- redirect_back_or_default(default: { action: 'index' })
- end
-
- def destroy
- hook.destroy
-
- redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
- end
-
- private
-
- def hook
- @web_hook ||= @ci_project.web_hooks.find(params[:id])
- end
-
- def web_hook_params
- params.require(:web_hook).permit(:url)
- end
-end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index deefdd76667..0aaba3792bf 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -31,13 +31,12 @@ class Projects::CommitController < Projects::ApplicationController
end
def builds
- @ci_project = @project.gitlab_ci_project
end
def cancel_builds
ci_commit.builds.running_or_pending.each(&:cancel)
- redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha)
+ redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end
def retry_builds
@@ -47,7 +46,7 @@ class Projects::CommitController < Projects::ApplicationController
end
end
- redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha)
+ redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end
def branches
@@ -67,10 +66,15 @@ class Projects::CommitController < Projects::ApplicationController
end
def define_show_vars
- @diffs = commit.diffs
+ if params[:w].to_i == 1
+ @diffs = commit.diffs({ ignore_whitespace_change: true })
+ else
+ @diffs = commit.diffs
+ end
+
@notes_count = commit.notes.count
-
- @builds = ci_commit.builds if ci_commit
+
+ @statuses = ci_commit.statuses if ci_commit
end
def authorize_manage_builds!
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 418b92040bc..d13ea9f34b6 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -5,7 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
- before_action :ci_enabled, only: :ci
+ before_action :builds_enabled, only: :ci
def show
respond_to do |format|
@@ -25,13 +25,31 @@ class Projects::GraphsController < Projects::ApplicationController
end
def ci
- ci_project = @project.gitlab_ci_project
-
@charts = {}
- @charts[:week] = Ci::Charts::WeekChart.new(ci_project)
- @charts[:month] = Ci::Charts::MonthChart.new(ci_project)
- @charts[:year] = Ci::Charts::YearChart.new(ci_project)
- @charts[:build_times] = Ci::Charts::BuildTime.new(ci_project)
+ @charts[:week] = Ci::Charts::WeekChart.new(project)
+ @charts[:month] = Ci::Charts::MonthChart.new(project)
+ @charts[:year] = Ci::Charts::YearChart.new(project)
+ @charts[:build_times] = Ci::Charts::BuildTime.new(project)
+ end
+
+ def languages
+ @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages
+ total = @languages.map(&:last).sum
+
+ @languages = @languages.map do |language|
+ name, share = language
+ color = Digest::SHA256.hexdigest(name)[0...6]
+ {
+ value: (share.to_f * 100 / total).round(2),
+ label: name,
+ color: "##{color}",
+ highlight: "##{color}"
+ }
+ end
+
+ @languages.sort! do |x, y|
+ y[:value] <=> x[:value]
+ end
end
private
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index c7569541899..5fd4f855dec 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -25,13 +25,12 @@ class Projects::HooksController < Projects::ApplicationController
def test
if !@project.empty_repo?
- status = TestHookService.new.execute(hook, current_user)
+ status, message = TestHookService.new.execute(hook, current_user)
if status
flash[:notice] = 'Hook successfully executed.'
else
- flash[:alert] = 'Hook execution failed. '\
- 'Ensure hook URL is correct and service is up.'
+ flash[:alert] = "Hook execution failed: #{message}"
end
else
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
@@ -54,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController
def hook_params
params.require(:hook).permit(:url, :push_events, :issues_events,
- :merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification)
+ :merge_requests_events, :tag_push_events, :note_events,
+ :build_events, :enable_ssl_verification)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 5250a0f5e67..b59b52291fb 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -58,10 +58,10 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
- @participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
+ @merge_requests = @issue.referenced_merge_requests
respond_with(@issue)
end
@@ -158,12 +158,10 @@ class Projects::IssuesController < Projects::ApplicationController
end
def issue_params
- permitted = params.require(:issue).permit(
+ params.require(:issue).permit(
:title, :assignee_id, :position, :description,
:milestone_id, :state_event, :task_num, label_ids: []
)
- params[:issue][:title].strip! if params[:issue][:title]
- permitted
end
def bulk_update_params
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 6378a1f56b0..ab5c953189c 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,13 +1,14 @@
class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
before_action :merge_request, only: [
- :edit, :update, :show, :diffs, :commits, :merge, :merge_check,
- :ci_status, :toggle_subscription
+ :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
+ :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds
]
- before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
- before_action :validates_merge_request, only: [:show, :diffs, :commits]
- before_action :define_show_vars, only: [:show, :diffs, :commits]
- before_action :ensure_ref_fetched, only: [:show, :commits, :diffs]
+ before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
+ before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
+ before_action :define_show_vars, only: [:show, :diffs, :commits, :builds]
+ before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
+ before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
# Allow read any merge_request
before_action :authorize_read_merge_request!
@@ -79,6 +80,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def builds
+ respond_to do |format|
+ format.html { render 'show' }
+ format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } }
+ end
+ end
+
def new
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
@@ -91,20 +99,18 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project
@source_project = merge_request.source_project
- @commits = @merge_request.compare_commits
+ @commits = @merge_request.compare_commits.reverse
@commit = @merge_request.last_commit
@first_commit = @merge_request.first_commit
@diffs = @merge_request.compare_diffs
+
+ @ci_commit = @merge_request.ci_commit
+ @statuses = @ci_commit.statuses if @ci_commit
+
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
end
- def edit
- @source_project = @merge_request.source_project
- @target_project = @merge_request.target_project
- @target_branches = @merge_request.target_project.repository.branch_names
- end
-
def create
@target_branches ||= []
@merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
@@ -118,6 +124,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def edit
+ @source_project = @merge_request.source_project
+ @target_project = @merge_request.target_project
+ @target_branches = @merge_request.target_project.repository.branch_names
+ end
+
def update
@merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
@@ -141,24 +153,34 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_check
- if @merge_request.unchecked?
- @merge_request.check_if_can_be_merged
- end
-
- closes_issues
+ @merge_request.check_if_can_be_merged if @merge_request.unchecked?
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
+ def cancel_merge_when_build_succeeds
+ return access_denied! unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+
+ MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user).cancel(@merge_request)
+ end
+
def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
- if @merge_request.mergeable?
- @merge_request.update(merge_error: nil)
- MergeWorker.perform_async(@merge_request.id, current_user.id, params)
- @status = true
+ unless @merge_request.mergeable?
+ @status = :failed
+ return
+ end
+
+ @merge_request.update(merge_error: nil)
+
+ if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active?
+ MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
+ .execute(@merge_request)
+ @status = :merge_when_build_succeeds
else
- @status = false
+ MergeWorker.perform_async(@merge_request.id, current_user.id, params)
+ @status = :success
end
end
@@ -250,8 +272,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_show_vars
- @participants = @merge_request.participants(current_user)
-
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
@@ -264,25 +284,35 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request_diff = @merge_request.merge_request_diff
+ @ci_commit = @merge_request.ci_commit
+ @statuses = @ci_commit.statuses if @ci_commit
+
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
end
end
+ def define_widget_vars
+ @ci_commit = @merge_request.ci_commit
+ closes_issues
+ end
+
def invalid_mr
# Render special view for MR with removed source or target branch
render 'invalid'
end
def merge_request_params
- permitted = params.require(:merge_request).permit(
+ params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id,
:state_event, :description, :task_num, label_ids: []
)
- params[:merge_request][:title].strip! if params[:merge_request][:title]
- permitted
+ end
+
+ def merge_params
+ params.permit(:should_remove_source_branch, :commit_message)
end
# Make sure merge requests created before 8.0
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 263b8b8d94e..ee705f32e81 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -13,7 +13,8 @@ class Projects::NotesController < Projects::ApplicationController
@notes.each do |note|
notes_json[:notes] << {
id: note.id,
- html: note_to_html(note)
+ html: note_to_html(note),
+ valid: note.valid?
}
end
@@ -59,13 +60,16 @@ class Projects::NotesController < Projects::ApplicationController
end
def award_toggle
- noteable = note_params[:noteable_type] == "issue" ? Issue : MergeRequest
- noteable = noteable.find_by!(id: note_params[:noteable_id], project: project)
+ noteable = if note_params[:noteable_type] == "issue"
+ project.issues.find(note_params[:noteable_id])
+ else
+ project.merge_requests.find(note_params[:noteable_id])
+ end
data = {
author: current_user,
is_award: true,
- note: note_params[:note]
+ note: note_params[:note].delete(":")
}
note = noteable.notes.find_by(data)
@@ -128,16 +132,25 @@ class Projects::NotesController < Projects::ApplicationController
end
def render_note_json(note)
- render json: {
- id: note.id,
- discussion_id: note.discussion_id,
- html: note_to_html(note),
- award: note.is_award,
- emoji_path: note.is_award ? ::AwardEmoji.path_to_emoji_image(note.note) : "",
- note: note.note,
- discussion_html: note_to_discussion_html(note),
- discussion_with_diff_html: note_to_discussion_with_diff_html(note)
- }
+ if note.valid?
+ render json: {
+ valid: true,
+ id: note.id,
+ discussion_id: note.discussion_id,
+ html: note_to_html(note),
+ award: note.is_award,
+ emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
+ note: note.note,
+ discussion_html: note_to_discussion_html(note),
+ discussion_with_diff_html: note_to_discussion_with_diff_html(note)
+ }
+ else
+ render json: {
+ valid: false,
+ award: note.is_award,
+ errors: note.errors
+ }
+ end
end
def authorize_admin_note!
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 07eb94e4f48..8364fc293b7 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -23,7 +23,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_members = @group_members.where(user_id: users)
end
- @group_members = @group_members.order('access_level DESC').limit(20)
+ @group_members = @group_members.order('access_level DESC')
end
@project_member = @project.project_members.new
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index 6b52eccebf7..e49259c34b6 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -21,7 +21,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
if protected_branch &&
protected_branch.update_attributes(
- developers_can_push: params[:developers_can_push]
+ developers_can_push: params[:developers_can_push]
)
respond_to do |format|
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index d5ee6ac8663..be7d5c187fe 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -10,15 +10,13 @@ class Projects::RawController < Projects::ApplicationController
@blob = @repository.blob_at(@commit.id, @path)
if @blob
- type = get_blob_type
-
headers['X-Content-Type-Options'] = 'nosniff'
- send_data(
- @blob.data,
- type: type,
- disposition: 'inline'
- )
+ if @blob.lfs_pointer?
+ send_lfs_object
+ else
+ stream_data
+ end
else
render_404
end
@@ -35,4 +33,33 @@ class Projects::RawController < Projects::ApplicationController
'application/octet-stream'
end
end
+
+ def stream_data
+ type = get_blob_type
+
+ send_data(
+ @blob.data,
+ type: type,
+ disposition: 'inline'
+ )
+ end
+
+ def send_lfs_object
+ lfs_object = find_lfs_object
+
+ if lfs_object && lfs_object.project_allowed_access?(@project)
+ send_file lfs_object.file.path, filename: @blob.name, disposition: 'attachment'
+ else
+ render_404
+ end
+ end
+
+ def find_lfs_object
+ lfs_object = LfsObject.find_by_oid(@blob.lfs_oid)
+ if lfs_object && lfs_object.file.exists?
+ lfs_object
+ else
+ nil
+ end
+ end
end
diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb
new file mode 100644
index 00000000000..e2785caa2fb
--- /dev/null
+++ b/app/controllers/projects/runner_projects_controller.rb
@@ -0,0 +1,26 @@
+class Projects::RunnerProjectsController < Projects::ApplicationController
+ before_action :authorize_admin_project!
+
+ layout 'project_settings'
+
+ def create
+ @runner = Ci::Runner.find(params[:runner_project][:runner_id])
+
+ return head(403) unless current_user.ci_authorized_runners.include?(@runner)
+
+ path = runners_path(project)
+
+ if @runner.assign_to(project, current_user)
+ redirect_to path
+ else
+ redirect_to path, alert: 'Failed adding runner to project'
+ end
+ end
+
+ def destroy
+ runner_project = project.runner_projects.find(params[:id])
+ runner_project.destroy
+
+ redirect_to runners_path(project)
+ end
+end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index bfbcf2567f3..4993b2648a5 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -1,14 +1,13 @@
class Projects::RunnersController < Projects::ApplicationController
- before_action :ci_project
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
before_action :authorize_admin_project!
layout 'project_settings'
def index
- @runners = @ci_project.runners.ordered
+ @runners = project.runners.ordered
@specific_runners = current_user.ci_authorized_runners.
- where.not(id: @ci_project.runners).
+ where.not(id: project.runners).
ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
@@ -26,7 +25,7 @@ class Projects::RunnersController < Projects::ApplicationController
end
def destroy
- if @runner.only_for?(@ci_project)
+ if @runner.only_for?(project)
@runner.destroy
end
@@ -52,10 +51,16 @@ class Projects::RunnersController < Projects::ApplicationController
def show
end
+ def toggle_shared_runners
+ project.toggle!(:shared_runners_enabled)
+
+ redirect_to namespace_project_runners_path(project.namespace, project)
+ end
+
protected
def set_runner
- @runner ||= @ci_project.runners.find(params[:id])
+ @runner ||= project.runners.find(params[:id])
end
def runner_params
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 42dbb497e01..6e7590260ff 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -6,7 +6,9 @@ class Projects::ServicesController < Projects::ApplicationController
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
- :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
+ :note_events, :build_events,
+ :notify_only_broken_builds, :add_pusher,
+ :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index cb39c2b8782..280fe12cc7c 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -2,7 +2,7 @@ class Projects::TagsController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
- before_action :authorize_push_code!, only: [:create]
+ before_action :authorize_push_code!, only: [:new, :create]
before_action :authorize_admin_project!, only: [:destroy]
def index
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index 782ebd01b05..30adfad1daa 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -1,22 +1,21 @@
class Projects::TriggersController < Projects::ApplicationController
- before_action :ci_project
before_action :authorize_admin_project!
layout 'project_settings'
def index
- @triggers = @ci_project.triggers
+ @triggers = project.triggers
@trigger = Ci::Trigger.new
end
def create
- @trigger = @ci_project.triggers.new
+ @trigger = project.triggers.new
@trigger.save
if @trigger.valid?
redirect_to namespace_project_triggers_path(@project.namespace, @project)
else
- @triggers = @ci_project.triggers.select(&:persisted?)
+ @triggers = project.triggers.select(&:persisted?)
render :index
end
end
@@ -30,6 +29,6 @@ class Projects::TriggersController < Projects::ApplicationController
private
def trigger
- @trigger ||= @ci_project.triggers.find(params[:id])
+ @trigger ||= project.triggers.find(params[:id])
end
end
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index d6561a45a70..10efafea9db 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -1,5 +1,4 @@
class Projects::VariablesController < Projects::ApplicationController
- before_action :ci_project
before_action :authorize_admin_project!
layout 'project_settings'
@@ -8,9 +7,7 @@ class Projects::VariablesController < Projects::ApplicationController
end
def update
- if ci_project.update_attributes(project_params)
- Ci::EventService.new.change_project_settings(current_user, ci_project)
-
+ if project.update_attributes(project_params)
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
else
render action: 'show'
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 23453195e85..bf5e25ff895 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -123,7 +123,7 @@ class ProjectsController < ApplicationController
::Projects::DestroyService.new(@project, current_user, {}).execute
flash[:alert] = "Project '#{@project.name}' was deleted."
- redirect_back_or_default(default: dashboard_projects_path, options: {})
+ redirect_to dashboard_projects_path
rescue Projects::DestroyService::DestroyError => ex
redirect_to edit_project_path(@project), alert: ex.message
end
@@ -210,10 +210,10 @@ class ProjectsController < ApplicationController
def project_params
params.require(:project).permit(
- :name, :path, :description, :issues_tracker, :tag_list,
+ :name, :path, :description, :issues_tracker, :tag_list, :runners_token,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
- :builds_enabled
+ :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
)
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 08f2483af33..c72df73af46 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -2,7 +2,7 @@ class SnippetsController < ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet
- before_action :authorize_read_snippet!, only: [:show]
+ before_action :authorize_read_snippet!, only: [:show, :raw]
# Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update]
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index b704e878903..630c73c2a94 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -1,7 +1,7 @@
class MilestonesFinder
def execute(projects, params)
milestones = Milestone.of_projects(projects)
- milestones = milestones.order("due_date ASC")
+ milestones = milestones.reorder("due_date ASC")
case params[:state]
when 'closed' then milestones.closed
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index dd35c215c50..3b4e0362e04 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -23,17 +23,17 @@ class ProjectsFinder
group = options[:group]
if group
- base, extra = group_projects(current_user, group)
+ segments = group_projects(current_user, group)
else
- base, extra = all_projects(current_user)
+ segments = all_projects(current_user)
end
- if base and extra
- union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)])
+ if segments.length > 1
+ union = Gitlab::SQL::Union.new(segments.map { |s| s.select(:id) })
Project.where("projects.id IN (#{union.to_sql})")
else
- base
+ segments.first
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8ecdeaf8e76..0b00b9a0702 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -61,14 +61,14 @@ module ApplicationHelper
options[:class] ||= ''
options[:class] << ' identicon'
bg_key = project.id % 7
- style = "background-color: ##{ allowed_colors.values[bg_key] }; color: #555"
+ style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555"
content_tag(:div, class: options[:class], style: style) do
project.name[0, 1].upcase
end
end
- def avatar_icon(user_or_email = nil, size = nil)
+ def avatar_icon(user_or_email = nil, size = nil, scale = 2)
if user_or_email.is_a?(User)
user = user_or_email
else
@@ -78,12 +78,12 @@ module ApplicationHelper
if user
user.avatar_url(size) || default_avatar
else
- gravatar_icon(user_or_email, size)
+ gravatar_icon(user_or_email, size, scale)
end
end
- def gravatar_icon(user_email = '', size = nil)
- GravatarService.new.execute(user_email, size) ||
+ def gravatar_icon(user_email = '', size = nil, scale = 2)
+ GravatarService.new.execute(user_email, size, scale) ||
default_avatar
end
@@ -204,12 +204,16 @@ module ApplicationHelper
# Returns an HTML-safe String
def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
element = content_tag :time, time.to_s,
- class: "#{html_class} js-timeago",
+ class: "#{html_class} js-timeago js-timeago-pending",
datetime: time.getutc.iso8601,
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
- element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
+ unless skip_js
+ element << javascript_tag(
+ "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()"
+ )
+ end
element
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 77d99140c43..68e5d5be600 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -30,26 +30,24 @@ module BlobHelper
nil
end
- if blob && blob.text?
- text = 'Edit'
- after = options[:after] || ''
- from_mr = options[:from_merge_request_id]
- link_opts = {}
- link_opts[:from_merge_request_id] = from_mr if from_mr
- cls = 'btn btn-small'
- if allowed_tree_edit?(project, ref)
- link_to(text,
- namespace_project_edit_blob_path(project.namespace, project,
- tree_join(ref, path),
- link_opts),
- class: cls
- )
- else
- content_tag :span, text, class: cls + ' disabled'
- end + after.html_safe
- else
- ''
- end
+ return unless blob && blob.text? && blob_editable?(blob)
+
+ text = 'Edit'
+ after = options[:after] || ''
+ from_mr = options[:from_merge_request_id]
+ link_opts = {}
+ link_opts[:from_merge_request_id] = from_mr if from_mr
+ cls = 'btn btn-small'
+ link_to(text,
+ namespace_project_edit_blob_path(project.namespace, project,
+ tree_join(ref, path),
+ link_opts),
+ class: cls
+ ) + after.html_safe
+ end
+
+ def blob_editable?(blob, project = @project, ref = @ref)
+ !blob.lfs_pointer? && allowed_tree_edit?(project, ref)
end
def leave_edit_message
@@ -60,7 +58,7 @@ module BlobHelper
if Gitlab::MarkupHelper.previewable?(filename)
'Preview'
else
- 'Preview changes'
+ 'Preview Changes'
end
end
@@ -71,4 +69,16 @@ module BlobHelper
def blob_icon(mode, name)
icon("#{file_type_icon_class('file', mode, name)} fw")
end
+
+ def blob_viewable?(blob)
+ blob && blob.text? && !blob.lfs_pointer?
+ end
+
+ def blob_size(blob)
+ if blob.lfs_pointer?
+ blob.lfs_size
+ else
+ blob.size
+ end
+ end
end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index d6eaa7d57bc..e39548e17e1 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -11,7 +11,7 @@ module BranchesHelper
def can_push_branch?(project, branch_name)
return false unless project.repository.branch_names.include?(branch_name)
-
+
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
new file mode 100644
index 00000000000..ec0e3f409c1
--- /dev/null
+++ b/app/helpers/button_helper.rb
@@ -0,0 +1,58 @@
+module ButtonHelper
+ # Output a "Copy to Clipboard" button
+ #
+ # data - Data attributes passed to `content_tag`
+ #
+ # Examples:
+ #
+ # # Define the clipboard's text
+ # clipboard_button(clipboard_text: "Foo")
+ # # => "<button class='...' data-clipboard-text='Foo'>...</button>"
+ #
+ # # Define the target element
+ # clipboard_button(clipboard_target: "div#foo")
+ # # => "<button class='...' data-clipboard-target='div#foo'>...</button>"
+ #
+ # See http://clipboardjs.com/#usage
+ def clipboard_button(data = {})
+ content_tag :button,
+ icon('clipboard'),
+ class: 'btn btn-xs btn-clipboard',
+ data: data,
+ type: :button
+ end
+
+ def http_clone_button(project)
+ klass = 'btn js-protocol-switch'
+ klass << ' active' if default_clone_protocol == 'http'
+ klass << ' has_tooltip' if current_user.try(:require_password?)
+
+ protocol = gitlab_config.protocol.upcase
+
+ content_tag :button, protocol,
+ class: klass,
+ data: {
+ clone: project.http_url_to_repo,
+ container: 'body',
+ html: 'true',
+ title: "Set a password on your account<br>to pull or push via #{protocol}"
+ },
+ type: :button
+ end
+
+ def ssh_clone_button(project)
+ klass = 'btn js-protocol-switch'
+ klass << ' active' if default_clone_protocol == 'ssh'
+ klass << ' has_tooltip' if current_user.try(:require_ssh_key?)
+
+ content_tag :button, 'SSH',
+ class: klass,
+ data: {
+ clone: project.ssh_url_to_repo,
+ container: 'body',
+ html: 'true',
+ title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
+ },
+ type: :button
+ end
+end
diff --git a/app/helpers/ci/gitlab_helper.rb b/app/helpers/ci/gitlab_helper.rb
deleted file mode 100644
index e34c8be1dfc..00000000000
--- a/app/helpers/ci/gitlab_helper.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module Ci
- module GitlabHelper
- def no_turbolink
- { :"data-no-turbolink" => "data-no-turbolink" }
- end
-
- def yaml_web_editor_link(project)
- commits = project.commits
-
- if commits.any? && commits.last.ci_yaml_file
- "#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
- else
- "#{project.gitlab_url}/new/master"
- end
- end
- end
-end
diff --git a/app/helpers/ci/projects_helper.rb b/app/helpers/ci/projects_helper.rb
deleted file mode 100644
index fd991a4165a..00000000000
--- a/app/helpers/ci/projects_helper.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module Ci
- module ProjectsHelper
- def ref_tab_class ref = nil
- 'active' if ref == @ref
- end
-
- def success_ratio(success_builds, failed_builds)
- failed_builds = failed_builds.count(:all)
- success_builds = success_builds.count(:all)
-
- return 100 if failed_builds.zero?
-
- ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
- ratio.to_i
- end
-
- def markdown_badge_code(project, ref)
- url = status_ci_project_url(project, ref: ref, format: 'png')
- "[![build status](#{url})](#{ci_project_url(project, ref: ref)})"
- end
-
- def html_badge_code(project, ref)
- url = status_ci_project_url(project, ref: ref, format: 'png')
- "<a href='#{ci_project_url(project, ref: ref)}'><img src='#{url}' /></a>"
- end
-
- def project_uses_specific_runner?(project)
- project.runners.any?
- end
-
- def no_runners_for_project?(project)
- project.runners.blank? &&
- Ci::Runner.shared.blank?
- end
- end
-end
diff --git a/app/helpers/ci_badge_helper.rb b/app/helpers/ci_badge_helper.rb
new file mode 100644
index 00000000000..27386133e36
--- /dev/null
+++ b/app/helpers/ci_badge_helper.rb
@@ -0,0 +1,13 @@
+module CiBadgeHelper
+ def markdown_badge_code(project, ref)
+ url = status_ci_project_url(project, ref: ref, format: 'png')
+ link = namespace_project_commits_path(project.namespace, project, ref)
+ "[![build status](#{url})](#{link})"
+ end
+
+ def html_badge_code(project, ref)
+ url = status_ci_project_url(project, ref: ref, format: 'png')
+ link = namespace_project_commits_path(project.namespace, project, ref)
+ "<a href='#{link}'><img src='#{url}' /></a>"
+ end
+end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 0ecf77bb45e..d8bee21c82e 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -1,6 +1,6 @@
module CiStatusHelper
def ci_status_path(ci_commit)
- project = ci_commit.gl_project
+ project = ci_commit.project
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end
@@ -8,22 +8,21 @@ module CiStatusHelper
ci_icon_for_status(ci_commit.status)
end
- def ci_status_color(ci_commit)
- case ci_commit.status
- when 'success'
- 'green'
- when 'failed'
- 'red'
- when 'running', 'pending'
- 'yellow'
- else
- 'gray'
- end
+ def ci_status_label(ci_commit)
+ ci_label_for_status(ci_commit.status)
end
def ci_status_with_icon(status)
content_tag :span, class: "ci-status ci-#{status}" do
- ci_icon_for_status(status) + '&nbsp;'.html_safe + status
+ ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
+ end
+ end
+
+ def ci_label_for_status(status)
+ if status == 'success'
+ 'passed'
+ else
+ status
end
end
@@ -40,15 +39,19 @@ module CiStatusHelper
'circle'
end
- icon(icon_name)
+ icon(icon_name + ' fw')
end
def render_ci_status(ci_commit)
- link_to ci_status_path(ci_commit),
- class: "c#{ci_status_color(ci_commit)}",
- title: "Build status: #{ci_commit.status}",
- data: { toggle: 'tooltip', placement: 'left' } do
- ci_status_icon(ci_commit)
- end
+ link_to ci_status_icon(ci_commit),
+ ci_status_path(ci_commit),
+ class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
+ title: "Build #{ci_status_label(ci_commit)}",
+ data: { toggle: 'tooltip', placement: 'left' }
+ end
+
+ def no_runners_for_project?(project)
+ project.runners.blank? &&
+ Ci::Runner.shared.blank?
end
end
diff --git a/app/helpers/clipboard_helper.rb b/app/helpers/clipboard_helper.rb
deleted file mode 100644
index 3c1d7569fac..00000000000
--- a/app/helpers/clipboard_helper.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module ClipboardHelper
- def clipboard_button
- content_tag :button,
- icon('clipboard'),
- class: 'btn btn-xs btn-clipboard js-clipboard-trigger',
- type: :button
- end
-end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 9df20c9fce5..590d20ac7b3 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -109,7 +109,7 @@ module CommitsHelper
)
elsif @path.present?
return link_to(
- "Browse Dir »",
+ "Browse Directory »",
namespace_project_tree_path(project.namespace, project,
tree_join(commit.id, @path)),
class: "pull-right"
@@ -117,7 +117,7 @@ module CommitsHelper
end
end
link_to(
- "Browse Code »",
+ "Browse Files »",
namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right"
)
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index bfd3622a6a9..24134310fc5 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -146,9 +146,9 @@ module DiffHelper
def submodule_link(blob, ref, repository = @repository)
tree, commit = submodule_links(blob, ref, repository)
commit_id = if commit.nil?
- blob.id[0..10]
+ Commit.truncate_sha(blob.id)
else
- link_to "#{blob.id[0..10]}", commit
+ link_to Commit.truncate_sha(blob.id), commit
end
[
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 45788ba95ac..41b5bd7be90 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -28,6 +28,8 @@ module EmailsHelper
return "View #{action.humanize.singularize}"
end
end
+
+ nil
end
def color_email_diff(diffcontent)
diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb
index 838b85afdfe..1f3401f2906 100644
--- a/app/helpers/external_wiki_helper.rb
+++ b/app/helpers/external_wiki_helper.rb
@@ -1,7 +1,7 @@
module ExternalWikiHelper
def get_project_wiki_path(project)
external_wiki_service = project.services.
- select { |service| service.to_param == 'external_wiki' }.first
+ find { |service| service.to_param == 'external_wiki' }
if external_wiki_service.present? && external_wiki_service.active?
external_wiki_service.properties['external_wiki_url']
else
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 98c6d9d5d2e..ca41657cec1 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -20,7 +20,7 @@ module GitlabMarkdownHelper
end
user = current_user if defined?(current_user)
- gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
+ gfm_body = Banzai.render(escaped_body, project: @project, current_user: user, pipeline: :single_line)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
@@ -46,23 +46,36 @@ module GitlabMarkdownHelper
end
def markdown(text, context = {})
- process_markdown(text, context)
- end
+ return "" unless text.present?
+
+ context[:project] ||= @project
- # 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 = {})
- process_markdown(text, options, :gfm)
+ html = Banzai.render(text, context)
+
+ context.merge!(
+ current_user: (current_user if defined?(current_user)),
+
+ # RelativeLinkFilter
+ requested_path: @path,
+ project_wiki: @project_wiki,
+ ref: @ref
+ )
+
+ Banzai.post_process(html, context)
end
def asciidoc(text)
- Gitlab::Asciidoc.render(text, {
- commit: @commit,
- project: @project,
- project_wiki: @project_wiki,
+ Gitlab::Asciidoc.render(
+ text,
+ project: @project,
+ current_user: (current_user if defined?(current_user)),
+
+ # RelativeLinkFilter
+ project_wiki: @project_wiki,
requested_path: @path,
- ref: @ref
- })
+ ref: @ref,
+ commit: @commit
+ )
end
# Return the first line of +text+, up to +max_chars+, after parsing the line
@@ -178,26 +191,4 @@ module GitlabMarkdownHelper
''
end
end
-
- def process_markdown(text, options, method = :markdown)
- return "" unless text.present?
-
- options.reverse_merge!(
- path: @path,
- pipeline: :default,
- project: @project,
- project_wiki: @project_wiki,
- ref: @ref
- )
-
- user = current_user if defined?(current_user)
-
- html = if method == :gfm
- Gitlab::Markdown.gfm(text, options)
- else
- Gitlab::Markdown.render(text, options)
- end
-
- Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
- end
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index b0b536d4649..f3fddef01cb 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -6,7 +6,7 @@
#
# For example instead of this:
#
-# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request)
+# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
#
# We can simply use shortcut:
#
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index 1e372d5631d..c2ab80f2e0d 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -16,4 +16,14 @@ module GraphHelper
ids = parents.map { |p| p.id }
ids.zip(parent_spaces)
end
+
+ def success_ratio(success_builds, failed_builds)
+ failed_builds = failed_builds.count(:all)
+ success_builds = success_builds.count(:all)
+
+ return 100 if failed_builds.zero?
+
+ ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
+ ratio.to_i
+ end
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 1cf5b96481a..5724d3aabec 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -27,16 +27,20 @@ module IconsHelper
end
end
- def public_icon
- icon('globe fw')
- end
-
- def internal_icon
- icon('shield fw')
- end
+ def visibility_level_icon(level, fw: true)
+ name =
+ case level
+ when Gitlab::VisibilityLevel::PRIVATE
+ 'lock'
+ when Gitlab::VisibilityLevel::INTERNAL
+ 'shield'
+ else # Gitlab::VisibilityLevel::PUBLIC
+ 'globe'
+ end
+
+ name << " fw" if fw
- def private_icon
- icon('lock fw')
+ icon(name)
end
def file_type_icon_class(type, mode, name)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 2c791aa5682..d2186427dba 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -44,25 +44,28 @@ module IssuesHelper
end
def bulk_update_milestone_options
- options_for_select([['None (backlog)', -1]]) +
- options_from_collection_for_select(project_active_milestones, 'id',
- 'title', params[:milestone_id])
+ milestones = project_active_milestones.to_a
+ milestones.unshift(Milestone::None)
+
+ options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
end
def milestone_options(object)
- options_from_collection_for_select(object.project.milestones.active,
- 'id', 'title', object.milestone_id)
+ milestones = object.project.milestones.active.to_a
+ milestones.unshift(Milestone::None)
+
+ options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
end
- def issue_box_class(item)
+ def status_box_class(item)
if item.respond_to?(:expired?) && item.expired?
- 'issue-box-expired'
+ 'status-box-expired'
elsif item.respond_to?(:merged?) && item.merged?
- 'issue-box-merged'
+ 'status-box-merged'
elsif item.closed?
- 'issue-box-closed'
+ 'status-box-closed'
else
- 'issue-box-open'
+ 'status-box-open'
end
end
@@ -84,17 +87,23 @@ module IssuesHelper
end
def merge_requests_sentence(merge_requests)
- merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
+ # Sorting based on the `!123` or `group/project!123` reference will sort
+ # local merge requests first.
+ merge_requests.map do |merge_request|
+ merge_request.to_reference(@project)
+ end.sort.to_sentence(last_word_connector: ', or ')
end
def url_to_emoji(name)
emoji_path = ::AwardEmoji.path_to_emoji_image(name)
url_to_image(emoji_path)
+ rescue StandardError
+ ""
end
def emoji_author_list(notes, current_user)
list = notes.map do |note|
- note.author == current_user ? "me" : note.author.username
+ note.author == current_user ? "me" : note.author.name
end
list.join(", ")
@@ -112,6 +121,6 @@ module IssuesHelper
end
end
- # Required for Gitlab::Markdown::IssueReferenceFilter
+ # Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 795fb439f25..a2c3d4d2f32 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -107,6 +107,6 @@ module LabelsHelper
options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
end
- # Required for Gitlab::Markdown::LabelReferenceFilter
+ # Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :text_color_for_bg, :escape_once
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index b804d4f4e3b..6c32647594d 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -39,7 +39,11 @@ module MergeRequestsHelper
end
def issues_sentence(issues)
- issues.map(&:to_reference).to_sentence
+ # Sorting based on the `#123` or `group/project#123` reference will sort
+ # local issues first.
+ issues.map do |issue|
+ issue.to_reference(@project)
+ end.sort.to_sentence
end
def mr_change_branches_path(merge_request)
@@ -49,18 +53,21 @@ module MergeRequestsHelper
source_project_id: @merge_request.source_project_id,
target_project_id: @merge_request.target_project_id,
source_branch: @merge_request.source_branch,
- target_branch: nil
- }
+ target_branch: @merge_request.target_branch,
+ },
+ change_branches: true
)
end
def source_branch_with_namespace(merge_request)
+ branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
+
if merge_request.for_fork?
namespace = link_to(merge_request.source_project_namespace,
project_path(merge_request.source_project))
- namespace + ":#{merge_request.source_branch}"
+ namespace + ":" + branch
else
- merge_request.source_branch
+ branch
end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index ad43892b639..a42cbcff182 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -28,7 +28,9 @@ module MilestonesHelper
Milestone.where(project_id: @projects)
end.active
+ epoch = DateTime.parse('1970-01-01')
grouped_milestones = GlobalMilestone.build_collection(milestones)
+ grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
grouped_milestones.unshift(Milestone::None)
grouped_milestones.unshift(Milestone::Any)
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index e7f3cb21038..faba418c4db 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,10 +1,10 @@
module NamespacesHelper
- def namespaces_options(selected = :current_user, scope = :default)
+ def namespaces_options(selected = :current_user, display_path: false)
groups = current_user.owned_groups + current_user.masters_groups
users = [current_user.namespace]
- group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
- users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ]
+ group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ]
+ users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ]
options = []
options << group_opts
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 9b1dd8b8e54..e6fb8670e57 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -5,6 +5,14 @@ module NavHelper
def nav_sidebar_class
if nav_menu_collapsed?
+ "sidebar-collapsed"
+ else
+ "sidebar-expanded"
+ end
+ end
+
+ def page_sidebar_class
+ if nav_menu_collapsed?
"page-sidebar-collapsed"
else
"page-sidebar-expanded"
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 775cf5a3dd4..9bf750124b2 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -4,7 +4,8 @@ module PageLayoutHelper
@page_title.push(*titles.compact) if titles.any?
- @page_title.join(" | ")
+ # Segments are seperated by middot
+ @page_title.join(" \u00b7 ")
end
def header_title(title = nil, title_url = nil)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index c9cd4a0d54c..777817e24aa 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -21,7 +21,7 @@ module ProjectsHelper
end
def link_to_member(project, author, opts = {})
- default_opts = { avatar: true, name: true, size: 16, author_class: 'author' }
+ default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
return "(deleted)" unless author
@@ -39,7 +39,8 @@ module ProjectsHelper
if opts[:name]
link_to(author_html, user_path(author), class: "author_link").html_safe
else
- link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => sanitize(author.name) } ).html_safe
+ title = opts[:title].sub(":name", sanitize(author.name))
+ link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => title, container: 'body' } ).html_safe
end
end
@@ -173,13 +174,20 @@ module ProjectsHelper
'unknown'
end
- def default_url_to_repo(project = nil)
- project = project || @project
- current_user ? project.url_to_repo : project.http_url_to_repo
+ def default_url_to_repo(project = @project)
+ if default_clone_protocol == "ssh"
+ project.ssh_url_to_repo
+ else
+ project.http_url_to_repo
+ end
end
def default_clone_protocol
- current_user ? "ssh" : "http"
+ if !current_user || current_user.require_ssh_key?
+ "http"
+ else
+ "ssh"
+ end
end
def project_last_activity(project)
@@ -322,10 +330,9 @@ module ProjectsHelper
def filename_path(project, filename)
if project && blob = project.repository.send(filename)
namespace_project_blob_path(
- project.namespace,
- project,
- tree_join(project.default_branch,
- blob.name)
+ project.namespace,
+ project,
+ tree_join(project.default_branch, blob.name)
)
end
end
diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb
index 46eb82a354f..9fb42487a75 100644
--- a/app/helpers/runners_helper.rb
+++ b/app/helpers/runners_helper.rb
@@ -19,7 +19,7 @@ module RunnersHelper
id = "\##{runner.id}"
if current_user && current_user.admin
- link_to ci_admin_runner_path(runner) do
+ link_to admin_runner_path(runner) do
display_name + id
end
else
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 7e54d4d1b5b..05386d790ca 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -15,12 +15,14 @@ module SelectsHelper
html = {
class: css_class,
- 'data-placeholder' => placeholder,
- 'data-null-user' => null_user,
- 'data-any-user' => any_user,
- 'data-email-user' => email_user,
- 'data-first-user' => first_user,
- 'data-current-user' => current_user
+ data: {
+ placeholder: placeholder,
+ null_user: null_user,
+ any_user: any_user,
+ email_user: email_user,
+ first_user: first_user,
+ current_user: current_user
+ }
}
unless opts[:scope] == :all
@@ -46,6 +48,19 @@ module SelectsHelper
select2_tag(id, opts)
end
+ def project_select_tag(id, opts = {})
+ opts[:class] ||= ''
+ opts[:class] << ' ajax-project-select'
+
+ unless opts.delete(:scope) == :all
+ if @group
+ opts['data-group-id'] = @group.id
+ end
+ end
+
+ hidden_field_tag(id, opts[:selected], opts)
+ end
+
def select2_tag(id, opts = {})
css_class = ''
css_class << 'multiselect ' if opts[:multiple]
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 03a49e119b8..f448dd0ab61 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -46,12 +46,26 @@ module TreeHelper
File.join(*args)
end
+ def on_top_of_branch?(project = @project, ref = @ref)
+ project.repository.branch_names.include?(ref)
+ end
+
def allowed_tree_edit?(project = nil, ref = nil)
project ||= @project
ref ||= @ref
- return false unless project.repository.branch_names.include?(ref)
+ return false unless on_top_of_branch?(project, ref)
+
+ can?(current_user, :push_code, project)
+ end
- ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
+ def tree_edit_branch(project = @project, ref = @ref)
+ if allowed_tree_edit?(project, ref)
+ if can_push_branch?(project, ref)
+ ref
+ else
+ project.repository.next_patch_branch
+ end
+ end
end
def tree_breadcrumbs(tree, max_links = 2)
@@ -65,7 +79,7 @@ module TreeHelper
part_path = File.join(part_path, part) unless part_path.empty?
part_path = part if part_path.empty?
- next unless parts.last(2).include?(part) if parts.count > max_links
+ next if parts.count > max_links && !parts.last(2).include?(part)
yield(part, tree_join(@ref, part_path))
end
end
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb
index 2a3a7e80fca..8cad994d10f 100644
--- a/app/helpers/triggers_helper.rb
+++ b/app/helpers/triggers_helper.rb
@@ -1,5 +1,5 @@
module TriggersHelper
- def ci_build_trigger_url(project_id, ref_name)
- "#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger"
+ def builds_trigger_url(project_id)
+ "#{Settings.gitlab.url}/api/v3/projects/#{project_id}/trigger/builds"
end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index bc594dc53c1..71d33b445c2 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -12,61 +12,41 @@ module VisibilityLevelHelper
# Return the description for the +level+ argument.
#
- # +level+ One of the Gitlab::VisibilityLevel constants
- # +form_model+ Either a model object (Project, Snippet, etc.) or the name of
- # a Project or Snippet class.
+ # +level+ One of the Gitlab::VisibilityLevel constants
+ # +form_model+ Either a model object (Project, Snippet, etc.) or the name of
+ # a Project or Snippet class.
def visibility_level_description(level, form_model)
- case form_model.is_a?(String) ? form_model : form_model.class.name
- when 'PersonalSnippet', 'ProjectSnippet', 'Snippet'
- snippet_visibility_level_description(level)
- when 'Project'
+ case form_model
+ when Project
project_visibility_level_description(level)
+ when Snippet
+ snippet_visibility_level_description(level, form_model)
end
end
def project_visibility_level_description(level)
- capture_haml do
- haml_tag :span do
- case level
- when Gitlab::VisibilityLevel::PRIVATE
- haml_concat "Project access must be granted explicitly for each user."
- when Gitlab::VisibilityLevel::INTERNAL
- haml_concat "The project can be cloned by"
- haml_concat "any logged in user."
- when Gitlab::VisibilityLevel::PUBLIC
- haml_concat "The project can be cloned"
- haml_concat "without any"
- haml_concat "authentication."
- end
- end
- end
- end
-
- def snippet_visibility_level_description(level)
- capture_haml do
- haml_tag :span do
- case level
- when Gitlab::VisibilityLevel::PRIVATE
- haml_concat "The snippet is visible only for me."
- when Gitlab::VisibilityLevel::INTERNAL
- haml_concat "The snippet is visible for any logged in user."
- when Gitlab::VisibilityLevel::PUBLIC
- haml_concat "The snippet can be accessed"
- haml_concat "without any"
- haml_concat "authentication."
- end
- end
+ case level
+ when Gitlab::VisibilityLevel::PRIVATE
+ "Project access must be granted explicitly to each user."
+ when Gitlab::VisibilityLevel::INTERNAL
+ "The project can be cloned by any logged in user."
+ when Gitlab::VisibilityLevel::PUBLIC
+ "The project can be cloned without any authentication."
end
end
- def visibility_level_icon(level)
+ def snippet_visibility_level_description(level, snippet = nil)
case level
when Gitlab::VisibilityLevel::PRIVATE
- private_icon
+ if snippet.is_a? ProjectSnippet
+ "The snippet is visible only to project members."
+ else
+ "The snippet is visible only to me."
+ end
when Gitlab::VisibilityLevel::INTERNAL
- internal_icon
+ "The snippet is visible to any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
- public_icon
+ "The snippet can be accessed without any authentication."
end
end
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index aedb0889185..8b83bbd93b7 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -8,10 +8,6 @@ class BaseMailer < ActionMailer::Base
default from: Proc.new { default_sender_address.format }
default reply_to: Proc.new { default_reply_to_address.format }
- def self.delay
- delay_for(2.seconds)
- end
-
def can?
Ability.abilities.allowed?(current_user, action, subject)
end
diff --git a/app/mailers/ci/emails/builds.rb b/app/mailers/ci/emails/builds.rb
deleted file mode 100644
index 6fb4fba85e5..00000000000
--- a/app/mailers/ci/emails/builds.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module Ci
- module Emails
- module Builds
- def build_fail_email(build_id, to)
- @build = Ci::Build.find(build_id)
- @project = @build.project
- mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
- end
-
- def build_success_email(build_id, to)
- @build = Ci::Build.find(build_id)
- @project = @build.project
- mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
- end
- end
- end
-end
diff --git a/app/mailers/ci/notify.rb b/app/mailers/ci/notify.rb
deleted file mode 100644
index 404842cf213..00000000000
--- a/app/mailers/ci/notify.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-module Ci
- class Notify < ActionMailer::Base
- include Ci::Emails::Builds
-
- add_template_helper Ci::GitlabHelper
-
- default_url_options[:host] = Gitlab.config.gitlab.host
- default_url_options[:protocol] = Gitlab.config.gitlab.protocol
- default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
- default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
-
- default from: Gitlab.config.gitlab.email_from
-
- # Just send email with 3 seconds delay
- def self.delay
- delay_for(2.seconds)
- end
-
- private
-
- # Formats arguments into a String suitable for use as an email subject
- #
- # extra - Extra Strings to be inserted into the subject
- #
- # Examples
- #
- # >> subject('Lorem ipsum')
- # => "GitLab-CI | Lorem ipsum"
- #
- # # Automatically inserts Project name when @project is set
- # >> @project = Project.last
- # => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
- # >> subject('Lorem ipsum')
- # => "GitLab-CI | Ruby on Rails | Lorem ipsum "
- #
- # # Accepts multiple arguments
- # >> subject('Lorem ipsum', 'Dolor sit amet')
- # => "GitLab-CI | Lorem ipsum | Dolor sit amet"
- def subject(*extra)
- subject = "GitLab-CI"
- subject << (@project ? " | #{@project.name}" : "")
- subject << " | " + extra.join(' | ') if extra.present?
- subject
- end
- end
-end
diff --git a/app/mailers/emails/builds.rb b/app/mailers/emails/builds.rb
new file mode 100644
index 00000000000..d58609a2de5
--- /dev/null
+++ b/app/mailers/emails/builds.rb
@@ -0,0 +1,15 @@
+module Emails
+ module Builds
+ def build_fail_email(build_id, to)
+ @build = Ci::Build.find(build_id)
+ @project = @build.project
+ mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
+ end
+
+ def build_success_email(build_id, to)
+ @build = Ci::Build.find(build_id)
+ @project = @build.project
+ mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
+ end
+ end
+end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index caba63006da..b96418679bd 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -59,85 +59,17 @@ module Emails
subject: subject("Project was moved"))
end
- def repository_push_email(project_id, recipient, author_id: nil,
- ref: nil,
- action: nil,
- compare: nil,
- reverse_compare: false,
- send_from_committer_email: false,
- disable_diffs: false)
- unless author_id && ref && action
- raise ArgumentError, "missing keywords: author_id, ref, action"
- end
+ def repository_push_email(project_id, recipient, opts = {})
+ @message =
+ Gitlab::Email::Message::RepositoryPush.new(self, project_id, recipient, opts)
- @project = Project.find(project_id)
- @current_user = @author = User.find(author_id)
- @reverse_compare = reverse_compare
- @compare = compare
- @ref_name = Gitlab::Git.ref_name(ref)
- @ref_type = Gitlab::Git.tag_ref?(ref) ? "tag" : "branch"
- @action = action
- @disable_diffs = disable_diffs
-
- if @compare
- @commits = Commit.decorate(compare.commits, @project)
- @diffs = compare.diffs
- end
-
- @action_name =
- case action
- when :create
- "pushed new"
- when :delete
- "deleted"
- else
- "pushed to"
- end
-
- @subject = "[Git]"
- @subject << "[#{@project.path_with_namespace}]"
- @subject << "[#{@ref_name}]" if action == :push
- @subject << " "
-
- if action == :push
- if @commits.length > 1
- @target_url = namespace_project_compare_url(@project.namespace,
- @project,
- from: Commit.new(@compare.base, @project),
- to: Commit.new(@compare.head, @project))
- @subject << "Deleted " if @reverse_compare
- @subject << "#{@commits.length} commits: #{@commits.first.title}"
- else
- @target_url = namespace_project_commit_url(@project.namespace,
- @project, @commits.first)
-
- @subject << "Deleted 1 commit: " if @reverse_compare
- @subject << @commits.first.title
- end
- else
- unless action == :delete
- @target_url = namespace_project_tree_url(@project.namespace,
- @project, @ref_name)
- end
-
- subject_action = @action_name.dup
- subject_action[0] = subject_action[0].capitalize
- @subject << "#{subject_action} #{@ref_type} #{@ref_name}"
- end
-
- @disable_footer = true
-
- reply_to =
- if send_from_committer_email && can_send_from_user_email?(@author)
- @author.email
- else
- Gitlab.config.gitlab.email_reply_to
- end
-
- mail(from: sender(author_id, send_from_committer_email),
- reply_to: reply_to,
- to: recipient,
- subject: @subject)
+ # used in notify layout
+ @target_url = @message.target_url
+
+ mail(from: sender(@message.author_id, @message.send_from_committer_email?),
+ reply_to: @message.reply_to,
+ to: @message.recipient,
+ subject: @message.subject)
end
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 50a409c3754..3bbdd9cee76 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -7,6 +7,7 @@ class Notify < BaseMailer
include Emails::Projects
include Emails::Profile
include Emails::Groups
+ include Emails::Builds
add_template_helper MergeRequestsHelper
add_template_helper EmailsHelper
@@ -16,7 +17,7 @@ class Notify < BaseMailer
subject: subject,
body: body.html_safe,
content_type: 'text/html'
- )
+ )
end
# Splits "gitlab.corp.company.com" up into "gitlab.corp.company.com",
@@ -33,13 +34,13 @@ class Notify < BaseMailer
allowed_domains
end
- private
-
def can_send_from_user_email?(sender)
sender_domain = sender.email.split("@").last
self.class.allowed_email_domains.include?(sender_domain)
end
+ private
+
# Return an email address that displays the name of the sender.
# Only the displayed name changes; the actual email address is always the same.
def sender(sender_id, send_from_user_email = false)
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 07f3a56ec7a..cd5ae0fb0fd 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -346,12 +346,10 @@ class Ability
unless group.last_owner?(target_user)
can_manage = group_abilities(user, group).include?(:admin_group_member)
- if can_manage && user != target_user
+ if can_manage
rules << :update_group_member
rules << :destroy_group_member
- end
-
- if user == target_user
+ elsif user == target_user
rules << :destroy_group_member
end
end
@@ -367,12 +365,10 @@ class Ability
unless target_user == project.owner
can_manage = project_abilities(user, project).include?(:admin_project_member)
- if can_manage && user != target_user
+ if can_manage
rules << :update_project_member
rules << :destroy_project_member
- end
-
- if user == target_user
+ elsif user == target_user
rules << :destroy_project_member
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 9e70247ef51..1f4e8b3ef24 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -27,9 +27,15 @@
# admin_notification_email :string(255)
# shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null
+# runners_registration_token :string(255)
#
class ApplicationSetting < ActiveRecord::Base
+ include TokenAuthenticatable
+ add_authentication_token_field :runners_registration_token
+
+ CACHE_KEY = 'application_setting.last'
+
serialize :restricted_visibility_levels
serialize :import_sources
serialize :restricted_signup_domains, Array
@@ -41,12 +47,12 @@ class ApplicationSetting < ActiveRecord::Base
validates :home_page_url,
allow_blank: true,
- format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" },
+ url: true,
if: :home_page_url_column_exist
validates :after_sign_out_path,
allow_blank: true,
- format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+ url: true
validates :admin_notification_email,
allow_blank: true,
@@ -72,16 +78,22 @@ class ApplicationSetting < ActiveRecord::Base
end
end
+ before_save :ensure_runners_registration_token
+
after_commit do
- Rails.cache.write('application_setting.last', self)
+ Rails.cache.write(CACHE_KEY, self)
end
def self.current
- Rails.cache.fetch('application_setting.last') do
+ Rails.cache.fetch(CACHE_KEY) do
ApplicationSetting.last
end
end
+ def self.expire
+ Rails.cache.delete(CACHE_KEY)
+ end
+
def self.create_from_defaults
create(
default_projects_limit: Settings.gitlab['default_projects_limit'],
@@ -99,7 +111,7 @@ class ApplicationSetting < ActiveRecord::Base
restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
- max_artifacts_size: Settings.gitlab_ci['max_artifacts_size'],
+ max_artifacts_size: Settings.artifacts['max_size'],
)
end
@@ -114,13 +126,12 @@ class ApplicationSetting < ActiveRecord::Base
def restricted_signup_domains_raw=(values)
self.restricted_signup_domains = []
self.restricted_signup_domains = values.split(
- /\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
- | # or
- \s # any whitespace character
- | # or
- [\r\n] # any number of newline characters
- /x)
+ /\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
+ | # or
+ \s # any whitespace character
+ | # or
+ [\r\n] # any number of newline characters
+ /x)
self.restricted_signup_domains.reject! { |d| d.empty? }
end
-
end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 05f5e979695..ad514706160 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -16,12 +16,12 @@
class BroadcastMessage < ActiveRecord::Base
include Sortable
- validates :message, presence: true
+ validates :message, presence: true
validates :starts_at, presence: true
- validates :ends_at, presence: true
+ validates :ends_at, presence: true
- validates :color, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
- validates :font, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
+ validates :color, allow_blank: true, color: true
+ validates :font, allow_blank: true, color: true
def self.current
where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last
diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb
deleted file mode 100644
index 1307fa0b472..00000000000
--- a/app/models/ci/application_setting.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_application_settings
-#
-# id :integer not null, primary key
-# all_broken_builds :boolean
-# add_pusher :boolean
-# created_at :datetime
-# updated_at :datetime
-#
-
-module Ci
- class ApplicationSetting < ActiveRecord::Base
- extend Ci::Model
-
- after_commit do
- Rails.cache.write('ci_application_setting.last', self)
- end
-
- def self.current
- Rails.cache.fetch('ci_application_setting.last') do
- Ci::ApplicationSetting.last
- end
- end
-
- def self.create_from_defaults
- create(
- all_broken_builds: Settings.gitlab_ci['all_broken_builds'],
- add_pusher: Settings.gitlab_ci['add_pusher'],
- )
- end
- end
-end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e78b154084b..6d9cdb95295 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -84,6 +84,7 @@ module Ci
new_build.options = build.options
new_build.commands = build.commands
new_build.tag_list = build.tag_list
+ new_build.gl_project_id = build.gl_project_id
new_build.commit_id = build.commit_id
new_build.name = build.name
new_build.allow_failure = build.allow_failure
@@ -96,19 +97,16 @@ module Ci
end
state_machine :status, initial: :pending do
- after_transition any => [:success, :failed, :canceled] do |build, transition|
- project = build.project
+ after_transition pending: :running do |build, transition|
+ build.execute_hooks
+ end
- if project.web_hooks?
- Ci::WebHookService.new.build_end(build)
- end
+ after_transition any => [:success, :failed, :canceled] do |build, transition|
+ return unless build.project
+ build.update_coverage
build.commit.create_next_builds(build)
- project.execute_services(build)
-
- if project.coverage_enabled?
- build.update_coverage
- end
+ build.execute_hooks
end
end
@@ -117,7 +115,7 @@ module Ci
end
def retryable?
- commands.present?
+ project.builds_enabled? && commands.present?
end
def retried?
@@ -130,7 +128,7 @@ module Ci
end
def timeout
- project.timeout
+ project.build_timeout
end
def variables
@@ -149,26 +147,21 @@ module Ci
project.name
end
- def project_recipients
- recipients = project.email_recipients.split(' ')
-
- if project.email_add_pusher? && user.present? && user.notification_email.present?
- recipients << user.notification_email
- end
-
- recipients.uniq
- end
-
def repo_url
- project.repo_url_with_auth
+ auth = "gitlab-ci-token:#{token}@"
+ project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
+ prefix + auth
+ end
end
def allow_git_fetch
- project.allow_git_fetch
+ project.build_allow_git_fetch
end
def update_coverage
- coverage = extract_coverage(trace, project.coverage_regex)
+ coverage_regex = project.build_coverage_regex
+ return unless coverage_regex
+ coverage = extract_coverage(trace, coverage_regex)
if coverage.is_a? Numeric
update_attributes(coverage: coverage)
@@ -201,7 +194,7 @@ module Ci
def trace
trace = raw_trace
if project && trace.present?
- trace.gsub(project.token, 'xxxxxx')
+ trace.gsub(project.runners_token, 'xxxxxx')
else
trace
end
@@ -228,29 +221,29 @@ module Ci
end
def token
- project.token
+ project.runners_token
end
def valid_token? token
- project.valid_token? token
+ project.valid_runners_token? token
end
def target_url
Gitlab::Application.routes.url_helpers.
- namespace_project_build_url(gl_project.namespace, gl_project, self)
+ namespace_project_build_url(project.namespace, project, self)
end
def cancel_url
if active?
Gitlab::Application.routes.url_helpers.
- cancel_namespace_project_build_path(gl_project.namespace, gl_project, self)
+ cancel_namespace_project_build_path(project.namespace, project, self)
end
end
def retry_url
if retryable?
Gitlab::Application.routes.url_helpers.
- retry_namespace_project_build_path(gl_project.namespace, gl_project, self)
+ retry_namespace_project_build_path(project.namespace, project, self)
end
end
@@ -269,10 +262,18 @@ module Ci
def download_url
if artifacts_file.exists?
Gitlab::Application.routes.url_helpers.
- download_namespace_project_build_path(gl_project.namespace, gl_project, self)
+ download_namespace_project_build_path(project.namespace, project, self)
end
end
+ def execute_hooks
+ build_data = Gitlab::BuildDataBuilder.build(self)
+ project.execute_hooks(build_data.dup, :build_hooks)
+ project.execute_services(build_data.dup, :build_hooks)
+ end
+
+
+
private
def yaml_variables
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 971e899de84..d2a29236942 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -20,8 +20,8 @@ module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
- belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id
- has_many :statuses, dependent: :destroy, class_name: 'CommitStatus'
+ belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
+ has_many :statuses, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
@@ -38,10 +38,6 @@ module Ci
sha
end
- def project
- @project ||= gl_project.ensure_gitlab_ci_project
- end
-
def project_id
project.id
end
@@ -57,7 +53,7 @@ module Ci
end
def valid_commit_sha
- if self.sha == Ci::Git::BLANK_SHA
+ if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)")
end
end
@@ -79,7 +75,7 @@ module Ci
end
def commit_data
- @commit ||= gl_project.commit(sha)
+ @commit ||= project.commit(sha)
rescue
nil
end
@@ -165,21 +161,31 @@ module Ci
status == 'canceled'
end
+ def active?
+ running? || pending?
+ end
+
+ def complete?
+ canceled? || success? || failed?
+ end
+
def duration
duration_array = latest_statuses.map(&:duration).compact
duration_array.reduce(:+).to_i
end
+ def started_at
+ @started_at ||= statuses.order('started_at ASC').first.try(:started_at)
+ end
+
def finished_at
@finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
end
def coverage
- if project.coverage_enabled?
- coverage_array = latest_builds.map(&:coverage).compact
- if coverage_array.size >= 1
- '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
- end
+ coverage_array = latest_builds.map(&:coverage).compact
+ if coverage_array.size >= 1
+ '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
end
@@ -189,7 +195,7 @@ module Ci
def config_processor
return nil unless ci_yaml_file
- @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace)
+ @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message)
nil
@@ -199,7 +205,7 @@ module Ci
end
def ci_yaml_file
- gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
+ @ci_yaml_file ||= project.repository.blob_at(sha, '.gitlab-ci.yml').data
rescue
nil
end
diff --git a/app/models/ci/event.rb b/app/models/ci/event.rb
deleted file mode 100644
index 8c39be42677..00000000000
--- a/app/models/ci/event.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_events
-#
-# id :integer not null, primary key
-# project_id :integer
-# user_id :integer
-# is_admin :integer
-# description :text
-# created_at :datetime
-# updated_at :datetime
-#
-
-module Ci
- class Event < ActiveRecord::Base
- extend Ci::Model
-
- belongs_to :project, class_name: 'Ci::Project'
-
- validates :description,
- presence: true,
- length: { in: 5..200 }
-
- scope :admin, ->(){ where(is_admin: true) }
- scope :project_wide, ->(){ where(is_admin: false) }
- end
-end
diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb
deleted file mode 100644
index 669ee1cc0d2..00000000000
--- a/app/models/ci/project.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_projects
-#
-# id :integer not null, primary key
-# name :string(255)
-# timeout :integer default(3600), not null
-# created_at :datetime
-# updated_at :datetime
-# token :string(255)
-# default_ref :string(255)
-# path :string(255)
-# always_build :boolean default(FALSE), not null
-# polling_interval :integer
-# public :boolean default(FALSE), not null
-# ssh_url_to_repo :string(255)
-# gitlab_id :integer
-# allow_git_fetch :boolean default(TRUE), not null
-# email_recipients :string(255) default(""), not null
-# email_add_pusher :boolean default(TRUE), not null
-# email_only_broken_builds :boolean default(TRUE), not null
-# skip_refs :string(255)
-# coverage_regex :string(255)
-# shared_runners_enabled :boolean default(FALSE)
-# generated_yaml_config :text
-#
-
-module Ci
- class Project < ActiveRecord::Base
- extend Ci::Model
-
- include Ci::ProjectStatus
-
- belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id
-
- has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
- has_many :runners, through: :runner_projects, class_name: 'Ci::Runner'
- has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook'
- has_many :events, dependent: :destroy, class_name: 'Ci::Event'
- has_many :variables, dependent: :destroy, class_name: 'Ci::Variable'
- has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger'
-
- # Project services
- has_many :services, dependent: :destroy, class_name: 'Ci::Service'
- has_one :hip_chat_service, dependent: :destroy, class_name: 'Ci::HipChatService'
- has_one :slack_service, dependent: :destroy, class_name: 'Ci::SlackService'
- has_one :mail_service, dependent: :destroy, class_name: 'Ci::MailService'
-
- accepts_nested_attributes_for :variables, allow_destroy: true
-
- delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project
-
- #
- # Validations
- #
- validates_presence_of :timeout, :token, :default_ref, :gitlab_id
-
- validates_uniqueness_of :gitlab_id
-
- validates :polling_interval,
- presence: true,
- if: ->(project) { project.always_build.present? }
-
- before_validation :set_default_values
-
- class << self
- include Ci::CurrentSettings
-
- def unassigned(runner)
- joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \
- "AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}").
- where("#{Ci::RunnerProject.table_name}.project_id" => nil)
- end
-
- def ordered_by_last_commit_date
- last_commit_subquery = "(SELECT gl_project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY gl_project_id)"
- joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id").
- joins(:gl_project).
- order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
- end
- end
-
- def name
- name_with_namespace
- end
-
- def path
- path_with_namespace
- end
-
- def gitlab_url
- web_url
- end
-
- def any_runners?(&block)
- if runners.active.any?(&block)
- return true
- end
-
- shared_runners_enabled && Ci::Runner.shared.active.any?(&block)
- end
-
- def set_default_values
- self.token = SecureRandom.hex(15) if self.token.blank?
- self.default_ref ||= 'master'
- end
-
- def tracked_refs
- @tracked_refs ||= default_ref.split(",").map { |ref| ref.strip }
- end
-
- def valid_token? token
- self.token && self.token == token
- end
-
- def no_running_builds?
- # Get running builds not later than 3 days ago to ignore hangs
- builds.running.where("updated_at > ?", 3.days.ago).empty?
- end
-
- def email_notification?
- email_add_pusher || email_recipients.present?
- end
-
- def web_hooks?
- web_hooks.any?
- end
-
- def services?
- services.any?
- end
-
- def timeout_in_minutes
- timeout / 60
- end
-
- def timeout_in_minutes=(value)
- self.timeout = value.to_i * 60
- end
-
- def coverage_enabled?
- coverage_regex.present?
- end
-
- # Build a clone-able repo url
- # using http and basic auth
- def repo_url_with_auth
- auth = "gitlab-ci-token:#{token}@"
- http_url_to_repo.sub(/^https?:\/\//) do |prefix|
- prefix + auth
- end
- end
-
- def available_services_names
- %w(slack mail hip_chat)
- end
-
- def build_missing_services
- available_services_names.each do |service_name|
- service = services.find { |service| service.to_param == service_name }
-
- # If service is available but missing in db
- # we should create an instance. Ex `create_gitlab_ci_service`
- self.send :"create_#{service_name}_service" if service.nil?
- end
- end
-
- def execute_services(data)
- services.each do |service|
-
- # Call service hook only if it is active
- begin
- service.execute(data) if service.active && service.can_execute?(data)
- rescue => e
- logger.error(e)
- end
- end
- end
-
- def setup_finished?
- commits.any?
- end
-
- def commits
- gl_project.ci_commits.ordered
- end
-
- def builds
- gl_project.ci_builds
- end
- end
-end
diff --git a/app/models/ci/project_status.rb b/app/models/ci/project_status.rb
deleted file mode 100644
index 2d35aeac225..00000000000
--- a/app/models/ci/project_status.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module Ci
- module ProjectStatus
- def status
- last_commit.status if last_commit
- end
-
- def broken?
- last_commit.failed? if last_commit
- end
-
- def success?
- last_commit.success? if last_commit
- end
-
- def broken_or_success?
- broken? || success?
- end
-
- def last_commit
- @last_commit ||= commits.last if commits.any?
- end
-
- def last_commit_date
- last_commit.try(:created_at)
- end
-
- def human_status
- status
- end
- end
-end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 89710485811..38b20cd7faa 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -25,7 +25,7 @@ module Ci
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
- has_many :projects, through: :runner_projects, class_name: 'Ci::Project'
+ has_many :projects, through: :runner_projects, class_name: '::Project', foreign_key: :gl_project_id
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
@@ -45,10 +45,6 @@ module Ci
query: "%#{query.try(:downcase)}%")
end
- def gl_projects_ids
- projects.select(:gitlab_id)
- end
-
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index 3f4fc43873e..93d9be144e8 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -14,8 +14,8 @@ module Ci
extend Ci::Model
belongs_to :runner, class_name: 'Ci::Runner'
- belongs_to :project, class_name: 'Ci::Project'
+ belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
- validates_uniqueness_of :runner_id, scope: :project_id
+ validates_uniqueness_of :runner_id, scope: :gl_project_id
end
end
diff --git a/app/models/ci/service.rb b/app/models/ci/service.rb
deleted file mode 100644
index 8063c51e82b..00000000000
--- a/app/models/ci/service.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-# To add new service you should build a class inherited from Service
-# and implement a set of methods
-module Ci
- class Service < ActiveRecord::Base
- extend Ci::Model
-
- serialize :properties, JSON
-
- default_value_for :active, false
-
- after_initialize :initialize_properties
-
- belongs_to :project, class_name: 'Ci::Project'
-
- validates :project_id, presence: true
-
- def activated?
- active
- end
-
- def category
- :common
- end
-
- def initialize_properties
- self.properties = {} if properties.nil?
- end
-
- def title
- # implement inside child
- end
-
- def description
- # implement inside child
- end
-
- def help
- # implement inside child
- end
-
- def to_param
- # implement inside child
- end
-
- def fields
- # implement inside child
- []
- end
-
- def can_test?
- project.builds.any?
- end
-
- def can_execute?(build)
- true
- end
-
- def execute(build)
- # implement inside child
- end
-
- # Provide convenient accessor methods
- # for each serialized property.
- def self.prop_accessor(*args)
- args.each do |arg|
- class_eval %{
- def #{arg}
- (properties || {})['#{arg}']
- end
-
- def #{arg}=(value)
- self.properties ||= {}
- self.properties['#{arg}'] = value
- end
- }
- end
- end
-
- def self.boolean_accessor(*args)
- self.prop_accessor(*args)
-
- args.each do |arg|
- class_eval %{
- def #{arg}?
- ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
- end
- }
- end
- end
- end
-end
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index b73c35d5ae5..23516709a41 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -16,7 +16,7 @@ module Ci
acts_as_paranoid
- belongs_to :project, class_name: 'Ci::Project'
+ belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :token
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index b3d2b809e03..56759d3e50f 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -15,10 +15,10 @@ module Ci
class Variable < ActiveRecord::Base
extend Ci::Model
- belongs_to :project, class_name: 'Ci::Project'
+ belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
validates_presence_of :key
- validates_uniqueness_of :key, scope: :project_id
+ validates_uniqueness_of :key, scope: :gl_project_id
attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
end
diff --git a/app/models/ci/web_hook.rb b/app/models/ci/web_hook.rb
deleted file mode 100644
index 7ca16a1bde8..00000000000
--- a/app/models/ci/web_hook.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_web_hooks
-#
-# id :integer not null, primary key
-# url :string(255) not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
-module Ci
- class WebHook < ActiveRecord::Base
- extend Ci::Model
-
- include HTTParty
-
- belongs_to :project, class_name: 'Ci::Project'
-
- # HTTParty timeout
- default_timeout 10
-
- validates :url, presence: true,
- format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
-
- def execute(data)
- parsed_url = URI.parse(url)
- if parsed_url.userinfo.blank?
- Ci::WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
- else
- post_url = url.gsub("#{parsed_url.userinfo}@", "")
- auth = {
- username: URI.decode(parsed_url.user),
- password: URI.decode(parsed_url.password),
- }
- Ci::WebHook.post(post_url,
- body: data.to_json,
- headers: { "Content-Type" => "application/json" },
- verify: false,
- basic_auth: auth)
- end
- end
- end
-end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 492f6be1ce3..0ba7b584d91 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -7,7 +7,7 @@ class Commit
include Referable
include StaticModel
- attr_mentionable :safe_message
+ attr_mentionable :safe_message, pipeline: :single_line
participant :author, :committer, :notes
attr_accessor :project
@@ -78,11 +78,23 @@ class Commit
}x
end
+ def self.link_reference_pattern
+ super("commit", /(?<commit>\h{6,40})/)
+ end
+
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
- "#{project.to_reference}@#{id}"
+ project.to_reference + self.class.reference_prefix + self.id
else
- id
+ self.id
+ end
+ end
+
+ def reference_link_text(from_project = nil)
+ if cross_project_reference?(from_project)
+ project.to_reference + self.class.reference_prefix + self.short_id
+ else
+ self.short_id
end
end
@@ -135,10 +147,10 @@ class Commit
description.present?
end
- def hook_attrs
+ def hook_attrs(with_changed_files: false)
path_with_namespace = project.path_with_namespace
- {
+ data = {
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
@@ -148,6 +160,12 @@ class Commit
email: author_email
}
}
+
+ if with_changed_files
+ data.merge!(repo_changes)
+ end
+
+ data
end
# Discover issues should be closed when this commit is pushed to a project's
@@ -157,11 +175,11 @@ class Commit
end
def author
- @author ||= User.find_by_any_email(author_email)
+ @author ||= User.find_by_any_email(author_email.downcase)
end
def committer
- @committer ||= User.find_by_any_email(committer_email)
+ @committer ||= User.find_by_any_email(committer_email.downcase)
end
def parents
@@ -196,4 +214,22 @@ class Commit
def status
ci_commit.try(:status) || :not_found
end
+
+ private
+
+ def repo_changes
+ changes = { added: [], modified: [], removed: [] }
+
+ diffs.each do |diff|
+ if diff.deleted_file
+ changes[:removed] << diff.old_path
+ elsif diff.renamed_file || diff.new_file
+ changes[:added] << diff.new_path
+ else
+ changes[:modified] << diff.new_path
+ end
+ end
+
+ changes
+ end
end
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 86fc9eb01a3..14e7971fa06 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -2,36 +2,38 @@
#
# Examples:
#
-# range = CommitRange.new('f3f85602...e86e1013')
+# range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false
# range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013"
#
-# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae')
+# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true
# range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013"
#
-# # Assuming `project` is a Project with a repository containing both commits:
-# range.project = project
+# # Assuming the specified project has a repository containing both commits:
# range.valid_commits? # => true
#
class CommitRange
include ActiveModel::Conversion
include Referable
- attr_reader :sha_from, :notation, :sha_to
+ attr_reader :commit_from, :notation, :commit_to
+ attr_reader :ref_from, :ref_to
# Optional Project model
attr_accessor :project
- # See `exclude_start?`
- attr_reader :exclude_start
-
- # The beginning and ending SHAs can be between 6 and 40 hex characters, and
+ # The beginning and ending refs can be named or SHAs, and
# the range notation can be double- or triple-dot.
- PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
+ REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
+ PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
+
+ # In text references, the beginning and ending refs can only be SHAs
+ # between 6 and 40 hex characters.
+ STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
def self.reference_prefix
'@'
@@ -43,27 +45,40 @@ class CommitRange
def self.reference_pattern
%r{
(?:#{Project.reference_pattern}#{reference_prefix})?
- (?<commit_range>#{PATTERN})
+ (?<commit_range>#{STRICT_PATTERN})
}x
end
+ def self.link_reference_pattern
+ super("compare", /(?<commit_range>#{PATTERN})/)
+ end
+
# Initialize a CommitRange
#
# range_string - The String commit range.
# project - An optional Project model.
#
# Raises ArgumentError if `range_string` does not match `PATTERN`.
- def initialize(range_string, project = nil)
+ def initialize(range_string, project)
+ @project = project
+
range_string.strip!
- unless range_string.match(/\A#{PATTERN}\z/)
+ unless range_string =~ /\A#{PATTERN}\z/
raise ArgumentError, "invalid CommitRange string format: #{range_string}"
end
- @exclude_start = !range_string.include?('...')
- @sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
+ @ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
- @project = project
+ if project.valid_repo?
+ @commit_from = project.commit(@ref_from)
+ @commit_to = project.commit(@ref_to)
+ end
+
+ if valid_commits?
+ @ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from)
+ @ref_to = Commit.truncate_sha(sha_to) if sha_to.start_with?(@ref_to)
+ end
end
def inspect
@@ -71,15 +86,24 @@ class CommitRange
end
def to_s
- "#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
+ sha_from + notation + sha_to
end
+ alias_method :id, :to_s
+
def to_reference(from_project = nil)
- # Not using to_s because we want the full SHAs
- reference = sha_from + notation + sha_to
+ if cross_project_reference?(from_project)
+ project.to_reference + self.class.reference_prefix + self.id
+ else
+ self.id
+ end
+ end
+
+ def reference_link_text(from_project = nil)
+ reference = ref_from + notation + ref_to
if cross_project_reference?(from_project)
- reference = project.to_reference + '@' + reference
+ reference = project.to_reference + self.class.reference_prefix + reference
end
reference
@@ -87,46 +111,58 @@ class CommitRange
# Returns a String for use in a link's title attribute
def reference_title
- "Commits #{suffixed_sha_from} through #{sha_to}"
+ "Commits #{sha_start} through #{sha_to}"
end
# Return a Hash of parameters for passing to a URL helper
#
# See `namespace_project_compare_url`
def to_param
- { from: suffixed_sha_from, to: sha_to }
+ { from: sha_start, to: sha_to }
end
def exclude_start?
- exclude_start
+ @notation == '..'
end
# Check if both the starting and ending commit IDs exist in a project's
# repository
- #
- # project - An optional Project to check (default: `project`)
- def valid_commits?(project = project)
- return nil unless project.present?
- return false unless project.valid_repo?
-
- commit_from.present? && commit_to.present?
+ def valid_commits?
+ commit_start.present? && commit_end.present?
end
def persisted?
true
end
- def commit_from
- @commit_from ||= project.repository.commit(suffixed_sha_from)
+ def sha_from
+ return nil unless @commit_from
+
+ @commit_from.id
+ end
+
+ def sha_to
+ return nil unless @commit_to
+
+ @commit_to.id
end
- def commit_to
- @commit_to ||= project.repository.commit(sha_to)
+ def sha_start
+ return nil unless sha_from
+
+ exclude_start? ? sha_from + '^' : sha_from
end
- private
+ def commit_start
+ return nil unless sha_start
- def suffixed_sha_from
- sha_from + (exclude_start? ? '^' : '')
+ if exclude_start?
+ @commit_start ||= project.commit(sha_start)
+ else
+ commit_from
+ end
end
+
+ alias_method :sha_end, :sha_to
+ alias_method :commit_end, :commit_to
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index e70f4d37184..21c5c87bc3d 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,39 +1,36 @@
# == Schema Information
#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string(255)
-# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
-# artifacts_file :text
+# project_id integer
+# status string
+# finished_at datetime
+# trace text
+# created_at datetime
+# updated_at datetime
+# started_at datetime
+# runner_id integer
+# coverage float
+# commit_id integer
+# commands text
+# job_id integer
+# name string
+# deploy boolean default: false
+# options text
+# allow_failure boolean default: false, null: false
+# stage string
+# trigger_request_id integer
+# stage_idx integer
+# tag boolean
+# ref string
+# user_id integer
+# type string
+# target_url string
+# description string
#
class CommitStatus < ActiveRecord::Base
self.table_name = 'ci_builds'
+ belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :user
@@ -79,6 +76,10 @@ class CommitStatus < ActiveRecord::Base
build.update_attributes finished_at: Time.now
end
+ after_transition [:pending, :running] => :success do |build, transition|
+ MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.project, nil).trigger(build)
+ end
+
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
@@ -86,8 +87,7 @@ class CommitStatus < ActiveRecord::Base
state :canceled, value: 'canceled'
end
- delegate :sha, :short_sha, :gl_project,
- to: :commit, prefix: false
+ delegate :sha, :short_sha, to: :commit, prefix: false
# TODO: this should be removed with all references
def before_sha
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 2dafb5e752f..f56fd3e02d4 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -8,6 +8,7 @@ module Issuable
extend ActiveSupport::Concern
include Participable
include Mentionable
+ include StripAttribute
included do
belongs_to :author, class_name: "User"
@@ -49,8 +50,10 @@ module Issuable
allow_nil: true,
prefix: true
- attr_mentionable :title, :description
+ attr_mentionable :title, pipeline: :single_line
+ attr_mentionable :description, cache: true
participant :author, :assignee, :notes_with_associations
+ strip_attributes :title
end
module ClassMethods
@@ -92,6 +95,16 @@ module Issuable
opened? || reopened?
end
+ # Deprecated. Still exists to preserve API compatibility.
+ def downvotes
+ 0
+ end
+
+ # Deprecated. Still exists to preserve API compatibility.
+ def upvotes
+ 0
+ end
+
def subscribed?(user)
subscription = subscriptions.find_by_user_id(user.id)
@@ -151,4 +164,9 @@ module Issuable
def notes_with_associations
notes.includes(:author, :project)
end
+
+ def updated_tasks
+ Taskable.get_updated_tasks(old_content: previous_changes['description'].first,
+ new_content: description)
+ end
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 193c91f1742..1fdcda97520 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -10,8 +10,9 @@ module Mentionable
module ClassMethods
# Indicate which attributes of the Mentionable to search for GFM references.
- def attr_mentionable(*attrs)
- mentionable_attrs.concat(attrs.map(&:to_s))
+ def attr_mentionable(attr, options = {})
+ attr = attr.to_s
+ mentionable_attrs << [attr, options]
end
# Accessor for attributes marked mentionable.
@@ -22,7 +23,7 @@ module Mentionable
included do
if self < Participable
- participant ->(current_user) { mentioned_users(current_user, load_lazy_references: false) }
+ participant ->(current_user) { mentioned_users(current_user) }
end
end
@@ -37,38 +38,46 @@ module Mentionable
"#{friendly_name} #{to_reference(from_project)}"
end
- # Construct a String that contains possible GFM references.
- def mentionable_text
- self.class.mentionable_attrs.map { |attr| send(attr) }.compact.join("\n\n")
- end
-
# The GFM reference to this Mentionable, which shouldn't be included in its #references.
def local_reference
self
end
- 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)
+ def all_references(current_user = self.author, text = nil)
+ ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
+
+ if text
+ ext.analyze(text)
+ else
+ self.class.mentionable_attrs.each do |attr, options|
+ text = send(attr)
+ options[:cache_key] = [self, attr] if options.delete(:cache) && self.persisted?
+ ext.analyze(text, options)
+ end
+ end
+
ext
end
- def mentioned_users(current_user = nil, load_lazy_references: true)
- all_references(current_user, load_lazy_references: load_lazy_references).users
+ def mentioned_users(current_user = nil)
+ all_references(current_user).users
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
- def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
- return [] if text.blank?
+ def referenced_mentionables(current_user = self.author, text = nil)
+ refs = all_references(current_user, text)
+ refs = (refs.issues + refs.merge_requests + refs.commits)
- refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
- (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
+ # We're using this method instead of Array diffing because that requires
+ # both of the object's `hash` values to be the same, which may not be the
+ # case for otherwise identical Commit objects.
+ refs.reject { |ref| ref == local_reference }
end
- # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
- def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
+ # Create a cross-reference Note for each GFM reference to another Mentionable found in the +mentionable_attrs+.
+ def create_cross_references!(author = self.author, without = [], text = nil)
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.
@@ -106,12 +115,12 @@ module Mentionable
def detect_mentionable_changes
source = (changes.present? ? changes : previous_changes).dup
- mentionable = self.class.mentionable_attrs
+ mentionable = self.class.mentionable_attrs.map { |attr, options| attr }
# Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) }
end
-
+
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
def cross_reference_exists?(target)
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 85367f89f4f..808d80b0530 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -38,20 +38,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, load_lazy_references: true)
- participants = self.class.participant_attrs.flat_map do |attr|
- value =
- if attr.respond_to?(:call)
- instance_exec(current_user, &attr)
- else
- send(attr)
- end
+ participants =
+ Gitlab::ReferenceExtractor.lazily do
+ self.class.participant_attrs.flat_map do |attr|
+ value =
+ if attr.respond_to?(:call)
+ instance_exec(current_user, &attr)
+ else
+ send(attr)
+ end
- participants_for(value, current_user)
- end.compact.uniq
-
- if load_lazy_references
- participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
+ participants_for(value, current_user)
+ end.compact.uniq
+ end
+ unless Gitlab::ReferenceExtractor.lazy?
participants.select! do |user|
user.can?(:read_project, project)
end
@@ -64,12 +65,12 @@ module Participable
def participants_for(value, current_user = nil)
case value
- when User, Gitlab::Markdown::ReferenceFilter::LazyReference
+ when User, Banzai::LazyReference
[value]
when Enumerable, ActiveRecord::Relation
value.flat_map { |v| participants_for(v, current_user) }
when Participable
- value.participants(current_user, load_lazy_references: false)
+ value.participants(current_user)
end
end
end
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index cced66cc1e4..ce064f675ae 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -21,6 +21,10 @@ module Referable
''
end
+ def reference_link_text(from_project = nil)
+ to_reference(from_project)
+ end
+
module ClassMethods
# The character that prefixes the actual reference identifier
#
@@ -44,6 +48,25 @@ module Referable
def reference_pattern
raise NotImplementedError, "#{self} does not implement #{__method__}"
end
+
+ def link_reference_pattern(route, pattern)
+ %r{
+ (?<url>
+ #{Regexp.escape(Gitlab.config.gitlab.url)}
+ \/#{Project.reference_pattern}
+ \/#{Regexp.escape(route)}
+ \/#{pattern}
+ (?<path>
+ (\/[a-z0-9_=-]+)*
+ )?
+ (?<query>
+ \?[a-z0-9_=-]+
+ (&[a-z0-9_=-]+)*
+ )?
+ (?<anchor>\#[a-z0-9_-]+)?
+ )
+ }x
+ end
end
private
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
new file mode 100644
index 00000000000..8806ebe897a
--- /dev/null
+++ b/app/models/concerns/strip_attribute.rb
@@ -0,0 +1,34 @@
+# == Strip Attribute module
+#
+# Contains functionality to clean attributes before validation
+#
+# Usage:
+#
+# class Milestone < ActiveRecord::Base
+# strip_attributes :title
+# end
+#
+#
+module StripAttribute
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def strip_attributes(*attrs)
+ strip_attrs.concat(attrs)
+ end
+
+ def strip_attrs
+ @strip_attrs ||= []
+ end
+ end
+
+ included do
+ before_validation :strip_attributes
+ end
+
+ def strip_attributes
+ self.class.strip_attrs.each do |attr|
+ self[attr].strip! if self[attr] && self[attr].respond_to?(:strip!)
+ end
+ end
+end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 660e58b876d..df2a9e3e84b 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -7,14 +7,39 @@ require 'task_list/filter'
#
# Used by MergeRequest and Issue
module Taskable
+ COMPLETED = 'completed'.freeze
+ INCOMPLETE = 'incomplete'.freeze
+ ITEM_PATTERN = /
+ ^
+ (?:\s*[-+*]|(?:\d+\.))? # optional list prefix
+ \s* # optional whitespace prefix
+ (\[\s\]|\[[xX]\]) # checkbox
+ (\s.+) # followed by whitespace and some text.
+ /x
+
+ def self.get_tasks(content)
+ content.to_s.scan(ITEM_PATTERN).map do |checkbox, label|
+ # ITEM_PATTERN strips out the hyphen, but Item requires it. Rabble rabble.
+ TaskList::Item.new("- #{checkbox}", label.strip)
+ end
+ end
+
+ def self.get_updated_tasks(old_content:, new_content:)
+ old_tasks, new_tasks = get_tasks(old_content), get_tasks(new_content)
+
+ new_tasks.select.with_index do |new_task, i|
+ old_task = old_tasks[i]
+ next unless old_task
+
+ new_task.source == old_task.source && new_task.complete? != old_task.complete?
+ end
+ end
+
# Called by `TaskList::Summary`
def task_list_items
return [] if description.blank?
- @task_list_items ||= description.scan(TaskList::Filter::ItemPattern).collect do |item|
- # ItemPattern strips out the hyphen, but Item requires it. Rabble rabble.
- TaskList::Item.new("- #{item}")
- end
+ @task_list_items ||= Taskable.get_tasks(description)
end
def tasks
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 9b88ec1cc38..488ff8c31b7 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -1,31 +1,43 @@
module TokenAuthenticatable
extend ActiveSupport::Concern
- module ClassMethods
- def find_by_authentication_token(authentication_token = nil)
- if authentication_token
- where(authentication_token: authentication_token).first
- end
+ class_methods do
+ def authentication_token_fields
+ @token_fields || []
end
- end
- def ensure_authentication_token
- if authentication_token.blank?
- self.authentication_token = generate_authentication_token
- end
- end
+ private
+
+ def add_authentication_token_field(token_field)
+ @token_fields = [] unless @token_fields
+ @token_fields << token_field
- def reset_authentication_token!
- self.authentication_token = generate_authentication_token
- save
+ define_singleton_method("find_by_#{token_field}") do |token|
+ find_by(token_field => token) if token
+ end
+
+ define_method("ensure_#{token_field}") do
+ current_token = read_attribute(token_field)
+ if current_token.blank?
+ write_attribute(token_field, generate_token_for(token_field))
+ else
+ current_token
+ end
+ end
+
+ define_method("reset_#{token_field}!") do
+ write_attribute(token_field, generate_token_for(token_field))
+ save!
+ end
+ end
end
private
- def generate_authentication_token
+ def generate_token_for(token_field)
loop do
token = Devise.friendly_token
- break token unless self.class.unscoped.where(authentication_token: token).first
+ break token unless self.class.unscoped.find_by(token_field => token)
end
end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 9afd223bce5..01d008035a5 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -201,7 +201,7 @@ class Event < ActiveRecord::Base
elsif commented?
"commented on"
elsif created_project?
- if project.import?
+ if project.external_import?
"imported"
else
"created"
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 1321ccd963f..8bfc79d88f8 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -16,7 +16,15 @@ class GlobalMilestone
end
def safe_title
- @title.parameterize
+ @title.to_slug.to_s
+ end
+
+ def expired?
+ if due_date
+ due_date.past?
+ else
+ false
+ end
end
def projects
@@ -98,4 +106,25 @@ class GlobalMilestone
def complete?
total_items_count == closed_items_count
end
+
+ def due_date
+ return @due_date if defined?(@due_date)
+
+ @due_date =
+ if @milestones.all? { |x| x.due_date == @milestones.first.due_date }
+ @milestones.first.due_date
+ else
+ nil
+ end
+ end
+
+ def expires_at
+ if due_date
+ if due_date.past?
+ "expired at #{due_date.stamp("Aug 21, 2011")}"
+ else
+ "expires at #{due_date.stamp("Aug 21, 2011")}"
+ end
+ end
+ end
end
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 337b3097126..22638057773 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -25,4 +25,5 @@ class ProjectHook < WebHook
scope :issue_hooks, -> { where(issues_events: true) }
scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
+ scope :build_hooks, -> { where(build_events: true) }
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index d6c6f415c4a..40eb0e20b4b 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -26,42 +26,44 @@ class WebHook < ActiveRecord::Base
default_value_for :note_events, false
default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false
+ default_value_for :build_events, false
default_value_for :enable_ssl_verification, true
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
- validates :url, presence: true,
- format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+ validates :url, presence: true, url: true
def execute(data, hook_name)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
- WebHook.post(url,
- body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
- verify: enable_ssl_verification)
+ response = WebHook.post(url,
+ body: data.to_json,
+ headers: {
+ "Content-Type" => "application/json",
+ "X-Gitlab-Event" => hook_name.singularize.titleize
+ },
+ verify: enable_ssl_verification)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
username: URI.decode(parsed_url.user),
password: URI.decode(parsed_url.password),
}
- WebHook.post(post_url,
- body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
- verify: enable_ssl_verification,
- basic_auth: auth)
+ response = WebHook.post(post_url,
+ body: data.to_json,
+ headers: {
+ "Content-Type" => "application/json",
+ "X-Gitlab-Event" => hook_name.singularize.titleize
+ },
+ verify: enable_ssl_verification,
+ basic_auth: auth)
end
- rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
+
+ [response.code == 200, ActionView::Base.full_sanitizer.sanitize(response.to_s)]
+ rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}")
- false
+ [false, e.to_s]
end
def async_execute(data, hook_name)
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 72183108033..4571d7f0ee1 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
}x
end
+ def self.link_reference_pattern
+ super("issues", /(?<issue>\d+)/)
+ end
+
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -79,6 +83,14 @@ class Issue < ActiveRecord::Base
reference
end
+ def referenced_merge_requests
+ Gitlab::ReferenceExtractor.lazily do
+ [self, *notes].flat_map do |note|
+ note.all_references(load_lazy_references: false).merge_requests
+ end
+ end.sort_by(&:iid)
+ end
+
# Reset issue events cache
#
# Since we do cache @event we need to reset cache in special cases:
diff --git a/app/models/label.rb b/app/models/label.rb
index b306aecbac1..220da10a6ab 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -17,7 +17,7 @@ class Label < ActiveRecord::Base
# Requests that have no label assigned.
LabelStruct = Struct.new(:title, :name)
None = LabelStruct.new('No Label', 'No Label')
- Any = LabelStruct.new('Any', '')
+ Any = LabelStruct.new('Any Label', '')
DEFAULT_COLOR = '#428BCA'
@@ -27,9 +27,7 @@ class Label < ActiveRecord::Base
has_many :label_links, dependent: :destroy
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
- validates :color,
- format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
- allow_blank: false
+ validates :color, color: true, allow_blank: false
validates :project, presence: true, unless: Proc.new { |service| service.template? }
# Don't allow '?', '&', and ',' for label titles
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 3c1426f59d0..86b1b7e2f99 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,3 +1,15 @@
+# == Schema Information
+#
+# Table name: lfs_objects
+#
+# id :integer not null, primary key
+# oid :string(255) not null
+# size :integer not null
+# created_at :datetime
+# updated_at :datetime
+# file :string(255)
+#
+
class LfsObject < ActiveRecord::Base
has_many :lfs_objects_projects, dependent: :destroy
has_many :projects, through: :lfs_objects_projects
@@ -5,4 +17,16 @@ class LfsObject < ActiveRecord::Base
validates :oid, presence: true, uniqueness: true
mount_uploader :file, LfsObjectUploader
+
+ def storage_project(project)
+ if project && project.forked?
+ storage_project(project.forked_from_project)
+ else
+ project
+ end
+ end
+
+ def project_allowed_access?(project)
+ projects.exists?(storage_project(project).id)
+ end
end
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 0fd5f089db9..890736bfc80 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -1,3 +1,14 @@
+# == Schema Information
+#
+# Table name: lfs_objects_projects
+#
+# id :integer not null, primary key
+# lfs_object_id :integer not null
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+#
+
class LfsObjectsProject < ActiveRecord::Base
belongs_to :project
belongs_to :lfs_object
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1e8d9908f0a..d7430d36c41 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -2,25 +2,28 @@
#
# Table name: merge_requests
#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
+# id :integer not null, primary key
+# target_branch :string(255) not null
+# source_branch :string(255) not null
+# source_project_id :integer not null
+# author_id :integer
+# assignee_id :integer
+# title :string(255)
+# created_at :datetime
+# updated_at :datetime
+# milestone_id :integer
+# state :string(255)
+# merge_status :string(255)
+# target_project_id :integer not null
+# iid :integer
+# description :text
+# position :integer default(0)
+# locked_at :datetime
+# updated_by_id :integer
+# merge_error :string(255)
+# merge_params :text (serialized to hash)
+# merge_when_build_succeeds :boolean default(false), not null
+# merge_user_id :integer
#
require Rails.root.join("app/models/commit")
@@ -35,9 +38,12 @@ class MergeRequest < ActiveRecord::Base
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
+ belongs_to :merge_user, class_name: "User"
has_one :merge_request_diff, dependent: :destroy
+ serialize :merge_params, Hash
+
after_create :create_merge_request_diff
after_update :update_merge_request_diff
@@ -121,6 +127,7 @@ class MergeRequest < ActiveRecord::Base
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
+ validates :merge_user, presence: true, if: :merge_when_build_succeeds?
validate :validate_branches
validate :validate_fork
@@ -151,6 +158,10 @@ class MergeRequest < ActiveRecord::Base
}x
end
+ def self.link_reference_pattern
+ super("merge_requests", /(?<merge_request>\d+)/)
+ end
+
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -183,9 +194,7 @@ class MergeRequest < ActiveRecord::Base
similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
if similar_mrs.any?
errors.add :validate_branches,
- "Cannot Create: This merge request already exists: #{
- similar_mrs.pluck(:title)
- }"
+ "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
end
end
end
@@ -254,6 +263,16 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def can_cancel_merge_when_build_succeeds?(current_user)
+ can_be_merged_by?(current_user) || self.author == current_user
+ end
+
+ def can_remove_source_branch?(current_user)
+ !source_project.protected_branch?(source_branch) &&
+ !source_project.root_ref?(source_branch) &&
+ Ability.abilities.allowed?(current_user, :push_code, source_project)
+ end
+
def mr_and_commit_notes
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
@@ -291,7 +310,7 @@ class MergeRequest < ActiveRecord::Base
work_in_progress: work_in_progress?
}
- unless last_commit.nil?
+ if last_commit
attrs.merge!(last_commit: last_commit.hook_attrs)
end
@@ -316,7 +335,7 @@ class MergeRequest < ActiveRecord::Base
issues = commits.flat_map { |c| c.closes_issues(current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description))
- issues.uniq.sort_by(&:id)
+ issues.uniq
else
[]
end
@@ -389,6 +408,16 @@ class MergeRequest < ActiveRecord::Base
message
end
+ def reset_merge_when_build_succeeds
+ return unless merge_when_build_succeeds?
+
+ self.merge_when_build_succeeds = false
+ self.merge_user = nil
+ self.merge_params = nil
+
+ self.save
+ end
+
# Return array of possible target branches
# depends on target project of MR
def target_branches
@@ -476,8 +505,10 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
- if last_commit
- source_project.ci_commit(last_commit.id)
- end
+ @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
+ end
+
+ def broken?
+ self.commits.blank? || branch_missing? || cannot_be_merged?
end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 2ff16e2825c..d8c7536cd31 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -16,12 +16,13 @@
class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
- MilestoneStruct = Struct.new(:title, :name)
- None = MilestoneStruct.new('No Milestone', 'No Milestone')
- Any = MilestoneStruct.new('Any', '')
+ MilestoneStruct = Struct.new(:title, :name, :id)
+ None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
+ Any = MilestoneStruct.new('Any Milestone', '', -1)
include InternalId
include Sortable
+ include StripAttribute
belongs_to :project
has_many :issues
@@ -35,6 +36,8 @@ class Milestone < ActiveRecord::Base
validates :title, presence: true
validates :project, presence: true
+ strip_attributes :title
+
state_machine :state, initial: :active do
event :close do
transition active: :closed
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 20b92e68d61..adafabbec07 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -23,19 +23,17 @@ class Namespace < ActiveRecord::Base
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
- presence: true, uniqueness: true,
length: { within: 0..255 },
- format: { with: Gitlab::Regex.namespace_name_regex,
- message: Gitlab::Regex.namespace_name_regex_message }
+ namespace_name: true,
+ presence: true,
+ uniqueness: true
validates :description, length: { within: 0..255 }
validates :path,
- uniqueness: { case_sensitive: false },
- presence: true,
length: { within: 1..255 },
- exclusion: { in: Gitlab::Blacklist.path },
- format: { with: Gitlab::Regex.namespace_regex,
- message: Gitlab::Regex.namespace_regex_message }
+ namespace: true,
+ presence: true,
+ uniqueness: { case_sensitive: false }
delegate :name, to: :owner, allow_nil: true, prefix: true
@@ -47,7 +45,7 @@ class Namespace < ActiveRecord::Base
class << self
def by_path(path)
- where('lower(path) = :value', value: path.downcase).first
+ find_by('lower(path) = :value', value: path.downcase)
end
# Case insensetive search for namespace by path or name
@@ -150,6 +148,6 @@ class Namespace < ActiveRecord::Base
end
def find_fork_of(project)
- projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first
+ projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id)
end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index e30be444eb5..8c5b5836f9a 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -16,6 +16,7 @@
# system :boolean default(FALSE), not null
# st_diff :text
# updated_by_id :integer
+# is_award :boolean default(FALSE), not null
#
require 'carrierwave/orm/activerecord'
@@ -28,7 +29,7 @@ class Note < ActiveRecord::Base
default_value_for :system, false
- attr_mentionable :note
+ attr_mentionable :note, cache: true, pipeline: :note
participant :author
belongs_to :project
@@ -39,9 +40,12 @@ class Note < ActiveRecord::Base
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
+ before_validation :set_award!
+
validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
- validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
+ validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
+ validates :line_code, line_code: true, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
@@ -335,7 +339,45 @@ class Note < ActiveRecord::Base
read_attribute(:system)
end
+ # Deprecated. Still exists to preserve API compatibility.
+ def downvote?
+ false
+ end
+
+ # Deprecated. Still exists to preserve API compatibility.
+ def upvote?
+ false
+ end
+
def editable?
- !system?
+ !system? && !is_award
+ end
+
+ # Checks if note is an award added as a comment
+ #
+ # If note is an award, this method sets is_award to true
+ # and changes content of the note to award name.
+ #
+ # Method is executed as a before_validation callback.
+ #
+ def set_award!
+ return unless awards_supported? && contains_emoji_only?
+ self.is_award = true
+ self.note = award_emoji_name
+ end
+
+ private
+
+ def awards_supported?
+ noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
+ end
+
+ def contains_emoji_only?
+ note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/
+ end
+
+ def award_emoji_name
+ original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
+ AwardEmoji.normilize_emoji_name(original_name)
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 921f24389ff..f981f12be0b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -28,6 +28,7 @@
# import_type :string(255)
# import_source :string(255)
# commit_count :integer default(0)
+# import_error :text
#
require 'carrierwave/orm/activerecord'
@@ -42,9 +43,8 @@ class Project < ActiveRecord::Base
include Sortable
include AfterCommitQueue
include CaseSensitivity
-
+
extend Gitlab::ConfigHelper
- extend Enumerize
UNKNOWN_IMPORT_URL = 'http://unknown.git'
@@ -56,6 +56,7 @@ class Project < ActiveRecord::Base
default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :wall_enabled, false
default_value_for :snippets_enabled, gitlab_config_features.snippets
+ default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
# set last_activity_at to the same as created_at
after_create :set_last_activity_at
@@ -90,10 +91,10 @@ class Project < ActiveRecord::Base
# Project services
has_many :services
- has_one :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy
has_one :drone_ci_service, dependent: :destroy
has_one :emails_on_push_service, dependent: :destroy
+ has_one :builds_email_service, dependent: :destroy
has_one :irker_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy
has_one :hipchat_service, dependent: :destroy
@@ -137,14 +138,21 @@ 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, 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_many :releases, dependent: :destroy
has_many :lfs_objects_projects, dependent: :destroy
has_many :lfs_objects, through: :lfs_objects_projects
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
- has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id
+
+ has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id
+ has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
+ has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses
+ has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id
+ has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
+ has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id
+ has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id
+
+ accepts_nested_attributes_for :variables, allow_destroy: true
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
@@ -169,7 +177,7 @@ class Project < ActiveRecord::Base
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
validates :import_url,
- format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' },
+ url: { protocols: %w(ssh git http https) },
if: :external_import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
@@ -177,6 +185,11 @@ class Project < ActiveRecord::Base
if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
+ before_validation :set_runners_token_token
+ def set_runners_token_token
+ self.runners_token = SecureRandom.hex(15) if self.runners_token.blank?
+ end
+
mount_uploader :avatar, AvatarUploader
# Scopes
@@ -268,10 +281,14 @@ class Project < ActiveRecord::Base
joins(:namespace).
iwhere('namespaces.path' => namespace_path)
- projects.where('projects.path' => project_path).take ||
+ projects.find_by('projects.path' => project_path) ||
projects.iwhere('projects.path' => project_path).take
end
+ def find_by_ci_id(id)
+ find_by(ci_id: id.to_i)
+ end
+
def visibility_levels
Gitlab::VisibilityLevel.options
end
@@ -449,7 +466,7 @@ class Project < ActiveRecord::Base
end
def external_issue_tracker
- @external_issues_tracker ||= external_issues_trackers.select(&:activated?).first
+ @external_issues_tracker ||= external_issues_trackers.find(&:activated?)
end
def can_have_issues_tracker_id?
@@ -495,7 +512,7 @@ class Project < ActiveRecord::Base
end
def ci_service
- @ci_service ||= ci_services.select(&:activated?).first
+ @ci_service ||= ci_services.find(&:activated?)
end
def avatar_type
@@ -546,7 +563,7 @@ class Project < ActiveRecord::Base
end
def project_member_by_name_or_email(name = nil, email = nil)
- user = users.where('name like ? or email like ?', name, email).first
+ user = users.find_by('name like ? or email like ?', name, email)
project_members.where(user: user) if user
end
@@ -678,6 +695,7 @@ class Project < ActiveRecord::Base
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace)
reset_events_cache
+ @repository = nil
rescue
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
@@ -720,7 +738,7 @@ class Project < ActiveRecord::Base
end
def project_member(user)
- project_members.where(user_id: user).first
+ project_members.find_by(user_id: user)
end
def default_branch
@@ -805,44 +823,56 @@ class Project < ActiveRecord::Base
ci_commit(sha) || ci_commits.create(sha: sha)
end
- def ensure_gitlab_ci_project
- gitlab_ci_project || create_gitlab_ci_project(
- shared_runners_enabled: current_application_settings.shared_runners_enabled
- )
+ def enable_ci
+ self.builds_enabled = true
end
- # TODO: this should be migrated to Project table,
- # the same as issues_enabled
- def builds_enabled
- gitlab_ci_service && gitlab_ci_service.active
+ def unlink_fork
+ if forked?
+ forked_from_project.lfs_objects.find_each do |lfs_object|
+ lfs_object.projects << self
+ end
+
+ forked_project_link.destroy
+ end
end
- def builds_enabled?
- builds_enabled
+ def any_runners?(&block)
+ if runners.active.any?(&block)
+ return true
+ end
+
+ shared_runners_enabled? && Ci::Runner.shared.active.any?(&block)
end
- def builds_enabled=(value)
- service = gitlab_ci_service || create_gitlab_ci_service
- service.active = value
- service.save
+ def valid_runners_token? token
+ self.runners_token && self.runners_token == token
end
- def visibility_level_allowed?(level)
- return true unless forked?
- Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i)
+ # TODO (ayufan): For now we use runners_token (backward compatibility)
+ # In 8.4 every build will have its own individual token valid for time of build
+ def valid_build_token? token
+ self.builds_enabled? && self.runners_token && self.runners_token == token
end
- def enable_ci
- self.builds_enabled = true
+ def build_coverage_enabled?
+ build_coverage_regex.present?
end
- def unlink_fork
- if forked?
- forked_from_project.lfs_objects.find_each do |lfs_object|
- lfs_object.projects << self
- end
+ def build_timeout_in_minutes
+ build_timeout / 60
+ end
- forked_project_link.destroy
- end
+ def build_timeout_in_minutes=(value)
+ self.build_timeout = value.to_i * 60
+ end
+
+ def open_issues_count
+ issues.opened.count
+ end
+
+ def visibility_level_allowed?(level)
+ return true unless forked?
+ Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i)
end
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index d31b12f539e..aa8746beb80 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -23,19 +23,14 @@ class BambooService < CiService
prop_accessor :bamboo_url, :build_key, :username, :password
- validates :bamboo_url,
- presence: true,
- format: { with: /\A#{URI.regexp}\z/ },
- if: :activated?
+ validates :bamboo_url, presence: true, url: true, if: :activated?
validates :build_key, presence: true, if: :activated?
validates :username,
presence: true,
- if: ->(service) { service.password? },
- if: :activated?
+ if: ->(service) { service.activated? && service.password }
validates :password,
presence: true,
- if: ->(service) { service.username? },
- if: :activated?
+ if: ->(service) { service.activated? && service.username }
attr_accessor :response
@@ -84,7 +79,7 @@ class BambooService < CiService
def supported_events
%w(push)
end
-
+
def build_info(sha)
url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 40058b53df5..199ee3a9d0d 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -37,7 +37,7 @@ class BuildkiteService < CiService
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = webhook_url
- hook.enable_ssl_verification = enable_ssl_verification
+ hook.enable_ssl_verification = !!enable_ssl_verification
hook.save
end
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
new file mode 100644
index 00000000000..8247c79fc33
--- /dev/null
+++ b/app/models/project_services/builds_email_service.rb
@@ -0,0 +1,90 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+class BuildsEmailService < Service
+ prop_accessor :recipients
+ boolean_accessor :add_pusher
+ boolean_accessor :notify_only_broken_builds
+ validates :recipients, presence: true, if: :activated?
+
+ def initialize_properties
+ if properties.nil?
+ self.properties = {}
+ self.notify_only_broken_builds = true
+ end
+ end
+
+ def title
+ 'Builds emails'
+ end
+
+ def description
+ 'Email the builds status to a list of recipients.'
+ end
+
+ def to_param
+ 'builds_email'
+ end
+
+ def supported_events
+ %w(build)
+ end
+
+ def execute(push_data)
+ return unless supported_events.include?(push_data[:object_kind])
+
+ if should_build_be_notified?(push_data)
+ BuildEmailWorker.perform_async(
+ push_data[:build_id],
+ all_recipients(push_data),
+ push_data,
+ )
+ end
+ end
+
+ def fields
+ [
+ { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by comma' },
+ { type: 'checkbox', name: 'add_pusher', label: 'Add pusher to recipients list' },
+ { type: 'checkbox', name: 'notify_only_broken_builds' },
+ ]
+ end
+
+ def should_build_be_notified?(data)
+ case data[:build_status]
+ when 'success'
+ !notify_only_broken_builds?
+ when 'failed'
+ true
+ else
+ false
+ end
+ end
+
+ def all_recipients(data)
+ all_recipients = recipients.split(',')
+
+ if add_pusher? && data[:user][:email]
+ all_recipients << "#{data[:user][:email]}"
+ end
+
+ all_recipients
+ end
+end
diff --git a/app/models/project_services/ci/hip_chat_message.rb b/app/models/project_services/ci/hip_chat_message.rb
deleted file mode 100644
index d89466b689f..00000000000
--- a/app/models/project_services/ci/hip_chat_message.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-module Ci
- class HipChatMessage
- include Gitlab::Application.routes.url_helpers
-
- attr_reader :build
-
- def initialize(build)
- @build = build
- end
-
- def to_s
- lines = Array.new
- lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ")
- lines.push("<a href=\"#{builds_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}\">Commit ##{commit.id}</a></br>")
- lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
- lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).")
- lines.join('')
- end
-
- def status_color(build_or_commit=nil)
- build_or_commit ||= commit_status
- case build_or_commit
- when :success
- 'green'
- when :failed, :canceled
- 'red'
- else # :pending, :running or unknown
- 'yellow'
- end
- end
-
- def notify?
- [:failed, :canceled].include?(commit_status)
- end
-
-
- private
-
- def commit
- build.commit
- end
-
- def project
- commit.project
- end
-
- def build_status
- build.status.to_sym
- end
-
- def commit_status
- commit.status.to_sym
- end
-
- def humanized_status(build_or_commit=nil)
- build_or_commit ||= commit_status
- case build_or_commit
- when :pending
- "Pending"
- when :running
- "Running"
- when :failed
- "Failed"
- when :success
- "Successful"
- when :canceled
- "Canceled"
- else
- "Unknown"
- end
- end
- end
-end
diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb
deleted file mode 100644
index 0df03890efb..00000000000
--- a/app/models/project_services/ci/hip_chat_service.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-module Ci
- class HipChatService < Ci::Service
- prop_accessor :hipchat_token, :hipchat_room, :hipchat_server
- boolean_accessor :notify_only_broken_builds
- validates :hipchat_token, presence: true, if: :activated?
- validates :hipchat_room, presence: true, if: :activated?
- default_value_for :notify_only_broken_builds, true
-
- def title
- "HipChat"
- end
-
- def description
- "Private group chat, video chat, instant messaging for teams"
- end
-
- def help
- end
-
- def to_param
- 'hip_chat'
- end
-
- def fields
- [
- { type: 'text', name: 'hipchat_token', label: 'Token', placeholder: '' },
- { type: 'text', name: 'hipchat_room', label: 'Room', placeholder: '' },
- { type: 'text', name: 'hipchat_server', label: 'Server', placeholder: 'https://hipchat.example.com', help: 'Leave blank for default' },
- { type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
- ]
- end
-
- def can_execute?(build)
- return if build.allow_failure?
-
- commit = build.commit
- return unless commit
- return unless commit.latest_builds.include? build
-
- case commit.status.to_sym
- when :failed
- true
- when :success
- true unless notify_only_broken_builds?
- else
- false
- end
- end
-
- def execute(build)
- msg = Ci::HipChatMessage.new(build)
- opts = default_options.merge(
- token: hipchat_token,
- room: hipchat_room,
- server: server_url,
- color: msg.status_color,
- notify: msg.notify?
- )
- Ci::HipChatNotifierWorker.perform_async(msg.to_s, opts)
- end
-
- private
-
- def default_options
- {
- service_name: 'GitLab CI',
- message_format: 'html'
- }
- end
-
- def server_url
- if hipchat_server.blank?
- 'https://api.hipchat.com'
- else
- hipchat_server
- end
- end
- end
-end
diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb
deleted file mode 100644
index d31dd6899c1..00000000000
--- a/app/models/project_services/ci/mail_service.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-module Ci
- class MailService < Ci::Service
- delegate :email_recipients, :email_recipients=,
- :email_add_pusher, :email_add_pusher=,
- :email_only_broken_builds, :email_only_broken_builds=, to: :project, prefix: false
-
- before_save :update_project
-
- default_value_for :active, true
-
- def title
- 'Mail'
- end
-
- def description
- 'Email notification'
- end
-
- def to_param
- 'mail'
- end
-
- def fields
- [
- { type: 'text', name: 'email_recipients', label: 'Recipients', help: 'Whitespace-separated list of recipient addresses' },
- { type: 'checkbox', name: 'email_add_pusher', label: 'Add pusher to recipients list' },
- { type: 'checkbox', name: 'email_only_broken_builds', label: 'Notify only broken builds' }
- ]
- end
-
- def can_execute?(build)
- return if build.allow_failure?
-
- # it doesn't make sense to send emails for retried builds
- commit = build.commit
- return unless commit
- return unless commit.latest_builds.include?(build)
-
- case build.status.to_sym
- when :failed
- true
- when :success
- true unless email_only_broken_builds
- else
- false
- end
- end
-
- def execute(build)
- build.project_recipients.each do |recipient|
- case build.status.to_sym
- when :success
- mailer.build_success_email(build.id, recipient)
- when :failed
- mailer.build_fail_email(build.id, recipient)
- end
- end
- end
-
- private
-
- def update_project
- project.save!
- end
-
- def mailer
- Ci::Notify.delay
- end
- end
-end
diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb
deleted file mode 100644
index 1a6ff8e34c9..00000000000
--- a/app/models/project_services/ci/slack_message.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'slack-notifier'
-
-module Ci
- class SlackMessage
- include Gitlab::Application.routes.url_helpers
-
- def initialize(commit)
- @commit = commit
- end
-
- def pretext
- ''
- end
-
- def color
- attachment_color
- end
-
- def fallback
- format(attachment_message)
- end
-
- def attachments
- fields = []
-
- commit.latest_builds.each do |build|
- next if build.allow_failure?
- next unless build.failed?
- fields << {
- title: build.name,
- value: "Build <#{namespace_project_build_url(build.gl_project.namespace, build.gl_project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
- }
- end
-
- [{
- text: attachment_message,
- color: attachment_color,
- fields: fields
- }]
- end
-
- private
-
- attr_reader :commit
-
- def attachment_message
- out = "<#{ci_project_url(project)}|#{project_name}>: "
- out << "Commit <#{builds_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}|\##{commit.id}> "
- out << "(<#{commit_sha_link}|#{commit.short_sha}>) "
- out << "of <#{commit_ref_link}|#{commit.ref}> "
- out << "by #{commit.git_author_name} " if commit.git_author_name
- out << "#{commit_status} in "
- out << "#{commit.duration} second(s)"
- end
-
- def format(string)
- Slack::Notifier::LinkFormatter.format(string)
- end
-
- def project
- commit.project
- end
-
- def project_name
- project.name
- end
-
- def commit_sha_link
- "#{project.gitlab_url}/commit/#{commit.sha}"
- end
-
- def commit_ref_link
- "#{project.gitlab_url}/commits/#{commit.ref}"
- end
-
- def attachment_color
- if commit.success?
- 'good'
- else
- 'danger'
- end
- end
-
- def commit_status
- if commit.success?
- 'succeeded'
- else
- 'failed'
- end
- end
- end
-end
diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb
deleted file mode 100644
index 7064bfe78db..00000000000
--- a/app/models/project_services/ci/slack_service.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-module Ci
- class SlackService < Ci::Service
- prop_accessor :webhook
- boolean_accessor :notify_only_broken_builds
- validates :webhook, presence: true, if: :activated?
-
- default_value_for :notify_only_broken_builds, true
-
- def title
- 'Slack'
- end
-
- def description
- 'A team communication tool for the 21st century'
- end
-
- def to_param
- 'slack'
- end
-
- def help
- 'Visit https://www.slack.com/services/new/incoming-webhook. Then copy link and save project!' unless webhook.present?
- end
-
- def fields
- [
- { type: 'text', name: 'webhook', label: 'Webhook URL', placeholder: '' },
- { type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
- ]
- end
-
- def can_execute?(build)
- return if build.allow_failure?
-
- commit = build.commit
- return unless commit
- return unless commit.latest_builds.include?(build)
-
- case commit.status.to_sym
- when :failed
- true
- when :success
- true unless notify_only_broken_builds?
- else
- false
- end
- end
-
- def execute(build)
- message = Ci::SlackMessage.new(build.commit)
- options = default_options.merge(
- color: message.color,
- fallback: message.fallback,
- attachments: message.attachments
- )
- Ci::SlackNotifierWorker.perform_async(webhook, message.pretext, options)
- end
-
- private
-
- def default_options
- {
- username: 'GitLab CI'
- }
- end
- end
-end
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index c240213200d..08e5ccb3855 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -19,20 +19,19 @@
#
class DroneCiService < CiService
-
+
prop_accessor :drone_url, :token, :enable_ssl_verification
- validates :drone_url,
- presence: true,
- format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
- validates :token,
- presence: true,
- if: :activated?
+
+ validates :drone_url, presence: true, url: true, if: :activated?
+ validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
- hook.enable_ssl_verification = enable_ssl_verification
+ # If using a service template, project may not be available
+ hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
+ hook.enable_ssl_verification = !!enable_ssl_verification
hook.save
end
@@ -56,16 +55,16 @@ class DroneCiService < CiService
end
def merge_request_status_path(iid, sha = nil, ref = nil)
- url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
"?access_token=#{token}"]
URI.join(*url).to_s
end
def commit_status_path(sha, ref)
- url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
URI.join(*url).to_s
@@ -112,15 +111,15 @@ class DroneCiService < CiService
end
def merge_request_page(iid, sha, ref)
- url = [drone_url,
+ url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
URI.join(*url).to_s
end
def commit_page(sha, ref)
- url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}"]
URI.join(*url).to_s
@@ -161,10 +160,10 @@ class DroneCiService < CiService
end
def push_valid?(data)
- opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
+ opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
source_branch: Gitlab::Git.ref_name(data[:ref]))
- opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
+ opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
!Gitlab::Git.blank_ref?(data[:after])
end
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index 9c46af7e721..74c57949b4d 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -22,10 +22,8 @@ class ExternalWikiService < Service
include HTTParty
prop_accessor :external_wiki_url
- validates :external_wiki_url,
- presence: true,
- format: { with: /\A#{URI.regexp}\z/ },
- if: :activated?
+
+ validates :external_wiki_url, presence: true, url: true, if: :activated?
def title
'External Wiki'
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 27fc19379f1..15c7c907f7e 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -58,6 +58,6 @@ class FlowdockService < Service
repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
- )
+ )
end
end
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 91ef267ad79..202fee042e3 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -57,6 +57,6 @@ class GemnasiumService < Service
token: token,
api_key: api_key,
repo: project.repository.path_to_repo
- )
+ )
end
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 095d04e0df4..d73182d40ac 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -19,75 +19,5 @@
#
class GitlabCiService < CiService
- include Gitlab::Application.routes.url_helpers
-
- after_save :compose_service_hook, if: :activated?
- after_save :ensure_gitlab_ci_project, if: :activated?
-
- def compose_service_hook
- hook = service_hook || build_service_hook
- hook.save
- end
-
- def ensure_gitlab_ci_project
- project.ensure_gitlab_ci_project
- end
-
- def supported_events
- %w(push tag_push)
- end
-
- def execute(data)
- return unless supported_events.include?(data[:object_kind])
-
- ci_project = project.gitlab_ci_project
- if ci_project
- current_user = User.find_by(id: data[:user_id])
- Ci::CreateCommitService.new.execute(ci_project, current_user, data)
- end
- end
-
- def token
- if project.gitlab_ci_project.present?
- project.gitlab_ci_project.token
- end
- end
-
- def get_ci_commit(sha, ref)
- Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha)
- end
-
- def commit_status(sha, ref)
- get_ci_commit(sha, ref).status
- rescue ActiveRecord::RecordNotFound
- :error
- end
-
- def commit_coverage(sha, ref)
- get_ci_commit(sha, ref).coverage
- rescue ActiveRecord::RecordNotFound
- :error
- end
-
- def build_page(sha, ref)
- if project.gitlab_ci_project.present?
- builds_namespace_project_commit_url(project.namespace, project, sha)
- end
- end
-
- def title
- 'GitLab CI'
- end
-
- def description
- 'Continuous integration server from GitLab'
- end
-
- def to_param
- 'gitlab_ci'
- end
-
- def fields
- []
- end
+ # this is no longer used
end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index af2840a57f0..1e1686a11c6 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -22,8 +22,16 @@ class HipchatService < Service
MAX_COMMITS = 3
prop_accessor :token, :room, :server, :notify, :color, :api_version
+ boolean_accessor :notify_only_broken_builds
validates :token, presence: true, if: :activated?
+ def initialize_properties
+ if properties.nil?
+ self.properties = {}
+ self.notify_only_broken_builds = true
+ end
+ end
+
def title
'HipChat'
end
@@ -45,12 +53,13 @@ class HipchatService < Service
{ type: 'text', name: 'api_version',
placeholder: 'Leave blank for default (v2)' },
{ type: 'text', name: 'server',
- placeholder: 'Leave blank for default. https://hipchat.example.com' }
+ placeholder: 'Leave blank for default. https://hipchat.example.com' },
+ { type: 'checkbox', name: 'notify_only_broken_builds' },
]
end
def supported_events
- %w(push issue merge_request note tag_push)
+ %w(push issue merge_request note tag_push build)
end
def execute(data)
@@ -94,6 +103,8 @@ class HipchatService < Service
create_merge_request_message(data) unless is_update?(data)
when "note"
create_note_message(data)
+ when "build"
+ create_build_message(data) if should_build_be_notified?(data)
end
end
@@ -235,6 +246,20 @@ class HipchatService < Service
message
end
+ def create_build_message(data)
+ ref_type = data[:tag] ? 'tag' : 'branch'
+ ref = data[:ref]
+ sha = data[:sha]
+ user_name = data[:commit][:author_name]
+ status = data[:commit][:status]
+ duration = data[:commit][:duration]
+
+ branch_link = "<a href=\"#{project_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"
+ commit_link = "<a href=\"#{project_url}/commit/#{URI.escape(sha)}/builds\">#{Commit.truncate_sha(sha)}</a>"
+
+ "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
+ end
+
def project_name
project.name_with_namespace.gsub(/\s/, '')
end
@@ -250,4 +275,24 @@ class HipchatService < Service
def is_update?(data)
data[:object_attributes][:action] == 'update'
end
+
+ def humanized_status(status)
+ case status
+ when 'success'
+ 'passed'
+ else
+ status
+ end
+ end
+
+ def should_build_be_notified?(data)
+ case data[:commit][:status]
+ when 'success'
+ !notify_only_broken_builds?
+ when 'failed'
+ true
+ else
+ false
+ end
+ end
end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 7cd5e892507..375b4534d07 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -20,8 +20,16 @@
class SlackService < Service
prop_accessor :webhook, :username, :channel
+ boolean_accessor :notify_only_broken_builds
validates :webhook, presence: true, if: :activated?
+ def initialize_properties
+ if properties.nil?
+ self.properties = {}
+ self.notify_only_broken_builds = true
+ end
+ end
+
def title
'Slack'
end
@@ -45,12 +53,13 @@ class SlackService < Service
{ type: 'text', name: 'webhook',
placeholder: 'https://hooks.slack.com/services/...' },
{ type: 'text', name: 'username', placeholder: 'username' },
- { type: 'text', name: 'channel', placeholder: '#channel' }
+ { type: 'text', name: 'channel', placeholder: '#channel' },
+ { type: 'checkbox', name: 'notify_only_broken_builds' },
]
end
def supported_events
- %w(push issue merge_request note tag_push)
+ %w(push issue merge_request note tag_push build)
end
def execute(data)
@@ -78,6 +87,8 @@ class SlackService < Service
MergeMessage.new(data) unless is_update?(data)
when "note"
NoteMessage.new(data)
+ when "build"
+ BuildMessage.new(data) if should_build_be_notified?(data)
end
opt = {}
@@ -86,7 +97,7 @@ class SlackService < Service
if message
notifier = Slack::Notifier.new(webhook, opt)
- notifier.ping(message.pretext, attachments: message.attachments)
+ notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
end
end
@@ -103,9 +114,21 @@ class SlackService < Service
def is_update?(data)
data[:object_attributes][:action] == 'update'
end
+
+ def should_build_be_notified?(data)
+ case data[:commit][:status]
+ when 'success'
+ !notify_only_broken_builds?
+ when 'failed'
+ true
+ else
+ false
+ end
+ end
end
require "slack_service/issue_message"
require "slack_service/push_message"
require "slack_service/merge_message"
require "slack_service/note_message"
+require "slack_service/build_message"
diff --git a/app/models/project_services/slack_service/base_message.rb b/app/models/project_services/slack_service/base_message.rb
index aa00d6061a1..f1182824687 100644
--- a/app/models/project_services/slack_service/base_message.rb
+++ b/app/models/project_services/slack_service/base_message.rb
@@ -10,6 +10,9 @@ class SlackService
format(message)
end
+ def fallback
+ end
+
def attachments
raise NotImplementedError
end
diff --git a/app/models/project_services/slack_service/build_message.rb b/app/models/project_services/slack_service/build_message.rb
new file mode 100644
index 00000000000..c124cad4afd
--- /dev/null
+++ b/app/models/project_services/slack_service/build_message.rb
@@ -0,0 +1,82 @@
+class SlackService
+ class BuildMessage < BaseMessage
+ attr_reader :sha
+ attr_reader :ref_type
+ attr_reader :ref
+ attr_reader :status
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :user_name
+ attr_reader :duration
+
+ def initialize(params, commit = true)
+ @sha = params[:sha]
+ @ref_type = params[:tag] ? 'tag' : 'branch'
+ @ref = params[:ref]
+ @project_name = params[:project_name]
+ @project_url = params[:project_url]
+ @status = params[:commit][:status]
+ @user_name = params[:commit][:author_name]
+ @duration = params[:commit][:duration]
+ end
+
+ def pretext
+ ''
+ end
+
+ def fallback
+ format(message)
+ end
+
+ def attachments
+ [{ text: format(message), color: attachment_color }]
+ end
+
+ private
+
+ def message
+ "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} second(s)"
+ end
+
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def humanized_status
+ case status
+ when 'success'
+ 'passed'
+ else
+ status
+ end
+ end
+
+ def attachment_color
+ if status == 'success'
+ 'good'
+ else
+ 'danger'
+ end
+ end
+
+ def branch_url
+ "#{project_url}/commits/#{ref}"
+ end
+
+ def branch_link
+ "[#{ref}](#{branch_url})"
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def commit_url
+ "#{project_url}/commit/#{sha}/builds"
+ end
+
+ def commit_link
+ "[#{Commit.truncate_sha(sha)}](#{commit_url})"
+ end
+ end
+end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 0b022461250..a63700693d7 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -23,16 +23,14 @@ class TeamcityService < CiService
prop_accessor :teamcity_url, :build_type, :username, :password
- validates :teamcity_url,
- presence: true,
- format: { with: /\A#{URI.regexp}\z/ }, if: :activated?
+ validates :teamcity_url, presence: true, url: true, if: :activated?
validates :build_type, presence: true, if: :activated?
validates :username,
presence: true,
- if: ->(service) { service.password? }, if: :activated?
+ if: ->(service) { service.activated? && service.password }
validates :password,
presence: true,
- if: ->(service) { service.username? }, if: :activated?
+ if: ->(service) { service.activated? && service.username }
attr_accessor :response
@@ -147,6 +145,6 @@ class TeamcityService < CiService
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: auth
- )
+ )
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c1836103463..2c25f4ce451 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1,7 +1,6 @@
require 'securerandom'
class Repository
- class PreReceiveError < StandardError; end
class CommitError < StandardError; end
include Gitlab::ShellAdapter
@@ -101,17 +100,26 @@ class Repository
end
def find_branch(name)
- branches.find { |branch| branch.name == name }
+ raw_repository.branches.find { |branch| branch.name == name }
end
def find_tag(name)
- tags.find { |tag| tag.name == name }
+ raw_repository.tags.find { |tag| tag.name == name }
end
- def add_branch(branch_name, ref)
- expire_branches_cache
+ def add_branch(user, branch_name, target)
+ oldrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+ target = commit(target).try(:id)
+
+ return false unless target
+
+ GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
+ rugged.branches.create(branch_name, target)
+ end
- gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
+ expire_branches_cache
+ find_branch(branch_name)
end
def add_tag(tag_name, ref, message = nil)
@@ -120,10 +128,20 @@ class Repository
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
- def rm_branch(branch_name)
+ def rm_branch(user, branch_name)
expire_branches_cache
- gitlab_shell.rm_branch(path_with_namespace, branch_name)
+ branch = find_branch(branch_name)
+ oldrev = branch.try(:target)
+ newrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+
+ GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
+ rugged.branches.delete(branch_name)
+ end
+
+ expire_branches_cache
+ true
end
def rm_tag(tag_name)
@@ -253,9 +271,25 @@ class Repository
def license
cache.fetch(:license) do
- tree(:head).blobs.find do |file|
- file.name =~ /\Alicense/i
+ licenses = tree(:head).blobs.find_all do |file|
+ file.name =~ /\A(copying|license|licence)/i
+ end
+
+ preferences = [
+ /\Alicen[sc]e\z/i, # LICENSE, LICENCE
+ /\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt
+ /\Acopying\z/i, # COPYING
+ /\Acopying\.(?!lesser)/i, # COPYING.txt
+ /Acopying.lesser/i # COPYING.LESSER
+ ]
+
+ license = nil
+ preferences.each do |r|
+ license = licenses.find { |l| l.name =~ r }
+ break if license
end
+
+ license
end
end
@@ -311,6 +345,17 @@ class Repository
commit(sha)
end
+ def next_patch_branch
+ patch_branch_ids = self.branch_names.map do |n|
+ result = n.match(/\Apatch-([0-9]+)\z/)
+ result[1].to_i if result
+ end.compact
+
+ highest_patch_branch_id = patch_branch_ids.max || 0
+
+ "patch-#{highest_patch_branch_id + 1}"
+ end
+
# Remove archives older than 2 hours
def branches_sorted_by(value)
case value
@@ -550,7 +595,6 @@ class Repository
def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
- gl_id = Gitlab::ShellEnv.gl_id(current_user)
was_empty = empty?
# Create temporary ref
@@ -569,11 +613,7 @@ class Repository
raise CommitError.new('Failed to create commit')
end
- # Run GitLab pre-receive hook
- pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
- status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
-
- if status
+ GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if was_empty
# Create branch
rugged.references.create(ref, newrev)
@@ -588,16 +628,11 @@ class Repository
raise CommitError.new('Commit was rejected because branch received new push')
end
end
-
- # Run GitLab post receive hook
- post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
- post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
- else
- # Remove tmp ref and return error to user
- rugged.references.delete(tmp_ref)
-
- raise PreReceiveError.new('Commit was rejected by pre-receive hook')
end
+ rescue GitHooksService::PreReceiveError
+ # Remove tmp ref and return error to user
+ rugged.references.delete(tmp_ref)
+ raise
end
private
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 3eed5c16e45..f36eda1531b 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -17,12 +17,11 @@ class SentNotification < ActiveRecord::Base
belongs_to :noteable, polymorphic: true
belongs_to :recipient, class_name: "User"
- validate :project, :recipient, :reply_key, presence: true
- validate :reply_key, uniqueness: true
-
+ validates :project, :recipient, :reply_key, presence: true
+ validates :reply_key, uniqueness: true
validates :noteable_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit?
- validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
+ validates :line_code, line_code: true, allow_blank: true
class << self
def reply_key
diff --git a/app/models/service.rb b/app/models/service.rb
index d610abd1683..d3bf7f0ebd1 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -30,6 +30,7 @@ class Service < ActiveRecord::Base
default_value_for :merge_requests_events, true
default_value_for :tag_push_events, true
default_value_for :note_events, true
+ default_value_for :build_events, true
after_initialize :initialize_properties
@@ -40,13 +41,14 @@ class Service < ActiveRecord::Base
validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
- scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
+ scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
scope :push_hooks, -> { where(push_events: true, active: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
scope :issue_hooks, -> { where(issues_events: true, active: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) }
+ scope :build_hooks, -> { where(build_events: true, active: true) }
def activated?
active
@@ -133,6 +135,21 @@ class Service < ActiveRecord::Base
end
end
+ # Provide convenient boolean accessor methods
+ # for each serialized property.
+ # Also keep track of updated properties in a similar way as ActiveModel::Dirty
+ def self.boolean_accessor(*args)
+ self.prop_accessor(*args)
+
+ args.each do |arg|
+ class_eval %{
+ def #{arg}?
+ ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
+ end
+ }
+ end
+ end
+
# 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,
@@ -163,6 +180,7 @@ class Service < ActiveRecord::Base
assembla
bamboo
buildkite
+ builds_email
campfire
custom_issue_tracker
drone_ci
@@ -170,7 +188,6 @@ class Service < ActiveRecord::Base
external_wiki
flowdock
gemnasium
- gitlab_ci
hipchat
irker
jira
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b0831982aa7..f876be7a4c8 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
}x
end
+ def self.link_reference_pattern
+ super("snippets", /(?<snippet>\d+)/)
+ end
+
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}"
diff --git a/app/models/user.rb b/app/models/user.rb
index 9374f01f99f..e0ce091c54e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -56,6 +56,7 @@
# project_view :integer default(0)
# consumed_timestep :integer
# layout :integer default(0)
+# hide_project_limit :boolean default(FALSE)
#
require 'carrierwave/orm/activerecord'
@@ -68,8 +69,10 @@ class User < ActiveRecord::Base
include Gitlab::CurrentSettings
include Referable
include Sortable
- include TokenAuthenticatable
include CaseSensitivity
+ include TokenAuthenticatable
+
+ add_authentication_token_field :authentication_token
default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group
@@ -133,7 +136,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
has_one :abuse_report, dependent: :destroy
- has_many :ci_builds, dependent: :nullify, class_name: 'Ci::Build'
+ has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
#
@@ -148,11 +151,9 @@ class User < ActiveRecord::Base
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username,
+ namespace: true,
presence: true,
- uniqueness: { case_sensitive: false },
- exclusion: { in: Gitlab::Blacklist.path },
- format: { with: Gitlab::Regex.namespace_regex,
- message: Gitlab::Regex.namespace_regex_message }
+ uniqueness: { case_sensitive: false }
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
@@ -219,9 +220,9 @@ class User < ActiveRecord::Base
def find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
- where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
+ where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase)
else
- where(conditions).first
+ find_by(conditions)
end
end
@@ -284,7 +285,7 @@ class User < ActiveRecord::Base
end
def by_username_or_id(name_or_id)
- where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
+ find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i)
end
def build_user(attrs = {})
@@ -637,11 +638,11 @@ class User < ActiveRecord::Base
email.start_with?('temp-email-for-oauth')
end
- def avatar_url(size = nil)
+ def avatar_url(size = nil, scale = 2)
if avatar.present?
[gitlab_config.url, avatar.url].join
else
- GravatarService.new.execute(email, size)
+ GravatarService.new.execute(email, size, scale)
end
end
@@ -690,7 +691,7 @@ class User < ActiveRecord::Base
end
def starred?(project)
- starred_projects.exists?(project)
+ starred_projects.exists?(project.id)
end
def toggle_star(project)
@@ -770,10 +771,9 @@ class User < ActiveRecord::Base
def ci_authorized_runners
@ci_authorized_runners ||= begin
- runner_ids = Ci::RunnerProject.joins(:project).
- where("ci_projects.gitlab_id IN (#{ci_projects_union.to_sql})").
+ runner_ids = Ci::RunnerProject.
+ where("ci_runner_projects.gl_project_id IN (#{ci_projects_union.to_sql})").
select(:runner_id)
-
Ci::Runner.specific.where(id: runner_ids)
end
end
@@ -794,4 +794,9 @@ class User < ActiveRecord::Base
Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id),
other.select(:id)])
end
+
+ # Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
+ def send_devise_notification(notification, *args)
+ devise_mailer.send(notification, self, *args).deliver_later
+ end
end
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index 3d49cb05949..413f3f485a8 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -10,7 +10,7 @@
#
class UsersStarProject < ActiveRecord::Base
- belongs_to :project, counter_cache: :star_count
+ belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
validates :user, presence: true
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 912eb6258a4..ad901f2da5d 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -29,9 +29,11 @@ module Ci
build_attrs.merge!(ref: ref,
tag: tag,
trigger_request: trigger_request,
- user: user)
+ user: user,
+ project: commit.project)
- commit.builds.create!(build_attrs)
+ build = commit.builds.create!(build_attrs)
+ build.execute_hooks
end
end
end
diff --git a/app/services/ci/create_commit_service.rb b/app/services/ci/create_commit_service.rb
deleted file mode 100644
index 479a2d6defc..00000000000
--- a/app/services/ci/create_commit_service.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module Ci
- class CreateCommitService
- def execute(project, user, params)
- sha = params[:checkout_sha] || params[:after]
- origin_ref = params[:ref]
-
- unless origin_ref && sha.present?
- return false
- end
-
- ref = origin_ref.gsub(/\Arefs\/(tags|heads)\//, '')
-
- # Skip branch removal
- if sha == Ci::Git::BLANK_SHA
- return false
- end
-
- tag = origin_ref.start_with?('refs/tags/')
- commit = project.gl_project.ensure_ci_commit(sha)
- unless commit.skip_ci?
- commit.update_committed!
- commit.create_builds(ref, tag, user)
- end
-
- commit
- end
- end
-end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index 4b86cb0a1f5..b3dfc707221 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -1,13 +1,13 @@
module Ci
class CreateTriggerRequestService
def execute(project, trigger, ref, variables = nil)
- commit = project.gl_project.commit(ref)
+ commit = project.commit(ref)
return unless commit
# check if ref is tag
- tag = project.gl_project.repository.find_tag(ref).present?
+ tag = project.repository.find_tag(ref).present?
- ci_commit = project.gl_project.ensure_ci_commit(commit.sha)
+ ci_commit = project.ensure_ci_commit(commit.sha)
trigger_request = trigger.trigger_requests.create!(
variables: variables,
diff --git a/app/services/ci/event_service.rb b/app/services/ci/event_service.rb
deleted file mode 100644
index 3f4e02dd26c..00000000000
--- a/app/services/ci/event_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module Ci
- class EventService
- def remove_project(user, project)
- create(
- description: "Project \"#{project.name}\" has been removed by #{user.username}",
- user_id: user.id,
- is_admin: true
- )
- end
-
- def create_project(user, project)
- create(
- description: "Project \"#{project.name}\" has been created by #{user.username}",
- user_id: user.id,
- is_admin: true
- )
- end
-
- def change_project_settings(user, project)
- create(
- project_id: project.id,
- user_id: user.id,
- description: "User \"#{user.username}\" updated projects settings"
- )
- end
-
- def create(*args)
- Ci::Event.create!(*args)
- end
- end
-end
diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb
index b8d24193035..f469b13e902 100644
--- a/app/services/ci/image_for_build_service.rb
+++ b/app/services/ci/image_for_build_service.rb
@@ -4,10 +4,10 @@ module Ci
sha = params[:sha]
sha ||=
if params[:ref]
- project.gl_project.commit(params[:ref]).try(:sha)
+ project.commit(params[:ref]).try(:sha)
end
- commit = project.commits.ordered.find_by(sha: sha)
+ commit = project.ci_commits.ordered.find_by(sha: sha)
image_name = image_for_commit(commit)
image_path = Rails.root.join('public/ci', image_name)
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 7beb098659c..4ff268a6f06 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -8,10 +8,10 @@ module Ci
builds =
if current_runner.shared?
# don't run projects which have not enables shared runners
- builds.joins(commit: { gl_project: :gitlab_ci_project }).where(ci_projects: { shared_runners_enabled: true })
+ builds.joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true })
else
# do run projects which are only assigned to this runner
- builds.joins(:commit).where(ci_commits: { gl_project_id: current_runner.gl_projects_ids })
+ builds.where(project: current_runner.projects.where(builds_enabled: true))
end
builds = builds.order('created_at ASC')
@@ -20,10 +20,9 @@ module Ci
build.can_be_served?(current_runner)
end
-
if build
# In case when 2 runners try to assign the same build, second runner will be declined
- # with StateMachine::InvalidTransition in run! method.
+ # with StateMachines::InvalidTransition in run! method.
build.with_lock do
build.runner_id = current_runner.id
build.save!
@@ -33,7 +32,7 @@ module Ci
build
- rescue StateMachine::InvalidTransition
+ rescue StateMachines::InvalidTransition
nil
end
end
diff --git a/app/services/ci/test_hook_service.rb b/app/services/ci/test_hook_service.rb
deleted file mode 100644
index 3a17596aaeb..00000000000
--- a/app/services/ci/test_hook_service.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Ci
- class TestHookService
- def execute(hook, current_user)
- Ci::WebHookService.new.build_end(hook.project.commits.last.last_build)
- end
- end
-end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index cf7ae4345f3..de18f3bc556 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -13,8 +13,7 @@ class CreateBranchService < BaseService
return error('Branch already exists')
end
- repository.add_branch(branch_name, ref)
- new_branch = repository.find_branch(branch_name)
+ new_branch = repository.add_branch(current_user, branch_name, ref)
if new_branch
push_data = build_push_data(project, current_user, new_branch)
@@ -27,6 +26,8 @@ class CreateBranchService < BaseService
else
error('Invalid reference name')
end
+ rescue GitHooksService::PreReceiveError
+ error('Branch creation was rejected by Git hook')
end
def success(branch)
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
new file mode 100644
index 00000000000..31b407efeb1
--- /dev/null
+++ b/app/services/create_commit_builds_service.rb
@@ -0,0 +1,42 @@
+class CreateCommitBuildsService
+ def execute(project, user, params)
+ return false unless project.builds_enabled?
+
+ sha = params[:checkout_sha] || params[:after]
+ origin_ref = params[:ref]
+
+ unless origin_ref && sha.present?
+ return false
+ end
+
+ ref = Gitlab::Git.ref_name(origin_ref)
+
+ # Skip branch removal
+ if sha == Gitlab::Git::BLANK_SHA
+ return false
+ end
+
+ commit = project.ci_commit(sha)
+ unless commit
+ commit = project.ci_commits.new(sha: sha)
+
+ # Skip creating ci_commit when no gitlab-ci.yml is found
+ unless commit.ci_yaml_file
+ return false
+ end
+
+ # Create a new ci_commit
+ commit.save!
+ end
+
+ # Skip creating builds for commits that have [ci skip]
+ unless commit.skip_ci?
+ # Create builds for commit
+ tag = Gitlab::Git.tag_ref?(origin_ref)
+ commit.update_committed!
+ commit.create_builds(ref, tag, user)
+ end
+
+ commit
+ end
+end
diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb
new file mode 100644
index 00000000000..e06a6f2f47a
--- /dev/null
+++ b/app/services/create_release_service.rb
@@ -0,0 +1,31 @@
+require_relative 'base_service'
+
+class CreateReleaseService < BaseService
+ def execute(tag_name, release_description)
+
+ repository = project.repository
+ existing_tag = repository.find_tag(tag_name)
+
+ # Only create a release if the tag exists
+ if existing_tag
+ release = project.releases.find_by(tag: tag_name)
+
+ if release
+ error('Release already exists', 409)
+ else
+ release = project.releases.new({ tag: tag_name, description: release_description })
+ release.save
+
+ success(release)
+ end
+ else
+ error('Tag does not exist', 404)
+ end
+ end
+
+ def success(release)
+ out = super()
+ out[:release] = release
+ out
+ end
+end
diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb
index 9917119fce2..2452999382a 100644
--- a/app/services/create_tag_service.rb
+++ b/app/services/create_tag_service.rb
@@ -19,16 +19,16 @@ class CreateTagService < BaseService
new_tag = repository.find_tag(tag_name)
if new_tag
- if release_description
- release = project.releases.find_or_initialize_by(tag: tag_name)
- release.update_attributes(description: release_description)
- end
-
push_data = create_push_data(project, current_user, new_tag)
EventCreateService.new.push(project, current_user, push_data)
project.execute_hooks(push_data.dup, :tag_push_hooks)
project.execute_services(push_data.dup, :tag_push_hooks)
+ if release_description
+ CreateReleaseService.new(@project, @current_user).
+ execute(tag_name, release_description)
+ end
+
success(new_tag)
else
error('Invalid reference name')
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index b19b112a0c4..22bf9dd935e 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService
return error('You dont have push access to repo', 405)
end
- if repository.rm_branch(branch_name)
+ if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch)
EventCreateService.new.push(project, current_user, push_data)
@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService
else
error('Failed to remove branch')
end
+ rescue GitHooksService::PreReceiveError
+ error('Branch deletion was rejected by Git hook')
end
def error(message, return_code = 400)
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 008833eed80..9a67b160940 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -26,7 +26,7 @@ module Files
else
error("Something went wrong. Your changes were not committed")
end
- rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex
+ rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message)
end
@@ -53,7 +53,7 @@ module Files
unless project.empty_repo?
unless repository.branch_names.include?(@current_branch)
- raise_error("You can only create files if you are on top of a branch")
+ raise_error("You can only create or edit files when you are on a branch")
end
if @current_branch != @target_branch
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
new file mode 100644
index 00000000000..8f5c3393dfc
--- /dev/null
+++ b/app/services/git_hooks_service.rb
@@ -0,0 +1,28 @@
+class GitHooksService
+ PreReceiveError = Class.new(StandardError)
+
+ def execute(user, repo_path, oldrev, newrev, ref)
+ @repo_path = repo_path
+ @user = Gitlab::ShellEnv.gl_id(user)
+ @oldrev = oldrev
+ @newrev = newrev
+ @ref = ref
+
+ %w(pre-receive update).each do |hook_name|
+ unless run_hook(hook_name)
+ raise PreReceiveError.new("Git operation was rejected by #{hook_name} hook")
+ end
+ end
+
+ yield
+
+ run_hook('post-receive')
+ end
+
+ private
+
+ def run_hook(name)
+ hook = Gitlab::Git::Hook.new(name, @repo_path)
+ hook.trigger(@user, @oldrev, @newrev, @ref)
+ end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index f11690aa3f4..d7ea30bc315 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -61,6 +61,7 @@ class GitPushService
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
+ CreateCommitBuildsService.new.execute(project, @user, @push_data)
ProjectCacheWorker.perform_async(project.id)
end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 1cc42b0b0ad..4144c7111d0 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -10,6 +10,7 @@ class GitTagPushService
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
+ CreateCommitBuildsService.new.execute(project, @user, @push_data)
ProjectCacheWorker.perform_async(project.id)
true
diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb
index 4bee0c26a68..433ecc2df32 100644
--- a/app/services/gravatar_service.rb
+++ b/app/services/gravatar_service.rb
@@ -1,13 +1,13 @@
class GravatarService
include Gitlab::CurrentSettings
- def execute(email, size = nil)
+ def execute(email, size = nil, scale = 2)
if current_application_settings.gravatar_enabled? && email.present?
size = 40 if size.nil? || size <= 0
sprintf gravatar_url,
hash: Digest::MD5.hexdigest(email.strip.downcase),
- size: size,
+ size: size * scale,
email: email.strip
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 11d2b08bba7..2556f06e2d3 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -27,6 +27,12 @@ class IssuableBaseService < BaseService
old_branch, new_branch)
end
+ def create_task_status_note(issuable)
+ issuable.updated_tasks.each do |task|
+ SystemNoteService.change_task_status(issuable, issuable.project, current_user, task)
+ end
+ end
+
def filter_params(issuable_ability_name = :issue)
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
@@ -47,14 +53,7 @@ class IssuableBaseService < BaseService
if params.present? && issuable.update_attributes(params.merge(updated_by: current_user))
issuable.reset_events_cache
-
- if issuable.labels != old_labels
- create_labels_note(
- issuable,
- issuable.labels - old_labels,
- old_labels - issuable.labels)
- end
-
+ handle_common_system_notes(issuable, old_labels: old_labels)
handle_changes(issuable)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
@@ -71,4 +70,19 @@ class IssuableBaseService < BaseService
close_service.new(project, current_user, {}).execute(issuable)
end
end
+
+ def handle_common_system_notes(issuable, options = {})
+ if issuable.previous_changes.include?('title')
+ create_title_change_note(issuable, issuable.previous_changes['title'].first)
+ end
+
+ if issuable.previous_changes.include?('description') && issuable.tasks?
+ create_task_status_note(issuable)
+ end
+
+ old_labels = options[:old_labels]
+ if old_labels && (issuable.labels != old_labels)
+ create_labels_note(issuable, issuable.labels - old_labels, old_labels - issuable.labels)
+ end
+ end
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 7c112f731a7..a55a04dd5e0 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -13,10 +13,6 @@ module Issues
create_assignee_note(issue)
notification_service.reassigned_issue(issue, current_user)
end
-
- if issue.previous_changes.include?('title')
- create_title_change_note(issue, issue.previous_changes['title'].first)
- end
end
def reopen_service
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 7963af127e1..cabc3d8fabb 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -6,15 +6,12 @@ module MergeRequests
# Executed when you do merge via GitLab UI
#
class MergeService < MergeRequests::BaseService
- attr_reader :merge_request, :commit_message
+ attr_reader :merge_request
- def execute(merge_request, commit_message)
- @commit_message = commit_message
+ def execute(merge_request)
@merge_request = merge_request
- unless @merge_request.mergeable?
- return error('Merge request is not mergeable')
- end
+ return error('Merge request is not mergeable') unless @merge_request.mergeable?
merge_request.in_locked_state do
if commit
@@ -32,13 +29,13 @@ module MergeRequests
committer = repository.user_to_committer(current_user)
options = {
- message: commit_message,
+ message: params[:commit_message] || merge_request.merge_commit_message,
author: committer,
committer: committer
}
repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
- rescue Exception => e
+ rescue StandardError => e
merge_request.update(merge_error: "Something went wrong during merge")
Rails.logger.error(e.message)
return false
@@ -46,6 +43,11 @@ module MergeRequests
def after_merge
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
+
+ if params[:should_remove_source_branch]
+ DeleteBranchService.new(@merge_request.source_project, current_user).
+ execute(merge_request.source_branch)
+ end
end
end
end
diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb
new file mode 100644
index 00000000000..5cf7404a493
--- /dev/null
+++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb
@@ -0,0 +1,55 @@
+module MergeRequests
+ class MergeWhenBuildSucceedsService < MergeRequests::BaseService
+ # Marks the passed `merge_request` to be merged when the build succeeds or
+ # updates the params for the automatic merge
+ def execute(merge_request)
+ merge_request.merge_params.merge!(params)
+
+ # The service is also called when the merge params are updated.
+ already_approved = merge_request.merge_when_build_succeeds?
+
+ unless already_approved
+ merge_request.merge_when_build_succeeds = true
+ merge_request.merge_user = @current_user
+
+ SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.last_commit)
+ end
+
+ merge_request.save
+ end
+
+ # Triggers the automatic merge of merge_request once the build succeeds
+ def trigger(build)
+ merge_requests = merge_request_from(build)
+
+ merge_requests.each do |merge_request|
+ next unless merge_request.merge_when_build_succeeds?
+
+ if merge_request.ci_commit && merge_request.ci_commit.success? && merge_request.mergeable?
+ MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
+ end
+ end
+ end
+
+ # Cancels the automatic merge
+ def cancel(merge_request)
+ if merge_request.merge_when_build_succeeds? && merge_request.open?
+ merge_request.reset_merge_when_build_succeeds
+ SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user)
+
+ success
+ else
+ error("Can't cancel the automatic merge", 406)
+ end
+ end
+
+ private
+
+ def merge_request_from(build)
+ merge_requests = @project.origin_merge_requests.opened.where(source_branch: build.ref).to_a
+ merge_requests += @project.fork_merge_requests.opened.where(source_branch: build.ref).to_a
+
+ merge_requests.uniq.select(&:source_project)
+ end
+ end
+end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index e180edb4bf3..8b3d56c2b4c 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -11,6 +11,7 @@ module MergeRequests
# empty diff during a manual merge
close_merge_requests
reload_merge_requests
+ reset_merge_when_build_succeeds
# Leave a system note if a branch was deleted/added
if branch_added? || branch_removed?
@@ -57,7 +58,6 @@ module MergeRequests
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
-
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_code
merge_request.mark_as_unchecked
@@ -76,6 +76,10 @@ module MergeRequests
end
end
+ def reset_merge_when_build_succeeds
+ merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds)
+ end
+
def find_new_commits
if branch_added?
@commits = []
@@ -108,7 +112,7 @@ module MergeRequests
merge_requests_for_source_branch.each do |merge_request|
SystemNoteService.change_branch_presence(
- merge_request, merge_request.project, @current_user,
+ merge_request, merge_request.project, @current_user,
:source, @branch_name, presence)
end
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index a5db3776eb6..5ff2cc03dda 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -30,10 +30,6 @@ module MergeRequests
notification_service.reassigned_merge_request(merge_request, current_user)
end
- if merge_request.previous_changes.include?('title')
- create_title_change_note(merge_request, merge_request.previous_changes['title'].first)
- end
-
if merge_request.previous_changes.include?('target_branch') ||
merge_request.previous_changes.include?('source_branch')
merge_request.mark_as_unchecked
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 25a985df4d8..a8486e6a5a1 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,11 +5,6 @@ module Notes
note.author = current_user
note.system = false
- if contains_emoji_only?(params[:note])
- note.is_award = true
- note.note = emoji_name(params[:note])
- end
-
if note.save
notification_service.new_note(note)
@@ -33,13 +28,5 @@ module Notes
note.project.execute_hooks(note_data, :note_hooks)
note.project.execute_services(note_data, :note_hooks)
end
-
- def contains_emoji_only?(note)
- note =~ /\A:?[-_+[:alnum:]]*:?\s?\z/
- end
-
- def emoji_name(note)
- note.match(/\A:?([-_+[:alnum:]]*):?\s?/)[1]
- end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index d6550fbb555..bdf7b3ad2bb 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -13,14 +13,14 @@ class NotificationService
# even if user disabled notifications
def new_key(key)
if key.user
- mailer.new_ssh_key_email(key.id)
+ mailer.new_ssh_key_email(key.id).deliver_later
end
end
# Always notify user about email added to profile
def new_email(email)
if email.user
- mailer.new_email_email(email.id)
+ mailer.new_email_email(email.id).deliver_later
end
end
@@ -79,17 +79,27 @@ class NotificationService
end
def merge_mr(merge_request, current_user)
- close_resource_email(merge_request, merge_request.target_project, current_user, 'merged_merge_request_email')
+ close_resource_email(
+ merge_request,
+ merge_request.target_project,
+ current_user,
+ 'merged_merge_request_email'
+ )
end
def reopen_mr(merge_request, current_user)
- reopen_resource_email(merge_request, merge_request.target_project, current_user, 'merge_request_status_email', 'reopened')
+ reopen_resource_email(
+ merge_request,
+ merge_request.target_project,
+ current_user, 'merge_request_status_email',
+ 'reopened'
+ )
end
# Notify new user with email after creation
def new_user(user, token = nil)
# Don't email omniauth created users
- mailer.new_user_email(user.id, token) unless user.identities.any?
+ mailer.new_user_email(user.id, token).deliver_later unless user.identities.any?
end
# Notify users on new note in system
@@ -135,53 +145,63 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients.delete(note.author)
+ recipients = recipients.uniq
# build notify method like 'note_commit_email'
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
-
recipients.each do |recipient|
- mailer.send(notify_method, recipient.id, note.id)
+ mailer.send(notify_method, recipient.id, note.id).deliver_later
end
end
def invite_project_member(project_member, token)
- mailer.project_member_invited_email(project_member.id, token)
+ mailer.project_member_invited_email(project_member.id, token).deliver_later
end
def accept_project_invite(project_member)
- mailer.project_invite_accepted_email(project_member.id)
+ mailer.project_invite_accepted_email(project_member.id).deliver_later
end
def decline_project_invite(project_member)
- mailer.project_invite_declined_email(project_member.project.id, project_member.invite_email, project_member.access_level, project_member.created_by_id)
+ mailer.project_invite_declined_email(
+ project_member.project.id,
+ project_member.invite_email,
+ project_member.access_level,
+ project_member.created_by_id
+ ).deliver_later
end
def new_project_member(project_member)
- mailer.project_access_granted_email(project_member.id)
+ mailer.project_access_granted_email(project_member.id).deliver_later
end
def update_project_member(project_member)
- mailer.project_access_granted_email(project_member.id)
+ mailer.project_access_granted_email(project_member.id).deliver_later
end
def invite_group_member(group_member, token)
- mailer.group_member_invited_email(group_member.id, token)
+ mailer.group_member_invited_email(group_member.id, token).deliver_later
end
def accept_group_invite(group_member)
- mailer.group_invite_accepted_email(group_member.id)
+ mailer.group_invite_accepted_email(group_member.id).deliver_later
end
def decline_group_invite(group_member)
- mailer.group_invite_declined_email(group_member.group.id, group_member.invite_email, group_member.access_level, group_member.created_by_id)
+ mailer.group_invite_declined_email(
+ group_member.group.id,
+ group_member.invite_email,
+ group_member.access_level,
+ group_member.created_by_id
+ ).deliver_later
end
def new_group_member(group_member)
- mailer.group_access_granted_email(group_member.id)
+ mailer.group_access_granted_email(group_member.id).deliver_later
end
def update_group_member(group_member)
- mailer.group_access_granted_email(group_member.id)
+ mailer.group_access_granted_email(group_member.id).deliver_later
end
def project_was_moved(project, old_path_with_namespace)
@@ -189,7 +209,11 @@ class NotificationService
recipients = reject_muted_users(recipients, project)
recipients.each do |recipient|
- mailer.project_was_moved_email(project.id, recipient.id, old_path_with_namespace)
+ mailer.project_was_moved_email(
+ project.id,
+ recipient.id,
+ old_path_with_namespace
+ ).deliver_later
end
end
@@ -339,7 +363,7 @@ class NotificationService
recipients = build_recipients(target, project, target.author)
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id)
+ mailer.send(method, recipient.id, target.id).deliver_later
end
end
@@ -347,7 +371,7 @@ class NotificationService
recipients = build_recipients(target, project, current_user)
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, current_user.id)
+ mailer.send(method, recipient.id, target.id, current_user.id).deliver_later
end
end
@@ -358,7 +382,13 @@ class NotificationService
recipients = build_recipients(target, project, current_user, [previous_assignee])
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, previous_assignee_id, current_user.id)
+ mailer.send(
+ method,
+ recipient.id,
+ target.id,
+ previous_assignee_id,
+ current_user.id
+ ).deliver_later
end
end
@@ -366,7 +396,7 @@ class NotificationService
recipients = build_recipients(target, project, current_user)
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, status, current_user.id)
+ mailer.send(method, recipient.id, target.id, status, current_user.id).deliver_later
end
end
@@ -388,7 +418,7 @@ class NotificationService
end
def mailer
- Notify.delay
+ Notify
end
def previous_record(object, attribute)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 700a1db04d8..a6820183bee 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -64,8 +64,10 @@ module Projects
after_create_actions if @project.persisted?
@project
- rescue
- @project.errors.add(:base, "Can't save project. Please try again later")
+ rescue => e
+ message = "Unable to save project: #{e.message}"
+ Rails.logger.error(message)
+ @project.errors.add(:base, message) if @project
@project
end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 5da1c7afd92..0577ae778d5 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -7,6 +7,8 @@ module Projects
description: @project.description,
name: @project.name,
path: @project.path,
+ shared_runners_enabled: @project.shared_runners_enabled,
+ builds_enabled: @project.builds_enabled,
namespace_id: @params[:namespace].try(:id) || current_user.namespace.id
}
@@ -15,19 +17,6 @@ module Projects
end
new_project = CreateService.new(current_user, new_params).execute
-
- if new_project.persisted?
- if @project.builds_enabled?
- new_project.enable_ci
-
- settings = @project.gitlab_ci_project.attributes.select do |attr_name, value|
- ["public", "shared_runners_enabled", "allow_git_fetch"].include? attr_name
- end
-
- new_project.gitlab_ci_project.update(settings)
- end
- end
-
new_project
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 708c2f00486..6975b2ee55b 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -125,7 +125,21 @@ class SystemNoteService
# Returns the created Note object
def self.change_status(noteable, project, author, status, source)
body = "Status changed to #{status}"
- body += " by #{source.gfm_reference}" if source
+ body += " by #{source.gfm_reference(project)}" if source
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when 'merge when build succeeds' is executed
+ def self.merge_when_build_succeeds(noteable, project, author, last_commit)
+ body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds"
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when 'merge when build succeeds' is canceled
+ def self.cancel_merge_when_build_succeeds(noteable, project, author)
+ body = "Canceled the automatic merge"
create_note(noteable: noteable, project: project, author: author, note: body)
end
@@ -341,4 +355,22 @@ class SystemNoteService
"* #{commit_ids} - #{commits_text} from branch `#{branch}`\n"
end
+
+ # Called when the status of a Task has changed
+ #
+ # noteable - Noteable object.
+ # project - Project owning noteable
+ # author - User performing the change
+ # new_task - TaskList::Item object.
+ #
+ # Example Note text:
+ #
+ # "Soandso marked the task Whatever as completed."
+ #
+ # Returns the created Note object
+ def self.change_task_status(noteable, project, author, new_task)
+ status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
+ body = "Marked the task **#{new_task.source}** as #{status_label}"
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
end
diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb
new file mode 100644
index 00000000000..25eb13ef09a
--- /dev/null
+++ b/app/services/update_release_service.rb
@@ -0,0 +1,29 @@
+require_relative 'base_service'
+
+class UpdateReleaseService < BaseService
+ def execute(tag_name, release_description)
+
+ repository = project.repository
+ existing_tag = repository.find_tag(tag_name)
+
+ if existing_tag
+ release = project.releases.find_by(tag: tag_name)
+
+ if release
+ release.update_attributes(description: release_description)
+
+ success(release)
+ else
+ error('Release does not exist', 404)
+ end
+ else
+ error('Tag does not exist', 404)
+ end
+ end
+
+ def success(release)
+ out = super()
+ out[:release] = release
+ out
+ end
+end
diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb
index b4e0fc5772d..1dccc39e7e2 100644
--- a/app/uploaders/artifact_uploader.rb
+++ b/app/uploaders/artifact_uploader.rb
@@ -5,15 +5,15 @@ class ArtifactUploader < CarrierWave::Uploader::Base
attr_accessor :build, :field
def self.artifacts_path
- File.expand_path('shared/artifacts/', Rails.root)
+ Gitlab.config.artifacts.path
end
def self.artifacts_upload_path
- File.expand_path('shared/artifacts/tmp/uploads/', Rails.root)
+ File.join(self.artifacts_path, 'tmp/uploads/')
end
def self.artifacts_cache_path
- File.expand_path('shared/artifacts/tmp/cache/', Rails.root)
+ File.join(self.artifacts_path, 'tmp/cache/')
end
def initialize(build, field)
diff --git a/app/validators/color_validator.rb b/app/validators/color_validator.rb
new file mode 100644
index 00000000000..571d0007aa2
--- /dev/null
+++ b/app/validators/color_validator.rb
@@ -0,0 +1,20 @@
+# ColorValidator
+#
+# Custom validator for web color codes. It requires the leading hash symbol and
+# will accept RGB triplet or hexadecimal formats.
+#
+# Example:
+#
+# class User < ActiveRecord::Base
+# validates :background_color, allow_blank: true, color: true
+# end
+#
+class ColorValidator < ActiveModel::EachValidator
+ PATTERN = /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/.freeze
+
+ def validate_each(record, attribute, value)
+ unless value =~ PATTERN
+ record.errors.add(attribute, "must be a valid color code")
+ end
+ end
+end
diff --git a/app/validators/email_validator.rb b/app/validators/email_validator.rb
new file mode 100644
index 00000000000..b35af100803
--- /dev/null
+++ b/app/validators/email_validator.rb
@@ -0,0 +1,18 @@
+# EmailValidator
+#
+# Based on https://github.com/balexand/email_validator
+#
+# Extended to use only strict mode with following allowed characters:
+# ' - apostrophe
+#
+# See http://www.remote.org/jochen/mail/info/chars.html
+#
+class EmailValidator < ActiveModel::EachValidator
+ PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
+
+ def validate_each(record, attribute, value)
+ unless value =~ PATTERN
+ record.errors.add(attribute, options[:message] || :invalid)
+ end
+ end
+end
diff --git a/app/validators/line_code_validator.rb b/app/validators/line_code_validator.rb
new file mode 100644
index 00000000000..ed29e5aeb67
--- /dev/null
+++ b/app/validators/line_code_validator.rb
@@ -0,0 +1,12 @@
+# LineCodeValidator
+#
+# Custom validator for GitLab line codes.
+class LineCodeValidator < ActiveModel::EachValidator
+ PATTERN = /\A[a-z0-9]+_\d+_\d+\z/.freeze
+
+ def validate_each(record, attribute, value)
+ unless value =~ PATTERN
+ record.errors.add(attribute, "must be a valid line code")
+ end
+ end
+end
diff --git a/app/validators/namespace_name_validator.rb b/app/validators/namespace_name_validator.rb
new file mode 100644
index 00000000000..2e51af2982d
--- /dev/null
+++ b/app/validators/namespace_name_validator.rb
@@ -0,0 +1,10 @@
+# NamespaceNameValidator
+#
+# Custom validator for GitLab namespace name strings.
+class NamespaceNameValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ unless value =~ Gitlab::Regex.namespace_name_regex
+ record.errors.add(attribute, Gitlab::Regex.namespace_name_regex_message)
+ end
+ end
+end
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
new file mode 100644
index 00000000000..10e35ce665a
--- /dev/null
+++ b/app/validators/namespace_validator.rb
@@ -0,0 +1,50 @@
+# NamespaceValidator
+#
+# Custom validator for GitLab namespace values.
+#
+# Values are checked for formatting and exclusion from a list of reserved path
+# names.
+class NamespaceValidator < ActiveModel::EachValidator
+ RESERVED = %w(
+ admin
+ all
+ assets
+ ci
+ dashboard
+ files
+ groups
+ help
+ hooks
+ issues
+ merge_requests
+ notes
+ profile
+ projects
+ public
+ repository
+ s
+ search
+ services
+ snippets
+ teams
+ u
+ unsubscribes
+ users
+ ).freeze
+
+ def validate_each(record, attribute, value)
+ unless value =~ Gitlab::Regex.namespace_regex
+ record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
+ end
+
+ if reserved?(value)
+ record.errors.add(attribute, "#{value} is a reserved name")
+ end
+ end
+
+ private
+
+ def reserved?(value)
+ RESERVED.include?(value)
+ end
+end
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
new file mode 100644
index 00000000000..2848b9cd33d
--- /dev/null
+++ b/app/validators/url_validator.rb
@@ -0,0 +1,36 @@
+# UrlValidator
+#
+# Custom validator for URLs.
+#
+# By default, only URLs for the HTTP(S) protocols will be considered valid.
+# Provide a `:protocols` option to configure accepted protocols.
+#
+# Example:
+#
+# class User < ActiveRecord::Base
+# validates :personal_url, url: true
+#
+# validates :ftp_url, url: { protocols: %w(ftp) }
+#
+# validates :git_url, url: { protocols: %w(http https ssh git) }
+# end
+#
+class UrlValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ unless valid_url?(value)
+ record.errors.add(attribute, "must be a valid URL")
+ end
+ end
+
+ private
+
+ def default_options
+ @default_options ||= { protocols: %w(http https) }
+ end
+
+ def valid_url?(value)
+ options = default_options.merge(self.options)
+
+ value =~ /\A#{URI.regexp(options[:protocols])}\z/
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index ddaf0e0e8ff..6c355366948 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -14,11 +14,11 @@
.form-group.project-visibility-level-holder
= f.label :default_project_visibility, class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: 'Project')
+ = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project)
.form-group.project-visibility-level-holder
= f.label :default_snippet_visibility, class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: 'Snippet')
+ = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: PersonalSnippet)
.form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
new file mode 100644
index 00000000000..6936e614346
--- /dev/null
+++ b/app/views/admin/builds/_build.html.haml
@@ -0,0 +1,73 @@
+- project = build.project
+%tr.build
+ %td.status
+ = ci_status_with_icon(build.status)
+
+ %td.build-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
+ - if project
+ = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project), class: "monospace"
+
+ %td
+ = link_to build.short_sha, namespace_project_commit_path(project.namespace, project, build.sha), class: "monospace"
+
+ %td
+ - if build.ref
+ = link_to build.ref, namespace_project_commits_path(project.namespace, project, build.ref)
+ - else
+ .light none
+
+ %td
+ - if build.try(:runner)
+ = runner_link(build.runner)
+ - else
+ .light none
+
+ %td
+ #{build.stage} / #{build.name}
+
+ .pull-right
+ - if build.tags.any?
+ - build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if build.try(:trigger_request)
+ %span.label.label-info triggered
+ - if build.try(: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_with_tooltip(build.finished_at)}
+
+ - if defined?(coverage) && coverage
+ %td.coverage
+ - if build.try(:coverage)
+ #{build.coverage}%
+
+ %td
+ .pull-right
+ - if current_user && can?(current_user, :download_build_artifacts, project) && build.download_url
+ = link_to build.download_url, title: 'Download artifacts' do
+ %i.fa.fa-download
+ - if current_user && can?(current_user, :manage_builds, build.project)
+ - if build.active?
+ - if build.cancel_url
+ = link_to build.cancel_url, method: :post, title: 'Cancel' do
+ %i.fa.fa-remove.cred
+ - elsif defined?(allow_retry) && allow_retry && build.retry_url
+ = link_to build.retry_url, method: :post, title: 'Retry' do
+ %i.fa.fa-repeat
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
new file mode 100644
index 00000000000..55da06a7fe9
--- /dev/null
+++ b/app/views/admin/builds/index.html.haml
@@ -0,0 +1,50 @@
+.project-issuable-filter
+ .controls
+ .pull-left.hidden-xs
+ - if @all_builds.running_or_pending.any?
+ = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+
+ %ul.center-top-menu
+ %li{class: ('active' if @scope.nil?)}
+ = link_to admin_builds_path do
+ Running
+ %span.badge.js-running-count= @all_builds.running_or_pending.count(:id)
+
+ %li{class: ('active' if @scope == 'finished')}
+ = link_to admin_builds_path(scope: :finished) do
+ Finished
+ %span.badge.js-running-count= @all_builds.finished.count(:id)
+
+ %li{class: ('active' if @scope == 'all')}
+ = link_to admin_builds_path(scope: :all) do
+ All
+ %span.badge.js-totalbuilds-count= @all_builds.count(:id)
+
+.gray-content-block
+ #{(@scope || 'running').capitalize} builds
+
+%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 Project
+ %th Commit
+ %th Ref
+ %th Runner
+ %th Name
+ %th Duration
+ %th Finished at
+ %th
+
+ - @builds.each do |build|
+ = render "admin/builds/build", build: build
+
+ = paginate @builds, theme: 'gitlab'
+
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index a5ace4e7a3b..eaa94ed9e36 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -12,7 +12,7 @@
.col-sm-10
= f.text_field :title, class: "form-control", required: true
.form-group
- = f.label :color, "Background Color", class: 'control-label'
+ = f.label :color, "Background color", class: 'control-label'
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index 596e06243dd..e3ccbf6c3a8 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -2,4 +2,4 @@
= render_colored_label(label)
.pull-right
= link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
- = link_to 'Remove', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+ = link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"}
diff --git a/app/views/admin/labels/edit.html.haml b/app/views/admin/labels/edit.html.haml
index 45c62a76259..309aedceded 100644
--- a/app/views/admin/labels/edit.html.haml
+++ b/app/views/admin/labels/edit.html.haml
@@ -1,9 +1,5 @@
- page_title "Edit", @label.name, "Labels"
-%h3
- Edit label
- %span.light #{@label.name}
-.back-link
- = link_to admin_labels_path do
- &larr; To labels list
+%h3.page-title
+ Edit Label
%hr
= render 'form'
diff --git a/app/views/admin/labels/new.html.haml b/app/views/admin/labels/new.html.haml
index 8d298ad20f7..0135ad0723d 100644
--- a/app/views/admin/labels/new.html.haml
+++ b/app/views/admin/labels/new.html.haml
@@ -1,7 +1,5 @@
- page_title "New Label"
-%h3 New label
-.back-link
- = link_to admin_labels_path do
- &larr; To labels list
+%h3.page-title
+ New Label
%hr
= render 'form'
diff --git a/app/views/ci/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index 701782d26bb..6745e58deca 100644
--- a/app/views/ci/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -8,14 +8,14 @@
%span.label.label-danger paused
%td
- = link_to ci_admin_runner_path(runner) do
+ = link_to admin_runner_path(runner) do
= runner.short_sha
%td
.runner-description
= runner.description
%span (#{link_to 'edit', '#', class: 'edit-runner-link'})
.runner-description-form.hide
- = form_for [:ci, :admin, runner], remote: true, html: { class: 'form-inline' } do |f|
+ = form_for [:admin, runner], remote: true, html: { class: 'form-inline' } do |f|
.form-group
= f.text_field :description, class: 'form-control'
= f.submit 'Save', class: 'btn'
@@ -38,11 +38,11 @@
Never
%td
.pull-right
- = link_to 'Edit', ci_admin_runner_path(runner), class: 'btn btn-sm'
+ = link_to 'Edit', admin_runner_path(runner), class: 'btn btn-sm'
&nbsp;
- if runner.active?
- = link_to 'Pause', [:pause, :ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm'
+ = link_to 'Pause', [:pause, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm'
- else
- = link_to 'Resume', [:resume, :ci, :admin, runner], method: :get, class: 'btn btn-success btn-sm'
- = link_to 'Remove', [:ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+ = link_to 'Resume', [:resume, :admin, runner], method: :get, class: 'btn btn-success btn-sm'
+ = link_to 'Remove', [:admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
diff --git a/app/views/ci/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index bacaccfbffa..c5fb3c95506 100644
--- a/app/views/ci/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,6 +1,20 @@
%p.lead
- %span To register a new runner you should enter the following registration token. With this token the runner will request a unique runner token and use that for future communication.
- %code #{GitlabCi::REGISTRATION_TOKEN}
+ %span
+ To register a new runner you should enter the following registration token.
+ With this token the runner will request a unique runner token and use that for future communication.
+ Registration token is
+ %code{ id: 'runners-token' } #{current_application_settings.ensure_runners_registration_token}
+
+.bs-callout.clearfix
+ .pull-left
+ %p
+ You can reset runners registration token by pressing a button below.
+ %p
+ = button_to reset_runners_token_admin_application_settings_path,
+ method: :put, class: 'btn btn-default',
+ data: { confirm: 'Are you sure you want to reset registration token?' } do
+ = icon('refresh')
+ Reset runners registration token
.bs-callout
%p
@@ -25,7 +39,7 @@
.append-bottom-20.clearfix
.pull-left
- = form_tag ci_admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
+ = form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
.form-group
= search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token', spellcheck: false
= submit_tag 'Search', class: 'btn'
@@ -49,5 +63,5 @@
%th
- @runners.each do |runner|
- = render "ci/admin/runners/runner", runner: runner
+ = render "admin/runners/runner", runner: runner
= paginate @runners
diff --git a/app/views/ci/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 1498db46a80..8700b4820cd 100644
--- a/app/views/ci/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -22,7 +22,7 @@
%h4 This runner will process builds only from ASSIGNED projects
%p You can't make this a shared runner.
%hr
-= form_for @runner, url: ci_admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
+= form_for @runner, url: admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
.form-group
= label_tag :token, class: 'control-label' do
Token
@@ -37,7 +37,7 @@
= label_tag :tag_list, class: 'control-label' do
Tags
.col-sm-10
- = f.text_field :tag_list, class: 'form-control'
+ = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
.help-block You can setup builds to only use runners with specific tags
.form-actions
= f.submit 'Save', class: 'btn btn-save'
@@ -53,29 +53,24 @@
%th
- @runner.runner_projects.each do |runner_project|
- project = runner_project.project
- - if project.gl_project
+ - if project
%tr.alert-info
%td
%strong
- = project.name
+ = project.name_with_namespace
%td
.pull-right
- = link_to 'Disable', [:ci, :admin, project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
+ = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
%table.table
%thead
%tr
%th Project
%th
- .pull-right
- = link_to 'Assign to all', assign_all_ci_admin_runner_path(@runner),
- class: 'btn btn-sm assign-all-runner',
- title: 'Assign runner to all projects',
- method: :put
%tr
%td
- = form_tag ci_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
+ = form_tag admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
.form-group
= search_field_tag :search, params[:search], class: 'form-control', spellcheck: false
= submit_tag 'Search', class: 'btn'
@@ -84,44 +79,44 @@
- @projects.each do |project|
%tr
%td
- = project.name
+ = project.name_with_namespace
%td
.pull-right
- = form_for [:ci, :admin, project, project.runner_projects.new] do |f|
+ = form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f|
= f.hidden_field :runner_id, value: @runner.id
= f.submit 'Enable', class: 'btn btn-xs'
= paginate @projects
.col-md-6
%h4 Recent builds served by this runner
- %table.builds.runner-builds
+ %table.table.builds.runner-builds
%thead
%tr
- %th Build ID
+ %th Build
%th Status
%th Project
%th Commit
%th Finished at
- @builds.each do |build|
- - gl_project = build.gl_project
+ - project = build.project
%tr.build
%td.id
- - if gl_project
- = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
- = build.id
+ - if project
+ = link_to namespace_project_build_path(project.namespace, project, build) do
+ %strong ##{build.id}
- else
- = build.id
+ %strong ##{build.id}
%td.status
= ci_status_with_icon(build.status)
%td.status
- - if gl_project
- = gl_project.name_with_namespace
+ - if project
+ = project.name_with_namespace
%td.build-link
- - if gl_project
+ - if project
= link_to ci_status_path(build.commit) do
%strong #{build.commit.short_sha}
diff --git a/app/views/ci/admin/runners/update.js.haml b/app/views/admin/runners/update.js.haml
index 2b7d3067e20..2b7d3067e20 100644
--- a/app/views/ci/admin/runners/update.js.haml
+++ b/app/views/admin/runners/update.js.haml
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index 8d1cab4137c..5e17b018163 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -6,7 +6,7 @@
%span.cred (Admin)
.pull-right
- - unless @user == current_user
+ - unless @user == current_user || @user.blocked?
= link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info"
= link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
diff --git a/app/views/admin/users/_projects.html.haml b/app/views/admin/users/_projects.html.haml
new file mode 100644
index 00000000000..a126a858ea8
--- /dev/null
+++ b/app/views/admin/users/_projects.html.haml
@@ -0,0 +1,13 @@
+- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
+ .panel.panel-default.contributed-projects
+ .panel-heading Projects contributed to
+ = render 'shared/projects/list',
+ projects: contributed_projects.sort_by(&:star_count).reverse,
+ projects_limit: 5, stars: true, avatar: false
+
+- if local_assigns.has_key?(:projects) && projects.present?
+ .panel.panel-default
+ .panel-heading Personal projects
+ = render 'shared/projects/list',
+ projects: projects.sort_by(&:star_count).reverse,
+ projects_limit: 10, stars: true, avatar: false
diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml
index a8837d74dd9..3b6fd71500d 100644
--- a/app/views/admin/users/edit.html.haml
+++ b/app/views/admin/users/edit.html.haml
@@ -1,8 +1,5 @@
- page_title "Edit", @user.name, "Users"
%h3.page-title
Edit user: #{@user.name}
-.back-link
- = link_to admin_user_path(@user) do
- &larr; Back to user page
%hr
= render 'form'
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index 0d7a1a25a80..b655b2a15f5 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -14,7 +14,7 @@
.row
.col-md-6
- if @personal_projects.present?
- = render 'users/projects', projects: @personal_projects
+ = render 'admin/users/projects', projects: @personal_projects
- else
.nothing-here-block This user has no personal projects.
diff --git a/app/views/ci/admin/application_settings/_form.html.haml b/app/views/ci/admin/application_settings/_form.html.haml
deleted file mode 100644
index 634c9daa477..00000000000
--- a/app/views/ci/admin/application_settings/_form.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-= form_for @application_setting, url: ci_admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
- - if @application_setting.errors.any?
- #error_explanation
- .alert.alert-danger
- - @application_setting.errors.full_messages.each do |msg|
- %p= msg
-
- %fieldset
- %legend Default Project Settings
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :all_broken_builds do
- = f.check_box :all_broken_builds
- Send emails only on broken builds
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :add_pusher do
- = f.check_box :add_pusher
- Add pusher to recipients list
-
- .form-actions
- = f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/ci/admin/application_settings/show.html.haml b/app/views/ci/admin/application_settings/show.html.haml
deleted file mode 100644
index 7ef0aa89ed6..00000000000
--- a/app/views/ci/admin/application_settings/show.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-%h3.page-title Settings
-%hr
-= render 'form'
diff --git a/app/views/ci/admin/builds/_build.html.haml b/app/views/ci/admin/builds/_build.html.haml
deleted file mode 100644
index 2df58713214..00000000000
--- a/app/views/ci/admin/builds/_build.html.haml
+++ /dev/null
@@ -1,34 +0,0 @@
-- gl_project = build.project.gl_project
-- if build.commit && build.project
- %tr.build
- %td.build-link
- = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
- %strong #{build.id}
-
- %td.status
- = ci_status_with_icon(build.status)
-
- %td.commit-link
- = link_to ci_status_path(build.commit) do
- %strong #{build.commit.short_sha}
-
- %td.runner
- - if build.runner
- = link_to build.runner.id, ci_admin_runner_path(build.runner)
-
- %td.build-project
- = truncate build.project.name, length: 30
-
- %td.build-message
- %span= truncate(build.commit.git_commit_message, length: 30)
-
- %td.build-branch
- %span= truncate(build.ref, length: 25)
-
- %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
diff --git a/app/views/ci/admin/builds/index.html.haml b/app/views/ci/admin/builds/index.html.haml
deleted file mode 100644
index d23119162cc..00000000000
--- a/app/views/ci/admin/builds/index.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-%ul.nav.nav-tabs.append-bottom-20
- %li{class: ("active" if @scope.nil?)}
- = link_to 'All builds', ci_admin_builds_path
-
- %li{class: ("active" if @scope == "pending")}
- = link_to "Pending", ci_admin_builds_path(scope: :pending)
-
- %li{class: ("active" if @scope == "running")}
- = link_to "Running", ci_admin_builds_path(scope: :running)
-
-
-%table.builds
- %thead
- %tr
- %th Build
- %th Status
- %th Commit
- %th Runner
- %th Project
- %th Message
- %th Branch
- %th Duration
- %th Finished at
-
- - @builds.each do |build|
- = render "ci/admin/builds/build", build: build
-
-= paginate @builds
diff --git a/app/views/ci/admin/events/index.html.haml b/app/views/ci/admin/events/index.html.haml
deleted file mode 100644
index 5a5b4dc7c35..00000000000
--- a/app/views/ci/admin/events/index.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-.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
diff --git a/app/views/ci/admin/projects/_project.html.haml b/app/views/ci/admin/projects/_project.html.haml
deleted file mode 100644
index a342d6e1cf0..00000000000
--- a/app/views/ci/admin/projects/_project.html.haml
+++ /dev/null
@@ -1,29 +0,0 @@
-- last_commit = project.commits.last
-%tr
- %td
- = project.id
- %td
- = link_to [:ci, project] do
- %strong= project.name
- %td
- - if last_commit
- = ci_status_with_icon(last_commit.status)
- - if project.last_commit_date
- &middot;
- = time_ago_in_words project.last_commit_date
- ago
- - else
- No builds yet
- %td
- - if project.public
- %i.fa.fa-globe
- Public
- - else
- %i.fa.fa-lock
- Private
- %td
- = project.commits.count
- %td
- = link_to [:ci, :admin, project], method: :delete, class: 'btn btn-danger btn-sm' do
- %i.fa.fa-remove
- Remove
diff --git a/app/views/ci/admin/projects/index.html.haml b/app/views/ci/admin/projects/index.html.haml
deleted file mode 100644
index 0da8547924b..00000000000
--- a/app/views/ci/admin/projects/index.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-.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
-
-= paginate @projects
-
diff --git a/app/views/ci/admin/runner_projects/index.html.haml b/app/views/ci/admin/runner_projects/index.html.haml
deleted file mode 100644
index 6b4e3b2cb38..00000000000
--- a/app/views/ci/admin/runner_projects/index.html.haml
+++ /dev/null
@@ -1,57 +0,0 @@
-%p.lead
- To register a new runner visit #{link_to 'this page ', ci_runners_path}
-
-.row
- .col-md-8
- %h5 Activated:
- %table.table
- %tr
- %th Runner ID
- %th Runner Description
- %th Last build
- %th Builds Stats
- %th Registered
- %th
-
- - @runner_projects.each do |runner_project|
- - runner = runner_project.runner
- - builds = runner.builds.where(project_id: @project.id)
- %tr
- %td
- %span.badge.badge-info= runner.id
- %td
- = runner.display_name
- %td
- - last_build = builds.last
- - if last_build
- = link_to last_build.short_sha, [last_build.project, last_build]
- - else
- unknown
- %td
- %span.badge.badge-success
- #{builds.success.count}
- %span /
- %span.badge.badge-important
- #{builds.failed.count}
- %td
- #{time_ago_in_words(runner_project.created_at)} ago
- %td
- = link_to 'Disable', [:ci, @project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm right'
- .col-md-4
- %h5 Available
- %table.table
- %tr
- %th ID
- %th Token
- %th
-
- - (Ci::Runner.all - @project.runners).each do |runner|
- %tr
- %td
- = runner.id
- %td
- = runner.token
- %td
- = form_for [:ci, @project, @runner_project] do |f|
- = f.hidden_field :runner_id, value: runner.id
- = f.submit 'Add', class: 'btn btn-sm'
diff --git a/app/views/ci/commits/_commit.html.haml b/app/views/ci/commits/_commit.html.haml
index b24a3b826cf..11163813f3e 100644
--- a/app/views/ci/commits/_commit.html.haml
+++ b/app/views/ci/commits/_commit.html.haml
@@ -27,7 +27,6 @@
- if commit.finished_at
%span #{time_ago_in_words commit.finished_at} ago
- - if commit.project.coverage_enabled?
+ - if commit.coverage
%td.coverage
- - if commit.coverage
- #{commit.coverage}%
+ #{commit.coverage}%
diff --git a/app/views/ci/shared/_guide.html.haml b/app/views/ci/shared/_guide.html.haml
index db2d7f2f4b6..09e7e653521 100644
--- a/app/views/ci/shared/_guide.html.haml
+++ b/app/views/ci/shared/_guide.html.haml
@@ -4,12 +4,10 @@
%ol
%li
Add at least one runner to the project.
- Go to #{link_to 'Runners page', runners_path(@project.gl_project), target: :blank} for instructions.
+ Go to #{link_to 'Runners page', runners_path(@project), target: :blank} for instructions.
%li
- Put the .gitlab-ci.yml in the root of your repository. Examples can be found in #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
+ Put the .gitlab-ci.yml in the root of your repository. Examples can be found in
+ #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
%li
- Visit #{link_to 'GitLab project settings', @project.gitlab_url + "/services/gitlab_ci/edit", target: :blank}
- and press the "Test settings" button.
- %li
Return to this page and refresh it, it should show a new build.
diff --git a/app/views/ci/user_sessions/new.html.haml b/app/views/ci/user_sessions/new.html.haml
deleted file mode 100644
index b8d9a1d7089..00000000000
--- a/app/views/ci/user_sessions/new.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-.login-block
- %h2 Login using GitLab account
- %p.light
- Make sure you have an account on the GitLab server
- = link_to GitlabCi.config.gitlab_server.url, GitlabCi.config.gitlab_server.url, no_turbolink
- %hr
- = link_to "Login with GitLab", auth_ci_user_sessions_path(state: params[:state]), no_turbolink.merge( class: 'btn btn-login btn-success' )
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index ed480b8caf8..2e77afb7525 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,5 +1,8 @@
+= content_for :flash_message do
+ = render 'shared/project_limit'
+
%ul.center-top-menu
- = nav_link(path: ['projects#index', 'root#index']) do
+ = nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects
= nav_link(page: starred_dashboard_projects_path) do
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index f3f3f58111e..d5b7e729e7b 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -8,8 +8,8 @@
= link_to new_group_path, class: "btn btn-new" do
%i.fa.fa-plus
New Group
- .title Welcome to the groups!
- Group members have access to all group projects.
+ .oneline
+ Group members have access to all group projects.
%ul.content-list
- @group_members.each do |group_member|
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index cd602e897b7..2d3da01178a 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,14 +4,20 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
+.project-issuable-filter
+ .controls
+ .pull-left
+ - if current_user
+ .hidden-xs.pull-left
+ = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
+ %i.fa.fa-rss
-.append-bottom-20
- .pull-right
- - if current_user
- .hidden-xs.pull-left.prepend-top-20
- = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: '' do
- %i.fa.fa-rss
+ = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
-= render 'shared/issues'
+.gray-content-block.second-block
+ List all issues from all projects you have access to.
+
+.prepend-top-default
+ = render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index d1f332fa0d3..c5a5ec21f78 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,6 +1,14 @@
- page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
-.append-bottom-20
+.project-issuable-filter
+ .controls
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
+
= render 'shared/issuable/filter', type: :merge_requests
-= render 'shared/merge_requests'
+
+.gray-content-block.second-block
+ List all merge requests from all projects you have access to.
+
+.prepend-top-default
+ = render 'shared/merge_requests'
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 55080d6b3fe..7c882a32702 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -16,7 +16,10 @@
= milestone_progress_bar(milestone)
.row
.col-sm-6
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name_with_namespace
+ .expiration
+ = render 'shared/milestone_expired', milestone: milestone
+ .projects
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = milestone.project.name_with_namespace
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 635251e2374..bec1692a4de 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,12 +1,14 @@
- page_title "Milestones"
-- header_title "Milestones", dashboard_milestones_path
+- header_title "Milestones", dashboard_milestones_path
+.project-issuable-filter
+ .controls
+ = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
-= render 'shared/milestones_filter'
+ = render 'shared/milestones_filter'
.gray-content-block
- .oneline
- List all milestones from all projects you have access to.
+ List all milestones from all projects you have access to.
.milestones
%ul.content-list
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 83077a398bd..4316c358dcb 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -1,19 +1,23 @@
- page_title @milestone.title, "Milestones"
-%h4.page-title
- .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
+- header_title "Milestones", dashboard_milestones_path
+
+.detail-page-header
+ .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @milestone.closed?
Closed
- else
Open
- Milestone #{@milestone.title}
+ %span.identifier
+ Milestone #{@milestone.title}
+
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@milestone.title), pipeline: :single_line
-%hr
- if @milestone.complete? && @milestone.active?
- .alert.alert-success
+ .alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
-.description
-
.table-holder
%table.table
%thead
@@ -44,7 +48,7 @@
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
-%ul.nav.nav-tabs
+%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
@@ -58,25 +62,39 @@
Participants
%span.badge= @milestone.participants.count
- .pull-right
- = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
-
.tab-content
.tab-pane.active#tab-issues
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All issues in this milestone
+
+ .row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ = link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All merge requests in this milestone
+
+ .row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
+ .gray-content-block.middle-block
+ .oneline
+ All participants to this milestone
%ul.bordered-list
- @milestone.participants.each do |user|
%li
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 7a16b811f6b..53abf274bdb 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -3,7 +3,7 @@
= auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity")
- page_title "Projects"
-- header_title "Projects", root_path
+- header_title "Projects", dashboard_projects_path
= render 'dashboard/projects_head'
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index f75f2e0a32a..70705923d42 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -1,5 +1,5 @@
- page_title "Starred Projects"
-- header_title "Projects", projects_path
+- header_title "Projects", dashboard_projects_path
= render 'dashboard/projects_head'
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index ad63841ccf3..4ba8b84fd92 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -2,4 +2,4 @@
.commit-row-title
= link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''
&middot;
- = gfm event_commit_title(commit[:message]), project: project
+ = markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 2761272aa8a..28b12c8dca8 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -3,7 +3,7 @@
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
.form-group
- = button_tag 'Search', class: "btn btn-success"
+ = button_tag 'Search', class: "btn"
.pull-right.hidden-sm.hidden-xs
- if current_user
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index 67e38ca3127..76bdd68fd76 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -1,5 +1,5 @@
- page_title "Projects"
-- header_title "Projects", root_path
+- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index 596cb0a96cd..e30c3633223 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -1,5 +1,5 @@
- page_title "Projects"
-- header_title "Projects", root_path
+- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 5ea6d81c5b9..1412b19acde 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -1,5 +1,5 @@
- page_title "Projects"
-- header_title "Projects", root_path
+- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 57308a661c0..8daac585960 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -3,8 +3,7 @@
.panel.panel-default
.panel-heading
- %strong= @group.name
- group settings:
+ Group settings
.panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- if @group.errors.any?
@@ -45,4 +44,5 @@
%br
%strong Removed group can not be restored!
- = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
+ .form-actions
+ = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index be94b1abc11..a79a0fcdc8e 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -4,7 +4,7 @@
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)}
- if member.user
- = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 24), class: "avatar s24", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
@@ -14,7 +14,7 @@
%label.label.label-danger
%strong Blocked
- else
- = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: ''
%strong
= member.invite_email
%span.cgray
@@ -30,7 +30,7 @@
- if should_user_see_group_roles?(current_user, @group)
%span.pull-right
- %strong= member.human_access
+ %strong.member-access-level= member.human_access
- if show_controls
- if can?(current_user, :update_group_member, member)
= button_tag class: "btn-xs btn js-toggle-button",
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index d4ad33a8bf1..335bf036074 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,38 +1,35 @@
- page_title "Members"
- header_title group_title(@group, "Members", group_group_members_path(@group))
-- if should_user_see_group_roles?(current_user, @group)
- %p.light
- Members of group have access to all group projects.
- Read more about permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-
-
-.clearfix.js-toggle-container
- = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
- .form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
- = button_tag 'Search', class: 'btn'
+- @blank_container = true
+.group-members-page
- if current_user && current_user.can?(:admin_group_member, @group)
- .pull-right
- = button_tag class: 'btn btn-new js-toggle-button', type: 'button' do
- Add members
- %i.fa.fa-chevron-down
-
- .js-toggle-content.hide.new-group-member-holder
- = render "new_group_member"
-
-.panel.panel-default.prepend-top-20
- .panel-heading
- %strong #{@group.name}
- group members
- %small
- (#{@members.total_count})
- %ul.well-list
- - @members.each do |member|
- = render 'groups/group_members/group_member', member: member, show_controls: true
+ .panel.panel-default
+ .panel-heading
+ Add new user to group
+ .panel-body
+ - if should_user_see_group_roles?(current_user, @group)
+ %p.light
+ Members of group have access to all group projects.
+ .new-group-member-holder
+ = render "new_group_member"
-= paginate @members, theme: 'gitlab'
+ .panel.panel-default
+ .panel-heading
+ %strong #{@group.name}
+ group members
+ %small
+ (#{@members.total_count})
+ .pull-right
+ = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
+ = button_tag class: 'btn', title: 'Search' do
+ = icon("search")
+ %ul.content-list
+ - @members.each do |member|
+ = render 'groups/group_members/group_member', member: member, show_controls: true
+ = paginate @members, theme: 'gitlab'
:javascript
$('form.member-search-form').on('submit', function(event) {
diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml
index 5bad48abafd..df726e2b2b9 100644
--- a/app/views/groups/group_members/update.js.haml
+++ b/app/views/groups/group_members/update.js.haml
@@ -1,2 +1,2 @@
:plain
- $("##{dom_id(@member)}").replaceWith('#{escape_javascript(render(@member, member: @member, show_controls: true))}');
+ $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member, show_controls: true))}');
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 08d97e418a3..90ade1e1680 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -4,21 +4,24 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
+.project-issuable-filter
+ .controls
+ .pull-left
+ - if current_user
+ .hidden-xs.pull-left
+ = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
+ %i.fa.fa-rss
+ = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
+
+ = render 'shared/issuable/filter', type: :issues
-= render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
- .pull-right
- - if current_user
- .hidden-xs.pull-left
- = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token) do
- %i.fa.fa-rss
- %div
- Only issues from
- %strong #{@group.name}
- group are listed here.
- - if current_user
- To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
+ Only issues from
+ %strong #{@group.name}
+ group are listed here.
+ - if current_user
+ To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
.prepend-top-default
= render 'shared/issues'
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 425ad8331bf..f662f5a8c17 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,13 +1,18 @@
- page_title "Merge Requests"
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
-= render 'shared/issuable/filter', type: :merge_requests
+.project-issuable-filter
+ .controls
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
+
+ = render 'shared/issuable/filter', type: :merge_requests
+
.gray-content-block.second-block
- %div
- Only merge requests from
- %strong #{@group.name}
- group are listed here.
- - if current_user
- To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
+ Only merge requests from
+ %strong #{@group.name}
+ group are listed here.
+ - if current_user
+ To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
+
.prepend-top-default
= render 'shared/merge_requests'
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 84ec77c6188..b221d3a89a4 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,18 +1,22 @@
- page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
-= render 'shared/milestones_filter'
+.project-issuable-filter
+ .controls
+ - if can?(current_user, :admin_milestones, @group)
+ .pull-right
+ %span.pull-right.hidden-xs
+ = link_to new_group_milestone_path(@group), class: "btn btn-new" do
+ = icon('plus')
+ New Milestone
+
+ = render 'shared/milestones_filter'
+
.gray-content-block
- - if can?(current_user, :admin_milestones, @group)
- .pull-right
- %span.pull-right.hidden-xs
- = link_to new_group_milestone_path(@group), class: "btn btn-new" do
- New Milestone
+ Only milestones from
+ %strong #{@group.name}
+ group are listed here.
- .oneline
- Only milestones from
- %strong #{@group.name}
- group are listed here.
.milestones
%ul.content-list
- if @milestones.blank?
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 800bac4ef02..3894a0ece74 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -14,8 +14,7 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
- %p.hint Required
+ = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index d161259e4aa..d063b257b5e 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,27 +1,29 @@
- page_title @milestone.title, "Milestones"
= render "header_title"
-%h4.page-title
- .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
+.detail-page-header
+ .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @milestone.closed?
Closed
- else
Open
- Milestone #{@milestone.title}
+ %span.identifier
+ Milestone #{@milestone.title}
.pull-right
- if can?(current_user, :admin_milestones, @group)
- if @milestone.active?
- = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
+ = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- else
- = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
+ = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@milestone.title), pipeline: :single_line
-%hr
- if @milestone.complete? && @milestone.active?
- .alert.alert-success
+ .alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
-.description
-
.table-holder
%table.table
%thead
@@ -52,7 +54,7 @@
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
-%ul.nav.nav-tabs
+%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
@@ -66,25 +68,40 @@
Participants
%span.badge= @milestone.participants.count
- .pull-right
- = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
-
.tab-content
.tab-pane.active#tab-issues
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All issues in this milestone
+
+ .row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ = link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All merge requests in this milestone
+
+ .row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
+ .gray-content-block.middle-block
+ .oneline
+ All participants to this milestone
+
%ul.bordered-list
- @milestone.participants.each do |user|
%li
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 0665cdf387a..4bc31cabea6 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,5 +1,10 @@
- page_title 'New Group'
-- header_title 'New Group'
+- header_title "Groups", dashboard_groups_path
+
+%h3.page-title
+ New Group
+%hr
+
= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
- if @group.errors.any?
.alert.alert-danger
@@ -18,3 +23,4 @@
.form-actions
= f.submit 'Create group', class: "btn btn-create", tabindex: 3
+ = link_to 'Cancel', dashboard_groups_path, class: 'btn btn-cancel'
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 2169a821fb2..d9ffda884c8 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -31,11 +31,9 @@
%h2#blocks Blocks
- %h3
+ %h4
%code .gray-content-block
-
-
.gray-content-block.middle-block
%h4 Normal block inside content
= lorem
@@ -45,9 +43,28 @@
= lorem
+ %h4
+ %code .cover-block
+ %br
+ .cover-block
+ .avatar-holder
+ = image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
+ .cover-title
+ John Smith
+
+ .cover-desc
+ = lorem
+
+ .cover-controls
+ = link_to '#', class: 'btn btn-gray' do
+ = icon('pencil')
+ &nbsp;
+ = link_to '#', class: 'btn btn-gray' do
+ = icon('rss')
+
%h2#lists Lists
- %h3
+ %h4
%code .content-list
%ul.content-list
%li
@@ -57,7 +74,7 @@
%li
One item
- %h3
+ %h4
%code .well-list
%ul.well-list
%li
@@ -67,7 +84,7 @@
%li
One item
- %h3
+ %h4
%code .panel .well-list
.panel.panel-default
@@ -80,7 +97,7 @@
%li
One item
- %h3
+ %h4
%code .bordered-list
%ul.bordered-list
%li
@@ -121,7 +138,7 @@
%h2#navs Navigation
- %h3
+ %h4
%code .center-top-menu
.example
%ul.center-top-menu
@@ -130,7 +147,7 @@
%li
%a Closed
- %h3
+ %h4
%code .btn-group.btn-group-next
.example
%div.btn-group.btn-group-next
@@ -138,7 +155,7 @@
%a.btn Closed
- %h3
+ %h4
%code .nav.nav-tabs
.example
%ul.nav.nav-tabs
@@ -204,7 +221,7 @@
%h2#forms Forms
- %h3
+ %h4
%code form.horizontal-form
%form.form-horizontal
@@ -226,7 +243,7 @@
.col-sm-offset-2.col-sm-10
%button.btn.btn-default{:type => "submit"} Sign in
- %h3
+ %h4
%code form
%form
@@ -243,7 +260,7 @@
%button.btn.btn-default{:type => "submit"} Sign in
%h2#file File
- %h3
+ %h4
%code .file-holder
- blob = Snippet.new(content: "Wow\nSuch\nFile")
@@ -254,13 +271,12 @@
.file-actions
.btn-group
%a.btn Edit
- %a.btn Remove
+ %a.btn.btn-danger Remove
.file-contenta.code
= render 'shared/file_highlight', blob: blob
-
%h2#markdown Markdown
- %h3
+ %h4
%code .md or .wiki and others
Markdown rendering has a bit different css and presented in next UI elements:
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 1f09a27e2d6..aec2e836c9f 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -1,4 +1,5 @@
- page_title "Bitbucket import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-bitbucket
Import projects from Bitbucket
diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml
index e1bb88ca4ed..5515fad6f48 100644
--- a/app/views/import/fogbugz/new.html.haml
+++ b/app/views/import/fogbugz/new.html.haml
@@ -1,4 +1,5 @@
- page_title "FogBugz Import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-bug
Import projects from FogBugz
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index bc3c90294e3..07338736bac 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -1,4 +1,5 @@
- page_title 'User map', 'FogBugz import'
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-bug
Import projects from FogBugz
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index b902006597b..6ee16c8be4b 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -1,4 +1,5 @@
- page_title "FogBugz import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-bug
Import projects from FogBugz
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 0699321c8c0..1416ee5bd5a 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -1,4 +1,5 @@
- page_title "GitHub import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-github
Import projects from GitHub
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index f4a2b33af21..911a55eb85d 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -1,4 +1,5 @@
- page_title "GitLab.com import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-heart
Import projects from GitLab.com
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index 71752d21efa..6b0fa1edf8c 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -1,4 +1,5 @@
- page_title "Gitorious import"
+- header_title "Projects", root_path
%h3.page-title
%i.icon-gitorious.icon-gitorious-big
Import projects from Gitorious.org
diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml
index 9c64e0a009f..5d2f149cd5f 100644
--- a/app/views/import/google_code/new.html.haml
+++ b/app/views/import/google_code/new.html.haml
@@ -1,4 +1,5 @@
- page_title "Google Code import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
@@ -6,7 +7,7 @@
= form_tag callback_import_google_code_path, class: 'form-horizontal', multipart: true do
%p
- Follow the steps below to export your Google Code project data.
+ Follow the steps below to export your Google Code project data.
In the next step, you'll be able to select the projects you want to import.
%ol
%li
diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml
index e53ebda7dc1..0738b3db1eb 100644
--- a/app/views/import/google_code/new_user_map.html.haml
+++ b/app/views/import/google_code/new_user_map.html.haml
@@ -1,4 +1,5 @@
- page_title "User map", "Google Code import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
@@ -8,31 +9,31 @@
%p
Customize how Google Code email addresses and usernames are imported into GitLab.
In the next step, you'll be able to select the projects you want to import.
- %p
+ %p
The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.
%ul
%li
%strong Default: Directly import the Google Code email address or username
%p
- <code>"johnsmith@example.com": "johnsm...@example.com"</code>
- will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com.
+ <code>"johnsmith@example.com": "johnsm...@example.com"</code>
+ will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com.
The email address or username is masked to ensure the user's privacy.
%li
%strong Map a Google Code user to a GitLab user
%p
- <code>"johnsmith@example.com": "@johnsmith"</code>
- will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com,
+ <code>"johnsmith@example.com": "@johnsmith"</code>
+ will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com,
and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.
%li
%strong Map a Google Code user to a full name
%p
- <code>"johnsmith@example.com": "John Smith"</code>
+ <code>"johnsmith@example.com": "John Smith"</code>
will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.
%li
%strong Map a Google Code user to a full email address
%p
- <code>"johnsmith@example.com": "johnsmith@example.com"</code>
- will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com.
+ <code>"johnsmith@example.com": "johnsmith@example.com"</code>
+ will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com.
By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address.
.form-group
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 8c64fd27e60..175ef6921cd 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -1,4 +1,5 @@
- page_title "Google Code import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 352b8040cf4..ec7cd79bc54 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,8 +1,8 @@
-.page-with-sidebar{ class: nav_sidebar_class }
+.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
- .sidebar-wrapper.nicescroll
+ .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
@@ -17,8 +17,8 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
- = link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = link_to current_user, class: 'sidebar-user', title: "Profile" do
+ = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 1c738719bd8..6591c52bdbd 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,5 +1,5 @@
-- page_title "Admin area"
-- header_title "Admin area", admin_root_path
+- page_title "Admin Area"
+- header_title "Admin Area", admin_root_path
- sidebar "admin"
= render template: "layouts/application"
diff --git a/app/views/layouts/ci/_nav_admin.html.haml b/app/views/layouts/ci/_nav_admin.html.haml
deleted file mode 100644
index dcda04a4638..00000000000
--- a/app/views/layouts/ci/_nav_admin.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to admin_root_path, title: 'Back to admin', data: {placement: 'right'}, class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Back to admin
-
- %li.separate-item
- = nav_link path: 'projects#index' do
- = link_to ci_admin_projects_path do
- = icon('list-alt fw')
- %span
- Projects
- = nav_link path: 'events#index' do
- = link_to ci_admin_events_path do
- = icon('book fw')
- %span
- Events
- = nav_link path: ['runners#index', 'runners#show'] do
- = link_to ci_admin_runners_path do
- = icon('cog fw')
- %span
- Runners
- %span.count= Ci::Runner.count(:all)
- = nav_link path: 'builds#index' do
- = link_to ci_admin_builds_path do
- = icon('link fw')
- %span
- Builds
- %span.count= Ci::Build.count(:all)
- = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
- = link_to ci_admin_application_settings_path do
- = icon('cogs fw')
- %span
- Settings
diff --git a/app/views/layouts/ci/_nav_project.html.haml b/app/views/layouts/ci/_nav_project.html.haml
deleted file mode 100644
index f094edbfa87..00000000000
--- a/app/views/layouts/ci/_nav_project.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to project_path(@project.gl_project), title: 'Back to project', data: {placement: 'right'}, class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Back to project
- %li.separate-item
- = nav_link path: 'events#index' do
- = link_to ci_project_events_path(@project) do
- = icon('book fw')
- %span
- Events
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
index ab3e29c3f42..7e90af21b21 100644
--- a/app/views/layouts/ci/_page.html.haml
+++ b/app/views/layouts/ci/_page.html.haml
@@ -1,8 +1,8 @@
-.page-with-sidebar{ class: nav_sidebar_class }
+.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
- .sidebar-wrapper.nicescroll
+ .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
@@ -14,8 +14,8 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
- = link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = link_to current_user, class: 'sidebar-user', title: "Profile" do
+ = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
diff --git a/app/views/layouts/ci/admin.html.haml b/app/views/layouts/ci/admin.html.haml
deleted file mode 100644
index c8cb185d28c..00000000000
--- a/app/views/layouts/ci/admin.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-!!! 5
-%html{ lang: "en"}
- = render 'layouts/head'
- %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
- - header_title = "Admin area"
- - if current_user
- = render "layouts/header/default", title: header_title
- - else
- = render "layouts/header/public", title: header_title
-
- = render 'layouts/ci/page', sidebar: 'nav_admin'
diff --git a/app/views/layouts/ci/application.html.haml b/app/views/layouts/ci/application.html.haml
deleted file mode 100644
index 38023468d0b..00000000000
--- a/app/views/layouts/ci/application.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-!!! 5
-%html{ lang: "en"}
- = render 'layouts/head'
- %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
- - header_title = "Continuous Integration"
- - if current_user
- = render "layouts/header/default", title: header_title
- - else
- = render "layouts/header/public", title: header_title
-
- = render 'layouts/ci/page'
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 95e077c339f..f08cb0a5428 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,13 +1,13 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body.ui_charcoal.login-page.application
+ %body.ui_charcoal.login-page.application.navless
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
.content
= render "layouts/flash"
- .row.prepend-top-20
+ .row
.col-sm-5.pull-right
= yield
.col-sm-7.brand-holder.pull-left
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 2af265a2296..915acc4612e 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body{class: "#{user_application_theme} application"}
+ %body{class: "#{user_application_theme} application navless"}
= render "layouts/header/empty"
.container.navless-container
= render "layouts/flash"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3ca30d3baab..3892ef8eefa 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -11,27 +11,27 @@
%li.hidden-sm.hidden-xs
= render 'layouts/search'
%li.visible-sm.visible-xs
- = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if session[:impersonator_id]
%li.impersonation
- = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop impersonation', data: { toggle: 'tooltip', placement: 'bottom' } do
+ = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
- = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
- if current_user.can_create_project?
%li
- = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('plus fw')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
- data: {toggle: 'tooltip', placement: 'bottom'} do
+ data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
%li
- = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('sign-out')
%h1.title= title
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 2079feeeab6..c60ac5eefac 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -5,78 +5,85 @@
%span
Overview
= nav_link(controller: [:admin, :projects]) do
- = link_to admin_namespaces_projects_path, title: 'Projects', data: {placement: 'right'} do
+ = link_to admin_namespaces_projects_path, title: 'Projects' do
= icon('cube fw')
%span
Projects
= nav_link(controller: :users) do
- = link_to admin_users_path, title: 'Users', data: {placement: 'right'} do
+ = link_to admin_users_path, title: 'Users' do
= icon('user fw')
%span
Users
= nav_link(controller: :groups) do
- = link_to admin_groups_path, title: 'Groups', data: {placement: 'right'} do
+ = link_to admin_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :deploy_keys) do
- = link_to admin_deploy_keys_path, title: 'Deploy Keys', data: {placement: 'right'} do
+ = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
= icon('key fw')
%span
Deploy Keys
- = nav_link do
- = link_to ci_admin_projects_path, title: 'Continuous Integration', data: {placement: 'right'} do
- = icon('building fw')
+ = nav_link path: ['runners#index', 'runners#show'] do
+ = link_to admin_runners_path do
+ = icon('cog fw')
+ %span
+ Runners
+ %span.count= Ci::Runner.count(:all)
+ = nav_link path: 'builds#index' do
+ = link_to admin_builds_path do
+ = icon('link fw')
%span
- Continuous Integration
+ Builds
+ %span.count= Ci::Build.count(:all)
= nav_link(controller: :logs) do
- = link_to admin_logs_path, title: 'Logs', data: {placement: 'right'} do
+ = link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw')
%span
Logs
= nav_link(controller: :broadcast_messages) do
- = link_to admin_broadcast_messages_path, title: 'Broadcast Messages', data: {placement: 'right'} do
+ = link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw')
%span
Messages
= nav_link(controller: :hooks) do
- = link_to admin_hooks_path, title: 'Hooks', data: {placement: 'right'} do
+ = link_to admin_hooks_path, title: 'Hooks' do
= icon('external-link fw')
%span
Hooks
= nav_link(controller: :background_jobs) do
- = link_to admin_background_jobs_path, title: 'Background Jobs', data: {placement: 'right'} do
+ = link_to admin_background_jobs_path, title: 'Background Jobs' do
= icon('cog fw')
%span
Background Jobs
= nav_link(controller: :applications) do
- = link_to admin_applications_path, title: 'Applications', data: {placement: 'right'} do
+ = link_to admin_applications_path, title: 'Applications' do
= icon('cloud fw')
%span
Applications
= nav_link(controller: :services) do
- = link_to admin_application_settings_services_path, title: 'Service Templates', data: {placement: 'right'} do
+ = link_to admin_application_settings_services_path, title: 'Service Templates' do
= icon('copy fw')
%span
Service Templates
= nav_link(controller: :labels) do
- = link_to admin_labels_path, title: 'Labels', data: {placement: 'right'} do
+ = link_to admin_labels_path, title: 'Labels' do
= icon('tags fw')
%span
Labels
= nav_link(controller: :abuse_reports) do
- = link_to admin_abuse_reports_path, title: "Abuse reports" do
+ = link_to admin_abuse_reports_path, title: "Abuse Reports" do
= icon('exclamation-circle fw')
%span
Abuse Reports
%span.count= AbuseReport.count(:all)
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
- = link_to admin_application_settings_path, title: 'Settings', data: {placement: 'right'} do
+ = link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
%span
Settings
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index b1a1d531846..da698831300 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,50 +1,50 @@
%ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
- = link_to dashboard_projects_path, title: 'Projects', data: {placement: 'right'} do
+ = link_to dashboard_projects_path, title: 'Projects' do
= icon('home fw')
%span
Projects
= nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity', data: {placement: 'right'} do
+ = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do
= icon('dashboard fw')
%span
Activity
= nav_link(controller: :groups) do
- = link_to dashboard_groups_path, title: 'Groups', data: {placement: 'right'} do
+ = link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :milestones) do
- = link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
+ = link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
= nav_link(path: 'dashboard#issues') do
- = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
= icon('exclamation-circle fw')
%span
Issues
%span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
- = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
+ = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :snippets) do
- = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
+ = link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
- = link_to help_path, title: 'Help', data: {placement: 'right'} do
+ = link_to help_path, title: 'Help' do
= icon('question-circle fw')
%span
Help
%li.separate-item
= nav_link(controller: :profile) do
- = link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do
+ = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
Profile Settings
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 21e565972a7..48039ca2918 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,21 +1,21 @@
%ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
- = link_to explore_root_path, title: 'Projects', data: {placement: 'right'} do
+ = link_to explore_root_path, title: 'Projects' do
= icon('home fw')
%span
Projects
= nav_link(controller: :groups) do
- = link_to explore_groups_path, title: 'Groups', data: {placement: 'right'} do
+ = link_to explore_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :snippets) do
- = link_to explore_snippets_path, title: 'Snippets', data: {placement: 'right'} do
+ = link_to explore_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
- = link_to help_path, title: 'Help', data: {placement: 'right'} do
+ = link_to help_path, title: 'Help' do
= icon('question-circle fw')
%span
Help
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 319352876b4..68da8d5de2a 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
@@ -8,39 +8,39 @@
%li.separate-item
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
- = link_to group_path(@group), title: 'Home', data: {placement: 'right'} do
+ = link_to group_path(@group), title: 'Home' do
= icon('dashboard fw')
%span
Group
- if can?(current_user, :read_group, @group)
- if current_user
= nav_link(controller: [:group, :milestones]) do
- = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
+ = link_to group_milestones_path(@group), title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
= nav_link(path: 'groups#issues') do
- = link_to issues_group_path(@group), title: 'Issues', data: {placement: 'right'} do
+ = link_to issues_group_path(@group), title: 'Issues' do
= icon('exclamation-circle fw')
%span
Issues
- if current_user
%span.count= Issue.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
- = link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do
+ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= icon('tasks fw')
%span
Merge Requests
- if current_user
%span.count= MergeRequest.opened.of_group(@group).count
= nav_link(controller: [:group_members]) do
- = link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} do
+ = link_to group_group_members_path(@group), title: 'Members' do
= icon('users fw')
%span
Members
- if can?(current_user, :admin_group, @group)
= nav_link(html_options: { class: "separate-item" }) do
- = link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do
+ = link_to edit_group_path(@group), title: 'Settings' do
= icon ('cogs fw')
%span
Settings
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index c8411521f36..56a92fe9103 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to group_path(@group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
+ = link_to group_path(@group), title: 'Go to group', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to group
@@ -9,12 +9,12 @@
%ul.sidebar-subnav
= nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: 'Group Settings', data: {placement: 'right'} do
+ = link_to edit_group_path(@group), title: 'Group Settings' do
= icon ('pencil-square-o fw')
%span
Group Settings
= nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do
+ = link_to projects_group_path(@group), title: 'Projects' do
= icon('folder fw')
%span
Projects
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 0f3a793e30b..64b30783c05 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
@@ -8,52 +8,52 @@
%li.separate-item
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
- = link_to profile_path, title: 'Profile', data: {placement: 'right'} do
+ = link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
Profile Settings
= nav_link(controller: [:accounts, :two_factor_auths]) do
- = link_to profile_account_path, title: 'Account', data: {placement: 'right'} do
+ = link_to profile_account_path, title: 'Account' do
= icon('gear fw')
%span
Account
= nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new', 'applications#create']) do
- = link_to applications_profile_path, title: 'Applications', data: {placement: 'right'} do
+ = link_to applications_profile_path, title: 'Applications' do
= icon('cloud fw')
%span
Applications
= nav_link(controller: :emails) do
- = link_to profile_emails_path, title: 'Emails', data: {placement: 'right'} do
+ = link_to profile_emails_path, title: 'Emails' do
= icon('envelope-o fw')
%span
Emails
%span.count= current_user.emails.count + 1
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
- = link_to edit_profile_password_path, title: 'Password', data: {placement: 'right'} do
+ = link_to edit_profile_password_path, title: 'Password' do
= icon('lock fw')
%span
Password
= nav_link(controller: :notifications) do
- = link_to profile_notifications_path, title: 'Notifications', data: {placement: 'right'} do
+ = link_to profile_notifications_path, title: 'Notifications' do
= icon('inbox fw')
%span
Notifications
= nav_link(controller: :keys) do
- = link_to profile_keys_path, title: 'SSH Keys', data: {placement: 'right'} do
+ = link_to profile_keys_path, title: 'SSH Keys' do
= icon('key fw')
%span
SSH Keys
%span.count= current_user.keys.count
= nav_link(controller: :preferences) do
- = link_to profile_preferences_path, title: 'Preferences', data: {placement: 'right'} do
+ = link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
= icon('image fw')
%span
Preferences
= nav_link(path: 'profiles#audit_log') do
- = link_to audit_log_profile_path, title: 'Audit Log', data: {placement: 'right'} do
+ = link_to audit_log_profile_path, title: 'Audit Log' do
= icon('history fw')
%span
Audit Log
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 2b91d7721f9..c0d62028639 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,13 +1,13 @@
%ul.nav.nav-sidebar
- if @project.group
= nav_link do
- = link_to group_path(@project.group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
+ = link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to group
- else
= nav_link do
- = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
@@ -15,61 +15,54 @@
%li.separate-item
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
- = link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do
+ = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('home fw')
%span
Project
= nav_link(path: 'projects#activity') do
- = link_to activity_project_path(@project), title: 'Project Activity', class: 'shortcuts-project-activity', data: {placement: 'right'} do
+ = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
= icon('dashboard fw')
%span
Activity
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
- = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do
+ = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
= icon('files-o fw')
%span
Files
- if project_nav_tab? :commits
- = nav_link(controller: %w(commit commits compare repositories tags branches releases)) do
- = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
+ = nav_link(controller: %w(commit commits compare repositories tags branches releases network)) do
+ = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
= icon('history fw')
%span
Commits
- if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do
- = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds', data: {placement: 'right'} do
+ = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
= icon('cubes fw')
%span
Builds
- %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
- = icon('code-fork fw')
- %span
- Network
+ %span.count.builds_counter= @project.builds.running_or_pending.count(:all)
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
- = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs', data: {placement: 'right'} do
+ = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
= icon('area-chart fw')
%span
Graphs
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones', data: {placement: 'right'} do
+ = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
- if project_nav_tab? :issues
= nav_link(controller: :issues) do
- = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
+ = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
= icon('exclamation-circle fw')
%span
Issues
@@ -78,7 +71,7 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
@@ -86,35 +79,42 @@
- if project_nav_tab? :settings
= nav_link(controller: [:project_members, :teams]) do
- = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
%span
Members
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels', data: {placement: 'right'} do
+ = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
= icon('tags fw')
%span
Labels
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
- = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki', data: {placement: 'right'} do
+ = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
= icon('book fw')
%span
Wiki
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
- = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do
+ = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
= icon('clipboard fw')
%span
Snippets
- if project_nav_tab? :settings
= nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
- = link_to edit_project_path(@project), title: 'Settings', data: {placement: 'right'} do
+ = link_to edit_project_path(@project), title: 'Settings' do
= icon('cogs fw')
%span
Settings
+
+ -# Global shortcut to network page for compatibility
+ - if project_nav_tab? :network
+ %li.hidden
+ = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
+ Network
+
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 377a99e719a..970da78a5c9 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to project_path(@project), title: 'Go to project', data: {placement: 'right'}, class: 'back-link' do
+ = link_to project_path(@project), title: 'Go to project', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to project
@@ -9,59 +9,44 @@
%ul.sidebar-subnav
= nav_link(path: 'projects#edit') do
- = link_to edit_project_path(@project), title: 'Project Settings', data: {placement: 'right'} do
+ = link_to edit_project_path(@project), title: 'Project Settings' do
= icon('pencil-square-o fw')
%span
Project Settings
= nav_link(controller: :deploy_keys) do
- = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys', data: {placement: 'right'} do
+ = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
= icon('key fw')
%span
Deploy Keys
= nav_link(controller: :hooks) do
- = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks', data: {placement: 'right'} do
+ = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do
= icon('link fw')
%span
Web Hooks
= nav_link(controller: :services) do
- = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services', data: {placement: 'right'} do
+ = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
= icon('cogs fw')
%span
Services
= nav_link(controller: :protected_branches) do
- = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do
+ = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
= icon('lock fw')
%span
Protected Branches
- if @project.builds_enabled?
= nav_link(controller: :runners) do
- = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do
+ = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
= icon('cog fw')
%span
Runners
= nav_link(controller: :variables) do
- = link_to namespace_project_variables_path(@project.namespace, @project) do
+ = link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do
= icon('code fw')
%span
Variables
= nav_link path: 'triggers#index' do
- = link_to namespace_project_triggers_path(@project.namespace, @project) do
+ = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
= icon('retweet fw')
%span
Triggers
- = nav_link path: 'ci_web_hooks#index' do
- = link_to namespace_project_ci_web_hooks_path(@project.namespace, @project) do
- = icon('link fw')
- %span
- CI Web Hooks
- = nav_link path: 'ci_settings#edit' do
- = link_to edit_namespace_project_ci_settings_path(@project.namespace, @project) do
- = icon('building fw')
- %span
- CI Settings
- = nav_link controller: 'ci_services' do
- = link_to namespace_project_ci_services_path(@project.namespace, @project) do
- = icon('share fw')
- %span
- CI Services
diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/notify/build_fail_email.html.haml
index b0aaea89075..f4e9749e5c7 100644
--- a/app/views/ci/notify/build_fail_email.html.haml
+++ b/app/views/notify/build_fail_email.html.haml
@@ -1,13 +1,13 @@
- content_for :header do
%h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
- GitLab CI (build failed)
+ GitLab (build failed)
%h3
Project:
= link_to ci_project_url(@project) do
= @project.name
%p
- Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)}
+ Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
%p
Author: #{@build.commit.git_author_name}
%p
@@ -20,4 +20,4 @@
Message: #{@build.commit.git_commit_message}
%p
- Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)}
+ Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/ci/notify/build_fail_email.text.erb b/app/views/notify/build_fail_email.text.erb
index 17a3b9b1d33..675acea60a1 100644
--- a/app/views/ci/notify/build_fail_email.text.erb
+++ b/app/views/notify/build_fail_email.text.erb
@@ -8,4 +8,4 @@ Stage: <%= @build.stage %>
Job: <%= @build.name %>
Message: <%= @build.commit.git_commit_message %>
-Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %>
+Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/notify/build_success_email.html.haml
index 24c439e50eb..8b004d34cca 100644
--- a/app/views/ci/notify/build_success_email.html.haml
+++ b/app/views/notify/build_success_email.html.haml
@@ -1,6 +1,6 @@
- content_for :header do
%h1{style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
- GitLab CI (build successful)
+ GitLab (build successful)
%h3
Project:
@@ -8,7 +8,7 @@
= @project.name
%p
- Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)}
+ Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
%p
Author: #{@build.commit.git_author_name}
%p
@@ -21,4 +21,4 @@
Message: #{@build.commit.git_commit_message}
%p
- Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)}
+ Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/ci/notify/build_success_email.text.erb b/app/views/notify/build_success_email.text.erb
index bc8b978c3d7..747da44acae 100644
--- a/app/views/ci/notify/build_success_email.text.erb
+++ b/app/views/notify/build_success_email.text.erb
@@ -8,4 +8,4 @@ Stage: <%= @build.stage %>
Job: <%= @build.name %>
Message: <%= @build.commit.git_commit_message %>
-Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %>
+Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 12f83aae04b..4361f67a74d 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -1,30 +1,32 @@
-%h3 #{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}
+%h3
+ #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
+ at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
-- if @compare
- - if @reverse_compare
+- if @message.compare
+ - if @message.reverse_compare?
%p
%strong WARNING:
The push did not contain any new commits, but force pushed to delete the commits and changes below.
%h4
- = @reverse_compare ? "Deleted commits:" : "Commits:"
+ = @message.reverse_compare? ? "Deleted commits:" : "Commits:"
%ul
- - @commits.each do |commit|
+ - @message.commits.each do |commit|
%li
- %strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)}
+ %strong #{link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))}
%div
%span by #{commit.author_name}
%i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
%pre.commit-message
= commit.safe_message
- %h4 #{pluralize @diffs.count, "changed file"}:
+ %h4 #{pluralize @message.diffs_count, "changed file"}:
%ul
- - @diffs.each_with_index do |diff, i|
+ - @message.diffs.each_with_index do |diff, i|
%li.file-stats
- %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" }
+ %a{href: "#{@message.target_url if @message.disable_diffs?}#diff-#{i}" }
- if diff.deleted_file
%span.deleted-file
&minus;
@@ -40,11 +42,11 @@
- else
= diff.new_path
- - unless @disable_diffs
+ - unless @message.disable_diffs?
%h4 Changes:
- - @diffs.each_with_index do |diff, i|
+ - @message.diffs.each_with_index do |diff, i|
%li{id: "diff-#{i}"}
- %a{href: @target_url + "#diff-#{i}"}
+ %a{href: @message.target_url + "#diff-#{i}"}
- if diff.deleted_file
%strong
= diff.old_path
@@ -62,5 +64,5 @@
= color_email_diff(diff.diff)
%br
- - if @compare.timeout
+ - if @message.compare_timeout
%h5 Huge diff. To prevent performance issues changes are hidden
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index 97a176ed2a3..aa0e263b6df 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -1,21 +1,21 @@
-#{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{@project.name_with_namespace}
-- if @compare
+#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} at #{@message.project_name_with_namespace}
+- if @message.compare
\
\
- - if @reverse_compare
+ - if @message.reverse_compare?
WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below.
\
\
- = @reverse_compare ? "Deleted commits:" : "Commits:"
- - @commits.each do |commit|
+ = @message.reverse_compare? ? "Deleted commits:" : "Commits:"
+ - @message.commits.each do |commit|
#{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
#{commit.safe_message}
\- - - - -
\
\
- #{pluralize @diffs.count, "changed file"}:
+ #{pluralize @message.diffs_count, "changed file"}:
\
- - @diffs.each do |diff|
+ - @message.diffs.each do |diff|
- if diff.deleted_file
\- − #{diff.old_path}
- elsif diff.renamed_file
@@ -24,11 +24,11 @@
\- + #{diff.new_path}
- else
\- #{diff.new_path}
- - unless @disable_diffs
+ - unless @message.disable_diffs?
\
\
Changes:
- - @diffs.each do |diff|
+ - @message.diffs.each do |diff|
\
\=====================================
- if diff.deleted_file
@@ -39,11 +39,11 @@
= diff.new_path
\=====================================
!= diff.diff
- - if @compare.timeout
+ - if @message.compare_timeout
\
\
Huge diff. To prevent performance issues it was hidden
- - if @target_url
+ - if @message.target_url
\
\
- View it on GitLab: #{@target_url}
+ View it on GitLab: #{@message.target_url}
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index cd7b1b0fe03..17e47c622ce 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -23,11 +23,14 @@
%p.cgray
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "form-control"
- %div
- = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
- = f.submit 'Generate', class: "btn btn-default btn-build-token"
+
+ .form-actions
+ - if current_user.private_token
+ = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default"
+ - else
+ = f.submit 'Generate', class: "btn btn-default"
- unless current_user.ldap_user?
.panel.panel-default
@@ -54,7 +57,8 @@
%p
Each time you log in you’ll be required to provide your username and
password as usual, plus a randomly-generated code from your phone.
- %div
+
+ .form-actions
= link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- if button_based_providers.any?
@@ -81,15 +85,16 @@
%p
Changing your username will change path to all personal projects!
%div
- = f.text_field :username, required: true, class: 'form-control'
+ .input-group
+ .input-group-addon
+ = "#{root_url}u/"
+ = f.text_field :username, required: true, class: 'form-control'
&nbsp;
.loading-gif.hide
%p
= icon('spinner spin')
Saving new username
- %p.light
- = user_url(@user)
- %div
+ .form-actions
= f.submit 'Save username', class: "btn btn-warning"
- if signup_enabled?
@@ -104,7 +109,8 @@
- rp = current_user.personal_projects.count
- unless rp.zero?
%li #{pluralize rp, 'personal project'} will be removed and cannot be restored
- = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
+ .form-actions
+ = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
- else
- if @user.solo_owned_groups.present?
%p
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index 2342936a5d5..0436c2213da 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -15,24 +15,25 @@
.pull-right
= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
- if @applications.any?
- %table.table.table-striped
- %thead
- %tr
- %th Name
- %th Callback URL
- %th Clients
- %th
- %th
- %tbody
- - @applications.each do |application|
- %tr{:id => "application_#{application.id}"}
- %td= link_to application.name, oauth_application_path(application)
- %td
- - application.redirect_uri.split.each do |uri|
- %div= uri
- %td= application.access_tokens.count
- %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
- %td= render 'doorkeeper/applications/delete_form', application: application
+ .table-holder
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th Clients
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td
+ - application.redirect_uri.split.each do |uri|
+ %div= uri
+ %td= application.access_tokens.count
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
+ %td= render 'doorkeeper/applications/delete_form', application: application
.oauth-authorized-applications.prepend-top-20
- if user_oauth_applications?
@@ -40,29 +41,30 @@
Authorized applications
- if @authorized_tokens.any?
- %table.table.table-striped
- %thead
- %tr
- %th Name
- %th Authorized At
- %th Scope
- %th
- %tbody
- - @authorized_apps.each do |app|
- - token = app.authorized_tokens.order('created_at desc').first
- %tr{:id => "application_#{app.id}"}
- %td= app.name
- %td= token.created_at
- %td= token.scopes
- %td= render 'doorkeeper/authorized_applications/delete_form', application: app
- - @authorized_anonymous_tokens.each do |token|
+ .table-holder
+ %table.table.table-striped
+ %thead
%tr
- %td
- Anonymous
- %div.help-block
- %em Authorization was granted by entering your username and password in the application.
- %td= token.created_at
- %td= token.scopes
- %td= render 'doorkeeper/authorized_applications/delete_form', token: token
+ %th Name
+ %th Authorized At
+ %th Scope
+ %th
+ %tbody
+ - @authorized_apps.each do |app|
+ - token = app.authorized_tokens.order('created_at desc').first
+ %tr{:id => "application_#{app.id}"}
+ %td= app.name
+ %td= token.created_at
+ %td= token.scopes
+ %td= render 'doorkeeper/authorized_applications/delete_form', application: app
+ - @authorized_anonymous_tokens.each do |token|
+ %tr
+ %td
+ Anonymous
+ %div.help-block
+ %em Authorization was granted by entering your username and password in the application.
+ %td= token.created_at
+ %td= token.scopes
+ %td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
%p.light You don't have any authorized applications
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index b76a5b636ac..2a8800de60e 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -1,5 +1,5 @@
%div
- = form_for [:profile, @key], html: { class: 'form-horizontal' } do |f|
+ = form_for [:profile, @key], html: { class: 'form-horizontal js-requires-input' } do |f|
- if @key.errors.any?
.alert.alert-danger
%ul
@@ -9,12 +9,11 @@
.form-group
= f.label :key, class: 'control-label'
.col-sm-10
- = f.text_area :key, class: "form-control", rows: 8
+ = f.text_area :key, class: "form-control", rows: 8, autofocus: true, required: true
.form-group
= f.label :title, class: 'control-label'
- .col-sm-10= f.text_field :title, class: "form-control"
+ .col-sm-10= f.text_field :title, class: "form-control", required: true
.form-actions
= f.submit 'Add key', class: "btn btn-create"
= link_to "Cancel", profile_keys_path, class: "btn btn-cancel"
-
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
index ef0075aad3b..8c9d546af4c 100644
--- a/app/views/profiles/keys/_key_table.html.haml
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -1,6 +1,6 @@
- is_admin = defined?(admin) ? true : false
-.panel.panel-default
- - if @keys.any?
+- if @keys.any?
+ .table-holder
%table.table
%thead.panel-heading
%tr
@@ -11,9 +11,9 @@
%tbody
- @keys.each do |key|
= render 'profiles/keys/key', key: key, is_admin: is_admin
- - else
- .nothing-here-block
- - if is_admin
- User has no ssh keys
- - else
- There are no SSH keys with access to your account.
+- else
+ .nothing-here-block
+ - if is_admin
+ User has no ssh keys
+ - else
+ There are no SSH keys with access to your account.
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 14adba1c797..17a4195030e 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -3,7 +3,9 @@
.gray-content-block.top-block
.pull-right
- = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
+ = link_to new_profile_key_path, class: "btn btn-new" do
+ = icon('plus')
+ Add SSH Key
.oneline
Before you can add an SSH key you need to
= link_to "generate it.", help_page_path("ssh", "README")
diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
index 2bf207a3221..11166dc6d99 100644
--- a/app/views/profiles/keys/new.html.haml
+++ b/app/views/profiles/keys/new.html.haml
@@ -9,7 +9,7 @@
$('#key_key').on('focusout', function(){
var title = $('#key_title'),
val = $('#key_key').val(),
- comment = val.match(/^\S+ \S+ (.+)$/);
+ comment = val.match(/^\S+ \S+ (.+)\n?$/);
if( comment && comment.length > 1 && title.val() == '' ){
$('#key_title').val( comment[1] );
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 8eebd96b674..0bcadc965fa 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -50,12 +50,10 @@
Watch
%p You will receive notifications for any activity
- .form-actions
+ .gray-content-block
= f.submit 'Save changes', class: "btn btn-create"
-.clearfix
- %hr
-.row.all-notifications
+.row.all-notifications.prepend-top-default
.col-md-6
%p
You can also specify notification level per group or per project.
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index cc41d7dd813..877589dc390 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -54,4 +54,4 @@
.help-block
Choose what content you want to see on a project's home page.
.panel-footer
- = f.submit 'Save', class: 'btn btn-save'
+ = f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index ac7355dde1f..9459d8a6295 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -43,7 +43,7 @@
.form-group
= f.label :public_email, class: "control-label"
.col-sm-10
- = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show in profile'}, class: "form-control"
+ = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show on profile'}, class: "select2"
%span.help-block This email will be displayed on your public profile.
.form-group
= f.label :skype, class: "control-label"
@@ -96,8 +96,6 @@
= link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
- .row
- .col-md-7
- .form-group
- .col-sm-offset-2.col-sm-10
- = f.submit 'Save changes', class: "btn btn-success"
+ .form-actions
+ = f.submit 'Save changes', class: "btn btn-success"
+ = link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
index 35f7e7bb34b..2fd3d9e1be4 100644
--- a/app/views/projects/_commit_button.html.haml
+++ b/app/views/projects/_commit_button.html.haml
@@ -1,6 +1,4 @@
.form-actions
- .commit-button-annotation
- = button_tag 'Commit Changes',
- class: 'btn commit-btn js-commit-button btn-create'
+ = button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create'
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 88d54bf6f21..c1669ac046b 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,5 +1,5 @@
- empty_repo = @project.empty_repo?
-.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
+.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)}
.project-identicon-holder
= project_icon(@project, alt: '', class: 'project-avatar avatar s90')
.project-home-desc
@@ -12,8 +12,19 @@
Forked from
= link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name)
+ .cover-controls.left
+ .visibility-level-label.has_tooltip{title: project_visibility_level_description(@project.visibility_level), data: { container: 'body' } }
+ = visibility_level_icon(@project.visibility_level, fw: false)
+ = visibility_level_label(@project.visibility_level)
-
+ .cover-controls
+ - if can?(current_user, :admin_project, @project)
+ = link_to edit_project_path(@project), class: 'btn btn-gray' do
+ = icon('pencil')
+ - if current_user
+ &nbsp;
+ = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do
+ = icon('rss')
.project-repo-buttons
.split-one
@@ -21,7 +32,7 @@
= render 'projects/buttons/fork'
= render "shared/clone_panel"
-
+
.split-repo-buttons
= render "projects/buttons/download"
= render 'projects/buttons/dropdown'
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index 7e1ee2b7fc1..386d72e7787 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -3,7 +3,7 @@
- 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
+ = ci_status_label(ci_commit)
= 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), class: "commit-row-message"
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 7b21095ea3e..54c818baaf4 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -5,20 +5,21 @@
%a.js-md-write-button(href="#md-write-holder" tabindex="-1")
Write
%li
- %a.js-md-preview-button(href="md-preview-holder" tabindex="-1")
+ %a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
Preview
- - if defined?(referenced_users) && referenced_users
- %span.referenced-users.pull-left.hide
+ %div
+ .md-write-holder
+ = yield
+ .md.md-preview-holder.hide
+ .js-md-preview{class: (preview_class if defined?(preview_class))}
+
+ - if defined?(referenced_users) && referenced_users
+ %div.referenced-users.hide
+ %span
= icon('exclamation-triangle')
You are about to add
%strong
%span.js-referenced-users-count 0
people
to the discussion. Proceed with caution.
-
- %div
- .md-write-holder
- = yield
- .md.md-preview-holder.hide
- .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index b5ef0aca540..d1191928d4f 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -7,15 +7,16 @@
= cache(readme_cache_key) do
= render_readme(readme)
- else
- %h3.page-title
- This project does not have README yet
- - if can?(current_user, :push_code, @project)
- %p.slead
- A
- %code README
- file contains information about other files in a repository and is commonly
- distributed with computer software, forming part of its documentation.
- %br
- We recommend you to
- = link_to "add README", new_readme_path, class: 'underlined-link'
- file to the repository and GitLab will render it here instead of this message.
+ .gray-content-block.second-block.center
+ %h3.page-title
+ This project does not have README yet
+ - if can?(current_user, :push_code, @project)
+ %p
+ A
+ %code README
+ file contains information about other files in a repository and is commonly
+ distributed with computer software, forming part of its documentation.
+ %p
+ We recommend you to
+ = link_to "add README", new_readme_path, class: 'underlined-link'
+ file to the repository and GitLab will render it here instead of this message.
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 6518c4173e1..8d9ec068a43 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -6,7 +6,7 @@
#tree-holder.tree-holder
.file-holder
.file-title
- %i.fa.fa-file
+ = blob_icon @blob.mode, @blob.name
%strong
= @path
%small= number_to_human_size @blob.size
@@ -43,4 +43,3 @@
- blame_group[:lines].each do |line|
:erb
<%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %>
-
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index ba3e0c3c590..b1df8d19938 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -1,9 +1,8 @@
.btn-group.tree-btn-group
- = edit_blob_link(@project, @ref, @path)
= link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files
- - if @blob.text?
+ - if blob_viewable?(@blob)
- if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
= link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
class: 'btn btn-sm'
@@ -12,11 +11,16 @@
class: 'btn btn-sm' unless @blob.empty?
= link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
class: 'btn btn-sm'
- - if @ref != @commit.sha
- = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
- tree_join(@commit.sha, @path)), class: 'btn btn-sm'
+ = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
+ tree_join(@commit.sha, @path)), class: 'btn btn-sm'
-- if allowed_tree_edit?
+- if blob_editable?(@blob)
.btn-group{ role: "group" }
+ = edit_blob_link(@project, @ref, @path)
%button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace
%button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete
+- elsif !on_top_of_branch?
+ .btn-group{ role: "group" }
+ %button.btn.btn-default.disabled.has_tooltip{title: "You can only edit files when you are on a branch.", data: {container: 'body'}} Edit
+ %button.btn.btn-default.disabled.has_tooltip{title: "You can only replace files when you are on a branch.", data: {container: 'body'}} Replace
+ %button.btn.btn-remove.disabled.has_tooltip{title: "You can only delete files when you are on a branch.", data: {container: 'body'}} Delete
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 42f632b38ef..2a3315da3db 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -29,10 +29,12 @@
%strong
= blob.name
%small
- = number_to_human_size(blob.size)
+ = number_to_human_size(blob_size(blob))
.file-actions.hidden-xs
= render "actions"
- - if blob.text?
+ - if blob.lfs_pointer?
+ = render "download", blob: blob
+ - elsif blob.text?
= render "text", blob: blob
- elsif blob.image?
= render "image", blob: blob
diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml
index f2c5e95ecf4..7908fcae3de 100644
--- a/app/views/projects/blob/_download.html.haml
+++ b/app/views/projects/blob/_download.html.haml
@@ -4,4 +4,4 @@
%h1.light
%i.fa.fa-download
%h4
- Download (#{number_to_human_size blob.size})
+ Download (#{number_to_human_size blob_size(blob)})
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index f1ad0c3c403..10b02813733 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -1,23 +1,22 @@
-.file-holder.file
- .file-title
+.file-holder.file.append-bottom-default
+ .file-title.clearfix
.editor-ref
- %i.fa.fa-code-fork
+ = icon('code-fork')
= ref
%span.editor-file-name
- - if @path
- %span.monospace
- = @path
+ = @path
- - if current_action?(:new) || current_action?(:create)
+ - if current_action?(:new) || current_action?(:create)
+ %span.editor-file-name
\/
- = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
- required: true, class: 'form-control new-file-name js-quick-submit'
- .pull-right
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
+ = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
+ required: true, class: 'form-control new-file-name js-quick-submit'
+
+ .pull-right
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
.file-content.code
- %pre.js-edit-mode-pane#editor
- = params[:content] || local_assigns[:blob_data]
+ %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
- if local_assigns[:path]
.js-edit-mode-pane#preview.hide
.center
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 13b5ffd17ff..fc6c9f5fd09 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -5,19 +5,17 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title Create New Directory
.modal-body
- = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form' do
+ = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do
.form-group
- = label_tag :dir_name, 'Directory Name', class: 'control-label'
+ = label_tag :dir_name, 'Directory name', class: 'control-label'
.col-sm-10
- = text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control'
+ = text_field_tag :dir_name, params[:dir_name], required: true, class: 'form-control'
= render 'shared/new_commit_form', placeholder: "Add new directory"
- .form-group
- .col-sm-offset-2.col-sm-10
- = submit_tag "Create directory", class: 'btn btn-primary btn-create'
- = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+ .form-actions
+ = submit_tag "Create directory", class: 'btn btn-create'
+ = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
- disableButtonIfAnyEmptyField($(".js-create-dir-form"), ".form-control", ".btn-create");
new NewCommitForm($('.js-create-dir-form'))
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 3bb61f0c944..ecc90a30e78 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -16,10 +16,9 @@
= render 'shared/new_commit_form', placeholder: placeholder
- .form-group
- .col-sm-offset-2.col-sm-10
- = button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
- = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+ .form-actions
+ = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
+ = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 56745165251..a47fe7ede80 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -5,12 +5,12 @@
%ul.center-top-menu.no-bottom.js-edit-mode
%li.active
= link_to '#editor' do
- %i.fa.fa-edit
- Edit file
+ = icon('edit')
+ Edit File
%li
= link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
- %i.fa.fa-eye
+ = icon('eye')
= editing_preview_title(@blob.name)
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input js-edit-blob-form') do
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 1ff68005450..167fa615182 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,9 +1,8 @@
- page_title "New File", @path.presence, @ref
= render "header_title"
-.gray-content-block.top-block
- %h3.page-title
- Create New File
+%h3.page-title
+ New File
.file-editor
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-requires-input') do
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index b7276868ce6..3f8d11ed8c8 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -6,7 +6,7 @@
%div#tree-holder.tree-holder
= render 'blob', blob: @blob
-- if allowed_tree_edit?
+- if blob_editable?(@blob)
= render 'projects/blob/remove'
- title = "Replace #{@blob.name}"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index cc0ec9483d2..5081bae6801 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -3,17 +3,17 @@
%div
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
%strong.str-truncated= branch.name
- &nbsp;
- - if branch.name == @repository.root_ref
- %span.label.label-primary default
- - elsif @repository.merged_to_root_ref? branch.name
- %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
- merged
+ &nbsp;
+ - if branch.name == @repository.root_ref
+ %span.label.label-primary default
+ - elsif @repository.merged_to_root_ref? branch.name
+ %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
+ merged
- - if @project.protected_branch? branch.name
- %span.label.label-success
- %i.fa.fa-lock
- protected
+ - if @project.protected_branch? branch.name
+ %span.label.label-success
+ %i.fa.fa-lock
+ protected
.controls.hidden-xs
- if create_mr_button?(@repository.root_ref, branch.name)
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do
@@ -26,7 +26,7 @@
Compare
- if can_remove_branch?(@project, branch.name)
- = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
+ = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if commit
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index 22d77dda938..9fe65cbb104 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,5 +1,5 @@
.branch-commit
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id"
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id monospace"
&middot;
%span.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 03ade02a0c8..204def60794 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -5,7 +5,7 @@
.pull-right
- if can? current_user, :push_code, @project
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
- %i.fa.fa-add-sign
+ = icon('plus')
New branch
&nbsp;
.dropdown.inline
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index f5577042ca4..31943a2407a 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -6,25 +6,27 @@
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
= @error
%h3.page-title
- %i.fa.fa-code-fork
- New branch
+ New Branch
+%hr
+
= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do
.form-group
- = label_tag :branch_name, 'Name for new branch', class: 'control-label'
+ = label_tag :branch_name, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :branch_name, params[:branch_name], placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control'
+ = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
- = text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
+ = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
+ .help-block Existing branch name, tag, or commit SHA
.form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
- var availableTags = #{@project.repository.ref_names.to_json};
+ var availableRefs = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
- source: availableTags,
+ source: availableRefs,
minLength: 1
});
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index dab7164153f..1a26908ab11 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -3,26 +3,29 @@
.project-issuable-filter
.controls
- - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
+ - if 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', method: :post
+ = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
%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)
+ %span.badge.js-running-count
+ = number_with_delimiter(@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)
+ %span.badge.js-running-count
+ = number_with_delimiter(@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)
+ %span.badge.js-totalbuilds-count
+ = number_with_delimiter(@all_builds.count(:id))
.gray-content-block
#{(@scope || 'running').capitalize} builds from this project
@@ -37,7 +40,7 @@
%thead
%tr
%th Status
- %th Build ID
+ %th Runner
%th Commit
%th Ref
%th Stage
@@ -50,4 +53,3 @@
= render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true
= paginate @builds, theme: 'gitlab'
-
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 907e1ce10bd..20a5b6a66e7 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -1,17 +1,16 @@
-- page_title "#{@build.name} (#{@build.id})", "Builds"
+- page_title "#{@build.name} (##{@build.id})", "Builds"
= render "header_title"
.build-page
- .gray-content-block
+ .gray-content-block.top-block
Build ##{@build.id} for commit
- %strong.monospace
- = link_to @build.commit.short_sha, ci_status_path(@build.commit)
+ %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
from
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
#up-build-trace
- if @commit.matrix_for_ref?(@build.ref)
- %ul.center-top-menu.build-top-menu
+ %ul.center-top-menu.no-top.no-bottom
- @commit.latest_builds_for_ref(@build.ref).each do |build|
%li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do
@@ -22,7 +21,6 @@
- else
= build.id
-
- if @build.retried?
%li.active
%a
@@ -31,7 +29,7 @@
%i.fa.fa-warning
This build was retried.
- .gray-content-block.second-block
+ .gray-content-block.middle-block
.build-head
.clearfix
= ci_status_with_icon(@build.status)
@@ -58,7 +56,7 @@
%br
Go to
- = link_to namespace_project_runners_path(@build.gl_project.namespace, @build.gl_project) do
+ = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
Runners page
.row.prepend-top-default
@@ -115,7 +113,7 @@
%p
%span.attr-name Runner:
- if @build.runner && current_user && current_user.admin
- = link_to "##{@build.runner.id}", ci_admin_runner_path(@build.runner.id)
+ = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
- elsif @build.runner
\##{@build.runner.id}
@@ -140,7 +138,7 @@
%h4.title
Commit
.pull-right
- %small
+ %small
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
%p
%span.attr-name Branch:
@@ -162,7 +160,7 @@
- if @builds.present?
.build-widget
- %h4.title #{pluralize(@builds.count(:id), "other build")} for
+ %h4.title #{pluralize(@builds.count(:id), "other build")} for
= succeed ":" do
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
%table.table.builds
diff --git a/app/views/projects/ci_services/_form.html.haml b/app/views/projects/ci_services/_form.html.haml
deleted file mode 100644
index 397832e56db..00000000000
--- a/app/views/projects/ci_services/_form.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-%h3.page-title
- = @service.title
- = boolean_to_icon @service.activated?
-
-%p= @service.description
-
-
-%hr
-
-= form_for(@service, as: :service, url: namespace_project_ci_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
- - if @service.errors.any?
- .alert.alert-danger
- %ul
- - @service.errors.full_messages.each do |msg|
- %li= msg
-
- - if @service.help.present?
- .bs-callout
- = @service.help
-
- .form-group
- = f.label :active, "Active", class: "control-label"
- .col-sm-10
- = f.check_box :active
-
- - @service.fields.each do |field|
- - name = field[:name]
- - label = field[:label] || name
- - value = @service.send(name)
- - type = field[:type]
- - placeholder = field[:placeholder]
- - choices = field[:choices]
- - default_choice = field[:default_choice]
- - help = field[:help]
-
- .form-group
- = f.label label, class: "control-label"
- .col-sm-10
- - if type == 'text'
- = f.text_field name, class: "form-control", placeholder: placeholder
- - elsif type == 'textarea'
- = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
- - elsif type == 'checkbox'
- = f.check_box name
- - elsif type == 'select'
- = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- - if help
- .light #{help}
-
- .form-actions
- = f.submit 'Save', class: 'btn btn-save'
- &nbsp;
- - if @service.valid? && @service.activated? && @service.can_test?
- = link_to 'Test settings', test_namespace_project_ci_service_path(@project.namespace, @project, @service.to_param), class: 'btn'
diff --git a/app/views/projects/ci_services/edit.html.haml b/app/views/projects/ci_services/edit.html.haml
deleted file mode 100644
index dacb6b4f6f4..00000000000
--- a/app/views/projects/ci_services/edit.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-- page_title @service.title, "CI Services"
-= render 'form'
diff --git a/app/views/projects/ci_services/index.html.haml b/app/views/projects/ci_services/index.html.haml
deleted file mode 100644
index 3f26c7851d8..00000000000
--- a/app/views/projects/ci_services/index.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-- page_title "CI Services"
-%h3.page-title Project services
-%p.light Project services allow you to integrate GitLab CI 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_ci_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/ci_settings/_form.html.haml b/app/views/projects/ci_settings/_form.html.haml
deleted file mode 100644
index ee6b8885e2d..00000000000
--- a/app/views/projects/ci_settings/_form.html.haml
+++ /dev/null
@@ -1,120 +0,0 @@
-%h3.page-title
- CI settings
-%hr
-.bs-callout.help-callout
- %p
- If you want to test your .gitlab-ci.yml, you can use special tool - #{link_to "Lint", ci_lint_path}
- %p
- Edit your
- #{link_to ".gitlab-ci.yml using web-editor", yaml_web_editor_link(@ci_project)}
-
-- unless @project.empty_repo?
- %p
- Paste build status image for #{@repository.root_ref} with next link
- = link_to '#', class: 'badge-codes-toggle btn btn-default btn-xs' do
- Status Badge
- .badge-codes-block.bs-callout.bs-callout-info.hide
- %p
- Status badge for
- %span.label.label-info #{@ref}
- branch
- %div
- %label Markdown:
- = text_field_tag 'badge_md', markdown_badge_code(@ci_project, @repository.root_ref), readonly: true, class: 'form-control'
- %label Html:
- = text_field_tag 'badge_html', html_badge_code(@ci_project, @repository.root_ref), readonly: true, class: 'form-control'
-
-= nested_form_for @ci_project, url: namespace_project_ci_settings_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
- - if @ci_project.errors.any?
- #error_explanation
- %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:"
- .alert.alert-error
- %ul
- - @ci_project.errors.full_messages.each do |msg|
- %li= msg
-
- %fieldset
- %legend Build settings
- .form-group
- = label_tag nil, class: 'control-label' do
- Get code
- .col-sm-10
- %p Get recent application code using the following command:
- .radio
- = label_tag do
- = f.radio_button :allow_git_fetch, 'false'
- %strong git clone
- .light Slower but makes sure you have a clean dir before every build
- .radio
- = label_tag do
- = f.radio_button :allow_git_fetch, 'true'
- %strong git fetch
- .light Faster
- .form-group
- = f.label :timeout_in_minutes, 'Timeout', class: 'control-label'
- .col-sm-10
- = f.number_field :timeout_in_minutes, class: 'form-control', min: '0'
- .light per build in minutes
-
-
- %fieldset
- %legend Build Schedule
- .form-group
- = f.label :always_build, 'Schedule build', class: 'control-label'
- .col-sm-10
- .checkbox
- = f.label :always_build do
- = f.check_box :always_build
- %span.light Repeat last build after X hours if no builds
- .form-group
- = f.label :polling_interval, "Build interval", class: 'control-label'
- .col-sm-10
- = f.number_field :polling_interval, placeholder: '5', min: '0', class: 'form-control'
- .light In hours
-
- %fieldset
- %legend Project settings
- .form-group
- = f.label :default_ref, "Make tabs for the following branches", class: 'control-label'
- .col-sm-10
- = f.text_field :default_ref, class: 'form-control', placeholder: 'master, stable'
- .light You will be able to filter builds by the following branches
- .form-group
- = f.label :public, 'Public mode', class: 'control-label'
- .col-sm-10
- .checkbox
- = f.label :public do
- = f.check_box :public
- %span.light Anyone can see project and builds
- .form-group
- = f.label :coverage_regex, "Test coverage parsing", class: 'control-label'
- .col-sm-10
- .input-group
- %span.input-group-addon /
- = f.text_field :coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
- %span.input-group-addon /
- .light We will use this regular expression to find test coverage output in build trace. Leave blank if you want to disable this feature
- .bs-callout.bs-callout-info
- %p Below are examples of regex for existing tools:
- %ul
- %li
- Simplecov (Ruby) -
- %code \(\d+.\d+\%\) covered
- %li
- pytest-cov (Python) -
- %code \d+\%\s*$
- %li
- phpunit --coverage-text --colors=never (PHP) -
- %code ^\s*Lines:\s*\d+.\d+\%
-
- %fieldset
- %legend Advanced settings
- .form-group
- = f.label :token, "CI token", class: 'control-label'
- .col-sm-10
- = f.text_field :token, class: 'form-control', placeholder: 'xEeFCaDAB89'
-
- .form-actions
- = f.submit 'Save changes', class: 'btn btn-save'
- - unless @ci_project.new_record?
- = link_to 'Remove Project', ci_project_path(@ci_project), method: :delete, data: { confirm: 'Project will be removed. Are you sure?' }, class: 'btn btn-danger pull-right'
diff --git a/app/views/projects/ci_settings/_no_runners.html.haml b/app/views/projects/ci_settings/_no_runners.html.haml
deleted file mode 100644
index 1374e6680f9..00000000000
--- a/app/views/projects/ci_settings/_no_runners.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-.alert.alert-danger
- %p
- There are NO runners to build this project.
- %br
- You can add Specific runner for this project on Runners page
-
- - if current_user.admin
- or add Shared runner for whole application in admin area.
diff --git a/app/views/projects/ci_settings/edit.html.haml b/app/views/projects/ci_settings/edit.html.haml
deleted file mode 100644
index acc912d4596..00000000000
--- a/app/views/projects/ci_settings/edit.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-- page_title "CI Settings"
-
-- if no_runners_for_project?(@ci_project)
- = render 'no_runners'
-
-= render 'form'
diff --git a/app/views/projects/ci_web_hooks/index.html.haml b/app/views/projects/ci_web_hooks/index.html.haml
deleted file mode 100644
index 2998fb08ff1..00000000000
--- a/app/views/projects/ci_web_hooks/index.html.haml
+++ /dev/null
@@ -1,94 +0,0 @@
-- page_title "CI Web Hooks"
-%h3.page-title
- CI Web hooks
-
-%p.light
- Web Hooks can be used for binding events when build completed.
-
-%hr.clearfix
-
-= form_for @web_hook, url: namespace_project_ci_web_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
- -if @web_hook.errors.any?
- .alert.alert-danger
- - @web_hook.errors.full_messages.each do |msg|
- %p= msg
- .form-group
- = f.label :url, "URL", class: 'control-label'
- .col-sm-10
- = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
- .form-actions
- = f.submit "Add Web Hook", class: "btn btn-create"
-
--if @web_hooks.any?
- %h4 Activated web hooks (#{@web_hooks.count})
- .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
-
-:erb
- <pre>
- <code>
- {
- "build_id": 2,
- "build_name":"rspec_linux"
- "build_status": "failed",
- "build_started_at": "2014-05-05T18:01:02.563Z",
- "build_finished_at": "2014-05-05T18:01:07.611Z",
- "project_id": 1,
- "project_name": "Brightbox \/ Brightbox Cli",
- "gitlab_url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli",
- "ref": "master",
- "sha": "a26cf5de9ed9827746d4970872376b10d9325f40",
- "before_sha": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
- "push_data": {
- "before": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
- "after": "a26cf5de9ed9827746d4970872376b10d9325f40",
- "ref": "refs\/heads\/master",
- "user_id": 1,
- "user_name": "Administrator",
- "project_id": 5,
- "repository": {
- "name": "Brightbox Cli",
- "url": "dzaporozhets@localhost:brightbox\/brightbox-cli.git",
- "description": "Voluptatibus quae error consectetur voluptas dolores vel excepturi possimus.",
- "homepage": "http:\/\/localhost:3000\/brightbox\/brightbox-cli"
- },
- "commits": [
- {
- "id": "a26cf5de9ed9827746d4970872376b10d9325f40",
- "message": "Release v1.2.2",
- "timestamp": "2014-04-22T16:46:42+03:00",
- "url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/a26cf5de9ed9827746d4970872376b10d9325f40",
- "author": {
- "name": "Paul Thornthwaite",
- "email": "tokengeek@gmail.com"
- }
- },
- {
- "id": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
- "message": "Fix server user data update\n\nIncorrect condition was being used so Base64 encoding option was having\nopposite effect from desired.",
- "timestamp": "2014-04-11T18:17:26+03:00",
- "url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/34f57f6ba3ed0c21c5e361bbb041c3591411176c",
- "author": {
- "name": "Paul Thornthwaite",
- "email": "tokengeek@gmail.com"
- }
- }
- ],
- "total_commits_count": 2,
- "ci_yaml_file":"rspec_linux:\r\n script: ls\r\n"
- }
- }
- </code>
- </pre>
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
new file mode 100644
index 00000000000..329aaa0bb8b
--- /dev/null
+++ b/app/views/projects/commit/_builds.html.haml
@@ -0,0 +1,68 @@
+.gray-content-block.middle-block
+ .pull-right
+ - if can?(current_user, :manage_builds, @ci_commit.project)
+ - if @ci_commit.builds.latest.failed.any?(&:retryable?)
+ = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
+
+ - if @ci_commit.builds.running_or_pending.any?
+ = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+
+ .oneline
+ = pluralize @statuses.count(:id), "build"
+ - if defined?(link_to_commit) && link_to_commit
+ for commit
+ = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace"
+ - if @ci_commit.duration > 0
+ in
+ = time_interval_in_words @ci_commit.duration
+
+- if @ci_commit.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - @ci_commit.yaml_errors.split(",").each do |error|
+ %li= error
+ You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
+
+- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file
+ .bs-callout.bs-callout-warning
+ \.gitlab-ci.yml not found in this commit
+
+.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_commit.project.build_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,
+ locals: { coverage: @ci_commit.project.build_coverage_enabled?, stage: true, 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_commit.project.build_coverage_enabled?
+ %th Coverage
+ %th
+ = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried,
+ locals: { coverage: @ci_commit.project.build_coverage_enabled?, stage: true }
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index c73ba74f5ef..f74f8b427ec 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -1,4 +1,4 @@
-%ul.center-top-menu.commit-ci-menu
+%ul.center-top-menu.no-top.no-bottom.commit-ci-menu
= nav_link(path: 'commit#show') do
= link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Changes
@@ -6,4 +6,4 @@
= nav_link(path: 'commit#builds') do
= link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Builds
- %span.badge= @builds.count(:id)
+ %span.badge= @statuses.count
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 776768537d0..ddb77fd796b 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -13,13 +13,15 @@
- unless @commit.parents.length > 1
%li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
%li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
- = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-primary btn-grouped" do
- %span Browse Code »
+ = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped" do
+ = icon('files-o')
+ Browse Files
%div
%p
%span.light Commit
= link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
+ = clipboard_button(clipboard_text: @commit.id)
.commit-info-row
%span.light Authored by
%strong
@@ -43,17 +45,17 @@
= link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
= ci_status_icon(@ci_commit)
build:
- = @ci_commit.status
+ = ci_status_label(@ci_commit)
.commit-info-row.branches
%i.fa.fa-spinner.fa-spin
.commit-box.gray-content-block.middle-block
%h3.commit-title
- = gfm escape_once(@commit.title)
+ = markdown escape_once(@commit.title), pipeline: :single_line
- if @commit.description.present?
%pre.commit-description
- = preserve(gfm(escape_once(@commit.description)))
+ = preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
:javascript
$(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml
index 00cf9c76102..99d62503a94 100644
--- a/app/views/projects/commit/builds.html.haml
+++ b/app/views/projects/commit/builds.html.haml
@@ -3,70 +3,4 @@
= render "commit_box"
= render "ci_menu"
-
-- if @ci_commit.yaml_errors.present?
- .bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
- %ul
- - @ci_commit.yaml_errors.split(",").each do |error|
- %li= error
-
-- unless @ci_commit.ci_yaml_file
- .bs-callout.bs-callout-warning
- \.gitlab-ci.yml not found in this commit
-
-.gray-content-block.second-block
- Latest builds
-
- .pull-right
- - if @ci_commit.duration > 0
- %i.fa.fa-time
- #{time_interval_in_words @ci_commit.duration}
-
- &nbsp;
-
- - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
- - if @ci_commit.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-primary', method: :post
-
- - if @ci_commit.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger', method: :post
-
-.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
- - @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?), stage: true, 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?), stage: true }
+= render "builds"
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 85e203cbe57..069b8b1f169 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,6 +1,9 @@
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
= render "projects/commits/header_title"
= render "commit_box"
-= render "ci_menu" if @ci_commit
+- if @ci_commit
+ = render "ci_menu"
+- else
+ %div.block-connector
= render "projects/diffs/diffs", diffs: @diffs, project: @project
= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml
index 9a0e7bff3f1..74a05df24d3 100644
--- a/app/views/projects/commit_statuses/_commit_status.html.haml
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -1,24 +1,29 @@
%tr.commit_status
%td.status
- = ci_status_with_icon(commit_status.status)
+ - if commit_status.target_url
+ = link_to commit_status.target_url, class: "ci-status ci-#{commit_status.status}" do
+ = ci_icon_for_status(commit_status.status)
+ = commit_status.status
+ - else
+ = ci_status_with_icon(commit_status.status)
%td.commit_status-link
- if commit_status.target_url
= link_to commit_status.target_url do
- %strong Build ##{commit_status.id}
+ %strong ##{commit_status.id}
- else
- %strong Build ##{commit_status.id}
+ %strong ##{commit_status.id}
- if commit_status.show_warning?
%i.fa.fa-warning.text-warning
- if defined?(commit_sha) && commit_sha
%td
- = link_to commit_status.short_sha, namespace_project_commit_path(@project.namespace, @project, commit_status.sha), class: "monospace"
-
+ = link_to commit_status.short_sha, namespace_project_commit_path(commit_status.project.namespace, commit_status.project, commit_status.sha), class: "monospace"
+
%td
- if commit_status.ref
- = link_to commit_status.ref, namespace_project_commits_path(@project.namespace, @project, commit_status.ref)
+ = link_to commit_status.ref, namespace_project_commits_path(commit_status.project.namespace, commit_status.project, commit_status.ref)
- else
.light none
@@ -61,10 +66,10 @@
%td
.pull-right
- - if current_user && can?(current_user, :download_build_artifacts, @project) && commit_status.download_url
+ - if current_user && can?(current_user, :download_build_artifacts, commit_status.project) && commit_status.download_url
= link_to commit_status.download_url, title: 'Download artifacts' do
%i.fa.fa-download
- - if current_user && can?(current_user, :manage_builds, commit_status.gl_project)
+ - if current_user && can?(current_user, :manage_builds, commit_status.project)
- if commit_status.active?
- if commit_status.cancel_url
= link_to commit_status.cancel_url, method: :post, title: 'Cancel' do
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 805be332e64..28b82dd31f3 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -9,7 +9,7 @@
- cache_key.push(ci_commit.status) if ci_commit
= cache(cache_key) do
- %li.commit.js-toggle-container
+ %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title
%strong.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
@@ -20,8 +20,8 @@
- if ci_commit
= render_ci_status(ci_commit)
&nbsp;
- = clipboard_button
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id", data: {clipboard_text: commit.id}
+ = clipboard_button(clipboard_text: commit.id)
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
.notes_count
- if note_count > 0
@@ -32,7 +32,7 @@
- if commit.description?
.commit-row-description.js-toggle-content
%pre
- = preserve(gfm(escape_once(commit.description)))
+ = preserve(markdown(escape_once(commit.description), pipeline: :single_line))
.commit-row-info
= commit_author_link(commit, avatar: true, size: 24)
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index f11a41cfd7b..fcccb002d7e 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -3,6 +3,11 @@
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
Commits
%span.badge= number_with_delimiter(@repository.commit_count)
+
+ = nav_link(controller: %w(network)) do
+ = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
+ Network
+
= nav_link(controller: :compare) do
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
Compare
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 268b9b815ee..7ffa7317196 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -17,7 +17,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.name commit.author_name
xml.email commit.author_email
end
- xml.summary gfm(commit.description)
+ xml.summary markdown(commit.description, pipeline: :single_line)
end
end
end
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 39755efd2fd..51088a7dea8 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -7,11 +7,11 @@
= render "form"
- if @commits.present?
- .prepend-top-20
+ .prepend-top-default
= render "projects/commits/commit_list"
= render "projects/diffs/diffs", diffs: @diffs, project: @project
- else
- .light-well.prepend-top-20
+ .light-well.prepend-top-default
.center
%h4
There isn't anything to compare.
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 91675b3738e..5e182af2669 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,5 +1,5 @@
%div
- = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
-if @key.errors.any?
.alert.alert-danger
%ul
@@ -8,16 +8,15 @@
.form-group
= f.label :title, class: "control-label"
- .col-sm-10= f.text_field :title, class: 'form-control'
+ .col-sm-10= f.text_field :title, class: 'form-control', autofocus: true, required: true
.form-group
= f.label :key, class: "control-label"
.col-sm-10
%p.light
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh", "README")
- = f.text_area :key, class: "form-control thin_area", rows: 5
+ = f.text_area :key, class: "form-control thin_area", rows: 5, required: true
.form-actions
- = f.submit 'Create', class: "btn-create btn"
+ = f.submit 'Create Deploy Key', class: "btn-create btn"
= link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel"
-
diff --git a/app/views/projects/deploy_keys/new.html.haml b/app/views/projects/deploy_keys/new.html.haml
index 01c810aee18..01fab3008a7 100644
--- a/app/views/projects/deploy_keys/new.html.haml
+++ b/app/views/projects/deploy_keys/new.html.haml
@@ -1,5 +1,5 @@
- page_title "New Deploy Key"
-%h3.page-title New Deploy key
+%h3.page-title New Deploy Key
%hr
= render 'form'
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 416fb4da071..f9d661d59d2 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -3,7 +3,7 @@
- diff_files = safe_diff_files(diffs)
-.gray-content-block.second-block.oneline-block
+.gray-content-block.middle-block.oneline-block
.inline-parallel-buttons
.btn-group
= inline_diff_btn
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c745b4e69bf..327e7d9245a 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -2,22 +2,29 @@
.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)
- = submodule_link(submodule_item, @commit.id, project.repository)
+ = icon('archive fw')
+ %strong
+ = submodule_link(blob, @commit.id, project.repository)
- else
%span
+ = blob_icon blob.mode, blob.name
+ = link_to "#diff-#{i}" do
+ %strong
+ = diff_file.new_path
+
- if diff_file.deleted_file
- = "#{diff_file.old_path} deleted"
+ deleted
- elsif diff_file.renamed_file
- = "#{diff_file.old_path} renamed to #{diff_file.new_path}"
- - else
- = diff_file.new_path
+ renamed from
+ %strong
+ = diff_file.old_path
- if diff_file.mode_changed?
- %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
+ %small
+ = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
.diff-controls
- - if blob.text?
+ - if blob_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do
%i.fa.fa-comments
&nbsp;
@@ -32,7 +39,7 @@
.diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?')
- - if blob.text?
+ - if blob_viewable?(blob)
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3ebc175648e..650629ef1b9 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -14,7 +14,7 @@
= f.label :name, class: 'control-label' do
Project name
.col-sm-10
- = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
+ = f.text_field :name, class: "form-control", id: "project_name_edit"
.form-group
@@ -22,12 +22,12 @@
Project description
%span.light (optional)
.col-sm-10
- = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250
+ = f.text_area :description, class: "form-control", rows: 3, maxlength: 250
- - if @project.repository.exists? && @project.repository.branch_names.any?
+ - unless @project.empty_repo?
.form-group
= f.label :default_branch, "Default Branch", class: 'control-label'
- .col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'})
+ .col-sm-10= f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'})
= render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project
@@ -35,7 +35,7 @@
.form-group
= f.label :tag_list, "Tags", class: 'control-label'
.col-sm-10
- = f.text_field :tag_list, maxlength: 2000, class: "form-control"
+ = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
%p.help-block Separate tags with commas.
%fieldset.features
@@ -112,6 +112,62 @@
%hr
= link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
+ %fieldset.features
+ %legend
+ Continuous Integration
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ %p Get recent application code using the following command:
+ .radio
+ = f.label :build_allow_git_fetch do
+ = f.radio_button :build_allow_git_fetch, 'false'
+ %strong git clone
+ %br
+ %span.descr Slower but makes sure you have a clean dir before every build
+ .radio
+ = f.label :build_allow_git_fetch do
+ = f.radio_button :build_allow_git_fetch, 'true'
+ %strong git fetch
+ %br
+ %span.descr Faster
+ .form-group
+ = f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label'
+ .col-sm-10
+ = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
+ %p.help-block per build in minutes
+ .form-group
+ = f.label :build_coverage_regex, "Test coverage parsing", class: 'control-label'
+ .col-sm-10
+ .input-group
+ %span.input-group-addon /
+ = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
+ %span.input-group-addon /
+ %p.help-block
+ We will use this regular expression to find test coverage output in build trace.
+ Leave blank if you want to disable this feature
+ .bs-callout.bs-callout-info
+ %p Below are examples of regex for existing tools:
+ %ul
+ %li
+ Simplecov (Ruby) -
+ %code \(\d+.\d+\%\) covered
+ %li
+ pytest-cov (Python) -
+ %code \d+\%\s*$
+ %li
+ phpunit --coverage-text --colors=never (PHP) -
+ %code ^\s*Lines:\s*\d+.\d+\%
+
+
+ %fieldset.features
+ %legend
+ Advanced settings
+ .form-group
+ = f.label :runners_token, "CI token", class: 'control-label'
+ .col-sm-10
+ = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
+ %p.help-block The secure token used to checkout project.
+
.form-actions
= f.submit 'Save changes', class: "btn btn-save"
@@ -130,9 +186,11 @@
The project can be committed to.
%br
%strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive', unarchive_namespace_project_path(@project.namespace, @project),
- data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
- method: :post, class: "btn btn-success"
+
+ .form-actions
+ = link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project),
+ data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
+ method: :post, class: "btn btn-success"
- else
.panel.panel-warning
.panel-heading
@@ -144,9 +202,11 @@
It is hidden from the dashboard and doesn't show up in searches.
%br
%strong Archived projects cannot be committed to!
- = link_to 'Archive', archive_namespace_project_path(@project.namespace, @project),
- data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
- method: :post, class: "btn btn-warning"
+
+ .form-actions
+ = link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project),
+ data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
+ method: :post, class: "btn btn-warning"
- else
.nothing-here-block Only the project owner can archive a project
@@ -160,7 +220,7 @@
Project name
.col-sm-9
.form-group
- = f.text_field :name, placeholder: "Example Project", class: "form-control"
+ = f.text_field :name, class: "form-control"
.form-group
= f.label :path, class: 'control-label' do
%span Path
@@ -170,12 +230,11 @@
.input-group-addon
#{URI.join(root_url, @project.namespace.path)}/
= f.text_field :path, class: 'form-control'
- %span.input-group-addon .git
%ul
%li Be careful. Renaming a project's repository can have unintended side effects.
%li You will need to update your local repositories to point to the new location.
.form-actions
- = f.submit 'Rename', class: "btn btn-warning"
+ = f.submit 'Rename project', class: "btn btn-warning"
- if can?(current_user, :change_namespace, @project)
.panel.panel-default.panel.panel-danger
@@ -194,7 +253,7 @@
%li You can only transfer the project to namespaces you manage.
%li You will need to update your local repositories to point to the new location.
.form-actions
- = f.submit 'Transfer', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
+ = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- else
.nothing-here-block Only the project owner can transfer a project
@@ -209,7 +268,8 @@
#{link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)}.
%br
%strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
- = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+ .form-actions
+ = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
- else
.nothing-here-block Only the project owner can remove the fork relationship.
@@ -222,8 +282,8 @@
Removing the project will delete its repository and all related resources including issues, merge requests etc.
%br
%strong Removed projects cannot be restored!
-
- = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
+ .form-actions
+ = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
- else
.nothing-here-block Only the project owner can remove a project.
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index c3858e78cad..503d156661e 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,21 +1,20 @@
-.alert_holder
+= content_for :flash_message do
- if current_user && can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
= render 'shared/no_password'
= render "home_panel"
-.gray-content-block.center
+.gray-content-block.second-block.center
%h3.page-title
The repository for this project is empty
- - if can?(current_user, :download_code, @project)
+ - if can?(current_user, :push_code, @project)
%p
If you already have files you can push them using command line instructions below.
- %br
- - if can?(current_user, :push_code, @project)
- Otherwise you can start with
- = link_to "adding README", new_readme_path, class: 'underlined-link'
- file to this project.
+ %p
+ Otherwise you can start with
+ = link_to "adding README", new_readme_path, class: 'underlined-link'
+ file to this project.
- if can?(current_user, :download_code, @project)
.prepend-top-20
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 03d0733f913..a47643bd09c 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -3,6 +3,8 @@
= link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do
= link_to 'Commits', commits_namespace_project_graph_path
+ = nav_link(action: :languages) do
+ = link_to 'Languages', languages_namespace_project_graph_path
- if @project.builds_enabled?
= nav_link(action: :ci) do
= link_to ci_namespace_project_graph_path do
diff --git a/app/views/projects/graphs/ci/_overall.haml b/app/views/projects/graphs/ci/_overall.haml
index cf4285a2671..4b12e5f2da1 100644
--- a/app/views/projects/graphs/ci/_overall.haml
+++ b/app/views/projects/graphs/ci/_overall.haml
@@ -1,20 +1,19 @@
-- ci_project = @project.gitlab_ci_project
%h4 Overall stats
%ul
%li
Total:
- %strong= pluralize ci_project.builds.count(:all), 'build'
+ %strong= pluralize @project.builds.count(:all), 'build'
%li
Successful:
- %strong= pluralize ci_project.builds.success.count(:all), 'build'
+ %strong= pluralize @project.builds.success.count(:all), 'build'
%li
Failed:
- %strong= pluralize ci_project.builds.failed.count(:all), 'build'
+ %strong= pluralize @project.builds.failed.count(:all), 'build'
%li
Success ratio:
%strong
- #{success_ratio(ci_project.builds.success, ci_project.builds.failed)}%
+ #{success_ratio(@project.builds.success, @project.builds.failed)}%
%li
Commits covered:
%strong
- = ci_project.commits.count(:all)
+ = @project.ci_commits.count(:all)
diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml
new file mode 100644
index 00000000000..a7fab5b6d72
--- /dev/null
+++ b/app/views/projects/graphs/languages.html.haml
@@ -0,0 +1,32 @@
+- page_title "Languages", "Graphs"
+= render "header_title"
+= render 'head'
+
+.gray-content-block.append-bottom-default
+ .oneline
+ Programming languages used in this repository
+
+.row
+ .col-md-8
+ %canvas#languages-chart{ height: 400 }
+ .col-md-4
+ %ul.bordered-list
+ - @languages.each do |language|
+ %li
+ %span{ style: "color: #{language[:color]}" }
+ = icon('circle')
+ &nbsp;
+ = language[:label]
+ .pull-right
+ = language[:value]
+ \%
+
+:javascript
+ var data = #{@languages.to_json};
+ var ctx = $("#languages-chart").get(0).getContext("2d");
+ var options = {
+ scaleOverlay: true,
+ responsive: true,
+ maintainAspectRatio: false
+ }
+ var myPieChart = new Chart(ctx).Pie(data, options);
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 3702aeaecba..b18d9197d0b 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -55,6 +55,13 @@
%strong Merge Request events
%p.light
This url will be triggered when a merge request is created
+ %div
+ = f.check_box :build_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :build_events, class: 'list-label' do
+ %strong Build events
+ %p.light
+ This url will be triggered when the build status changes
.form-group
= f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
.col-sm-10
@@ -78,7 +85,7 @@
.clearfix
%span.monospace= hook.url
%p
- - %w(push_events tag_push_events issues_events note_events merge_requests_events).each do |trigger|
+ - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
- if hook.send(trigger)
%span.label.label-gray= trigger.titleize
SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml
index aef352029d0..de415ae51a4 100644
--- a/app/views/projects/issues/_closed_by_box.html.haml
+++ b/app/views/projects/issues/_closed_by_box.html.haml
@@ -1,3 +1,2 @@
-.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.
+.issue-closed-by-widget.gray-content-block.second-block.white
+ This issue will be closed automatically when merge request #{markdown(merge_requests_sentence(@closed_by_merge_requests), pipeline: :gfm)} is accepted.
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 020952dd001..86d3dc546ba 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -5,34 +5,5 @@
- else
= link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue'
-= render 'shared/show_aside'
-
-.gray-content-block.second-block.oneline-block
- .row
- .col-md-9
- .votes-holder.pull-right
- #votes= render 'votes/votes_block', votable: @issue
- .participants
- %span= pluralize(@participants.count, 'participant')
- - @participants.each do |participant|
- = link_to_member(@project, participant, name: false, size: 24)
- .col-md-3
- .input-group.cross-project-reference
- %span.slead.has_tooltip{title: 'Cross-project reference'}
- = cross_project_reference(@project, @issue)
- = clipboard_button
-
-.row
- %section.col-md-9
- .voting_notes#notes= render 'projects/notes/notes_with_form'
- %aside.col-md-3
- .issuable-affix
- .context
- = render 'shared/issuable/context', issuable: @issue
-
- - if @issue.labels.any?
- .issuable-context-title
- %label Labels
- .issue-show-labels
- - @issue.labels.each do |label|
- = link_to_label(label)
+#notes
+ = render 'projects/notes/notes_with_form'
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index f39bb7d2574..6588d9bdbe1 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,9 +1,5 @@
-%div.issue-form-holder
- %h3.page-title= @issue.new_record? ? "Create Issue" : "Edit Issue ##{@issue.iid}"
- %hr
-
- = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
- = render 'shared/issuable/form', f: f, issuable: @issue
+= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-requires-input' } do |f|
+ = render 'shared/issuable/form', f: f, issuable: @issue
:javascript
$('.assign-to-me-link').on('click', function(e){
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index d7657ee7e40..f9cf4910df3 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -6,35 +6,42 @@
.issue-title
%span.issue-title-text
= link_to_gfm issue.title, issue_path(issue), class: "row_title"
- .issue-labels
- - issue.labels.each do |label|
- = link_to_label(label, project: issue.project)
- .pull-right.light
+ %ul.controls.light
- if issue.closed?
- %span
+ %li
CLOSED
+
- if issue.assignee
- = link_to_member(@project, issue.assignee, name: false)
+ %li
+ = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
+
- note_count = issue.notes.user.count
- if note_count > 0
- &nbsp;
- %span
- %i.fa.fa-comments
- = note_count
+ %li
+ = link_to issue_path(issue) + "#notes" do
+ = icon('comments')
+ = note_count
- else
- &nbsp;
- %span.issue-no-comments
- %i.fa.fa-comments
- = 0
+ %li
+ = link_to issue_path(issue) + "#notes", class: "issue-no-comments" do
+ = icon('comments')
+ = note_count
.issue-info
- = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
+ #{issue.to_reference} &middot;
+ opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, issue.author, avatar: false)}
- if issue.milestone
&nbsp;
- %span
- %i.fa.fa-clock-o
+ = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
+ = icon('clock-o')
= issue.milestone.title
+ - if issue.labels.any?
+ &nbsp;
+ - issue.labels.each do |label|
+ = link_to_label(label, project: issue.project)
- if issue.tasks?
+ &nbsp;
%span.task-status
= issue.task_status
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
new file mode 100644
index 00000000000..254968e4f67
--- /dev/null
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -0,0 +1,26 @@
+-if @merge_requests.any?
+ %h2.merge-requests-title
+ = pluralize(@merge_requests.count, 'Related Merge Request')
+ %ul.bordered-list
+ - has_any_ci = @merge_requests.any?(&:ci_commit)
+ - @merge_requests.each do |merge_request|
+ %li
+ %span.merge-request-ci-status
+ - if merge_request.ci_commit
+ = render_ci_status(merge_request.ci_commit)
+ - elsif has_any_ci
+ = icon('blank fw')
+ %span.merge-request-id
+ \##{merge_request.iid}
+ %span.merge-request-info
+ %strong
+ = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
+ - unless @issue.project.id == merge_request.target_project.id
+ in
+ - project = merge_request.target_project
+ = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
+ %span.merge-request-status.prepend-left-10
+ - if merge_request.merged?
+ MERGED
+ - elsif merge_request.closed?
+ CLOSED
diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
index 53b6f0879c9..20216297d25 100644
--- a/app/views/projects/issues/edit.html.haml
+++ b/app/views/projects/issues/edit.html.haml
@@ -1,2 +1,8 @@
- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues"
+= render "header_title"
+
+%h3.page-title
+ Edit Issue ##{@issue.iid}
+%hr
+
= render "form"
diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml
index 153447baa1b..b317a0c1cf4 100644
--- a/app/views/projects/issues/new.html.haml
+++ b/app/views/projects/issues/new.html.haml
@@ -1,4 +1,8 @@
- page_title "New Issue"
= render "header_title"
+%h3.page-title
+ New Issue
+%hr
+
= render "form"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index f01bf2505da..2fe6f88b2a9 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -2,51 +2,68 @@
= render "header_title"
.issue
- .issue-details.issuable-details
- .page-title
- .issue-box{ class: issue_box_class(@issue) }
+ .detail-page-header
+ .status-box{ class: status_box_class(@issue) }
+ - if @issue.closed?
+ Closed
+ - else
+ Open
+ %span.identifier
+ Issue ##{@issue.iid}
+ %span.creator
+ &middot;
+ opened by #{link_to_member(@project, @issue.author, size: 24)}
+ &middot;
+ = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
+ - if @issue.updated_at != @issue.created_at
+ %span
+ &middot;
+ = icon('edit', title: 'edited')
+ = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
+
+ .pull-right
+ - if can?(current_user, :create_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do
+ = icon('plus')
+ New Issue
+ - if can?(current_user, :update_issue, @issue)
- if @issue.closed?
- Closed
+ = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen'
- else
- Open
- %span.issue-id Issue ##{@issue.iid}
- %span.creator
- &middot; created by #{link_to_member(@project, @issue.author, size: 24)}
- &middot;
- = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- - if @issue.updated_at != @issue.created_at
- %span
- &middot;
- = icon('edit', title: 'edited')
- = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
-
- .pull-right
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do
- = icon('plus')
- New Issue
- - if can?(current_user, :update_issue, @issue)
- - if @issue.closed?
- = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen'
- - else
- = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close', title: 'Close Issue'
-
- = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do
- = icon('pencil-square-o')
- Edit
-
- .gray-content-block.middle-block
- %h2.issue-title
- = gfm escape_once(@issue.title)
+ = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close', title: 'Close Issue'
+
+ = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do
+ = icon('pencil-square-o')
+ Edit
+
+ .issue-details.issuable-details
+ .detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@issue.title), pipeline: :single_line
%div
- if @issue.description.present?
.description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
.wiki
= preserve do
- = markdown(@issue.description)
+ = markdown(@issue.description, cache_key: [@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'
+
+ .merge-requests
+ = render 'merge_requests'
+
+ .gray-content-block.second-block.oneline-block
+ = render 'votes/votes_block', votable: @issue
+
+ - if @closed_by_merge_requests.present?
+ = render 'projects/issues/closed_by_box'
+
+ .row
+ %section.col-md-9
+ .issuable-discussion
+ = render 'projects/issues/discussion'
+
+ %aside.col-md-3
+ = render 'shared/issuable/sidebar', issuable: @issue
+
+ = render 'shared/show_aside'
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index b7735aaf3c1..2f0f3fcfb06 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -1,3 +1,3 @@
-$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @issue)}");
-$('.context').effect('highlight')
+$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}");
+$('.issuable-sidebar').parent().effect('highlight')
new Issue();
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 4cf13492e99..5ce2a7b985d 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -10,9 +10,9 @@
.form-group
= f.label :title, class: 'control-label'
.col-sm-10
- = f.text_field :title, class: "form-control js-quick-submit", required: true
+ = f.text_field :title, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group
- = f.label :color, "Background Color", class: 'control-label'
+ = f.label :color, "Background color", class: 'control-label'
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
@@ -28,6 +28,8 @@
&nbsp;
.form-actions
- = f.submit 'Save', class: 'btn btn-save js-save-button'
+ - if @label.persisted?
+ = f.submit 'Save changes', class: 'btn btn-save js-save-button'
+ - else
+ = f.submit 'Create Label', class: 'btn btn-create js-save-button'
= link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel'
-
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index c6ebfa281a1..b70a9fc9fe5 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -7,4 +7,4 @@
- if can? current_user, :admin_label, @project
= link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm'
- = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+ = link_to 'Delete', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index bc4ab0ca27c..675a805e12f 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,11 +1,7 @@
- page_title "Edit", @label.name, "Labels"
= render "header_title"
-%h3
- Edit label
- %span.light #{@label.name}
-.back-link
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- &larr; To labels list
+%h3.page-title
+ Edit Label
%hr
= render 'form'
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index fb784ee5f4f..9081bcfe9b3 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -4,6 +4,7 @@
.gray-content-block.top-block
- if can? current_user, :admin_label, @project
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do
+ = icon('plus')
New label
.oneline
Labels can be applied to issues and merge requests.
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 342ad4f3f95..e20fd7d6891 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,9 +1,7 @@
- page_title "New Label"
= render "header_title"
-%h3 New label
-.back-link
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- &larr; To labels list
+%h3.page-title
+ New Label
%hr
= render 'form'
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 7e60782ff5b..399e9cc1e1b 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -5,29 +5,4 @@
- if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
-= render 'shared/show_aside'
-
-.gray-content-block.second-block.oneline-block
- .row
- .col-md-9
- .votes-holder.pull-right
- #votes= render 'votes/votes_block', votable: @merge_request
- = render "projects/merge_requests/show/participants"
- .col-md-3
- %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
- = cross_project_reference(@project, @merge_request)
-
-.row
- %section.col-md-9
- = render "projects/notes/notes_with_form"
- %aside.col-md-3
- .issuable-affix
- .context
- = render 'shared/issuable/context', issuable: @merge_request
-
- - if @merge_request.labels.any?
- .issuable-context-title
- %label Labels
- .merge-request-show-labels
- - @merge_request.labels.each do |label|
- = link_to_label(label)
+#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 9cf389dbe38..3e4ab09c6d4 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,6 +1,5 @@
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
- .merge-request-form-info
- = render 'shared/issuable/form', f: f, issuable: @merge_request
+ = render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript
$('.assign-to-me-link').on('click', function(e){
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 83e8ad11989..105c731c7e1 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,50 +1,61 @@
-- ci_commit = merge_request.ci_commit
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
%span.merge-request-title-text
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
- .merge-request-labels
- - merge_request.labels.each do |label|
- = link_to_label(label, project: merge_request.project)
- .pull-right.light
- - if ci_commit
- = render_ci_status(ci_commit)
+ %ul.controls.light
- if merge_request.merged?
- %span
- %i.fa.fa-check
+ %li
MERGED
- elsif merge_request.closed?
- %span
- %i.fa.fa-ban
+ %li
+ = icon('ban')
CLOSED
- - note_count = merge_request.mr_and_commit_notes.user.count
+
+ - if merge_request.ci_commit
+ %li
+ = render_ci_status(merge_request.ci_commit)
+
+ - if merge_request.open? && merge_request.broken?
+ %li
+ = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: {container: 'body'} do
+ = icon('exclamation-triangle')
+
- if merge_request.assignee
- &nbsp;
- = link_to_member(merge_request.source_project, merge_request.assignee, name: false)
+ %li
+ = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
+
+ - note_count = merge_request.mr_and_commit_notes.user.count
- if note_count > 0
- &nbsp;
- %span
- %i.fa.fa-comments
- = note_count
+ %li
+ = link_to merge_request_path(merge_request) + "#notes" do
+ = icon('comments')
+ = note_count
- else
- &nbsp;
- %span.merge-request-no-comments
- %i.fa.fa-comments
- = 0
+ %li
+ = link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do
+ = icon('comments')
+ = note_count
.merge-request-info
- = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
- - if merge_request.milestone_id?
- &nbsp;
- %span
- %i.fa.fa-clock-o
- = merge_request.milestone.title
+ \##{merge_request.iid} &middot;
+ opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, merge_request.author, avatar: false)}
- if merge_request.target_project.default_branch != merge_request.target_branch
&nbsp;
- %span
- %i.fa.fa-code-fork
+ = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do
+ = icon('code-fork')
= merge_request.target_branch
+ - if merge_request.milestone
+ &nbsp;
+ = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
+ = icon('clock-o')
+ = merge_request.milestone.title
+ - if merge_request.labels.any?
+ &nbsp;
+ - merge_request.labels.each do |label|
+ = link_to_label(label, project: merge_request.project)
- if merge_request.tasks?
+ &nbsp;
%span.task-status
= merge_request.task_status
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index d9eff1f9320..236a545c840 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -1,4 +1,5 @@
-%p.lead Compare branches for new Merge Request
+%h3.page-title
+ New Merge Request
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors
@@ -10,7 +11,7 @@
.panel-body
= f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
- = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2', required: true})
+ = f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } })
.panel-footer
.mr_source_commit
@@ -22,7 +23,7 @@
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
= f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2', required: true})
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } })
.panel-footer
.mr_target_commit
@@ -37,7 +38,7 @@
%h4 Compare failed
%p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
- else
- .light-well.append-bottom-10
+ .light-well.append-bottom-default
.center
%h4
There isn't anything to merge.
@@ -51,8 +52,8 @@
are the same.
- %div
- = f.submit 'Compare branches', class: "btn btn-new mr-compare-btn"
+ .form-actions
+ = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
:javascript
var source_branch = $("#merge_request_source_branch")
@@ -86,4 +87,3 @@
return;
}
});
-
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 6244d3ba0b4..a14943b15d3 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -1,5 +1,5 @@
%h3.page-title
- New merge request
+ New Merge Request
%p.slead
- source_title, target_title = format_mr_branch_names(@merge_request)
From
@@ -11,27 +11,31 @@
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
- .merge-request-form-info
- = render 'shared/issuable/form', f: f, issuable: @merge_request
- = f.hidden_field :source_project_id
- = f.hidden_field :source_branch
- = f.hidden_field :target_project_id
- = f.hidden_field :target_branch
+ = render 'shared/issuable/form', f: f, issuable: @merge_request
+ = f.hidden_field :source_project_id
+ = f.hidden_field :source_branch
+ = f.hidden_field :target_project_id
+ = f.hidden_field :target_branch
.mr-compare.merge-request
- %ul.merge-request-tabs
+ %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.commits-tab
- = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
+ = link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
+ - if @ci_commit
+ %li.builds-tab.active
+ = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
+ Builds
+ %span.badge= @statuses.size
%li.diffs-tab.active
- = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
+ = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge= @diffs.size
.tab-content
#commits.commits.tab-pane
- = render "projects/commits/commits", project: @project
+ = render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane.active
- if @diffs.present?
= render "projects/diffs/diffs", diffs: @diffs, project: @project
@@ -43,6 +47,9 @@
.alert.alert-danger
%h4 This comparison includes a huge diff.
%p To preserve performance the line changes are not shown.
+ - if @ci_commit
+ #builds.builds.tab-pane
+ = render "projects/merge_requests/show/builds"
:javascript
$('.assign-to-me-link').on('click', function(e){
@@ -57,4 +64,3 @@
diffs_loaded: true,
commits_loaded: true
});
-
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index eeaa72ed21b..e9ffbd06be2 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -5,10 +5,11 @@
- fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)}
+ = render "projects/merge_requests/show/mr_title"
+
.merge-request-details.issuable-details
- = render "projects/merge_requests/show/mr_title"
= render "projects/merge_requests/show/mr_box"
- .append-bottom-20.mr-source-target.prepend-top-default
+ .append-bottom-default.mr-source-target.prepend-top-default
- if @merge_request.open?
.pull-right
- if @merge_request.source_branch_exists?
@@ -26,44 +27,62 @@
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
.normal
%span Request to merge
- %span.label-branch #{source_branch_with_namespace(@merge_request)}
+ %span.label-branch= source_branch_with_namespace(@merge_request)
%span into
- %span.label-branch #{@merge_request.target_branch}
+ = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
+ = @merge_request.target_branch
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
- - if @merge_request.open? && @merge_request.can_be_merged?
- .light.append-bottom-20
+ - if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
+ .light.prepend-top-default
You can also accept this merge request manually using the
= succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- - if @commits.present?
- %ul.merge-request-tabs
- %li.notes-tab
- = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
- Discussion
- %span.badge= @merge_request.mr_and_commit_notes.user.count
- %li.commits-tab
- = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
- Commits
- %span.badge= @commits.size
- %li.diffs-tab
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
- Changes
- %span.badge= @merge_request.diffs.size
+ - if @commits.present?
+ %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
+ %li.notes-tab
+ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
+ Discussion
+ %span.badge= @merge_request.mr_and_commit_notes.user.count
+ %li.commits-tab
+ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
+ Commits
+ %span.badge= @commits.size
+ - if @ci_commit
+ %li.builds-tab
+ = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
+ Builds
+ %span.badge= @statuses.size
+ %li.diffs-tab
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
+ Changes
+ %span.badge= @merge_request.diffs.size
+
+ .tab-content
+ #notes.notes.tab-pane.voting_notes
+ .gray-content-block.second-block.oneline-block
+ = render 'votes/votes_block', votable: @merge_request
+
+ .row
+ %section.col-md-9
+ .issuable-discussion
+ = render "projects/merge_requests/discussion"
+ %aside.col-md-3
+ = render 'shared/issuable/sidebar', issuable: @merge_request
+ = render 'shared/show_aside'
- .tab-content
- #notes.notes.tab-pane.voting_notes
- = render "projects/merge_requests/discussion"
- #commits.commits.tab-pane
- - # This tab is always loaded via AJAX
- #diffs.diffs.tab-pane
- - # This tab is always loaded via AJAX
+ #commits.commits.tab-pane
+ - # This tab is always loaded via AJAX
+ #builds.builds.tab-pane
+ - # This tab is always loaded via AJAX
+ #diffs.diffs.tab-pane
+ - # This tab is always loaded via AJAX
- .mr-loading-status
- = spinner
+ .mr-loading-status
+ = spinner
:javascript
var merge_request;
diff --git a/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml b/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml
new file mode 100644
index 00000000000..eab5be488b5
--- /dev/null
+++ b/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml
@@ -0,0 +1,2 @@
+:plain
+ $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/accept'))}");
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index 303ca0a880b..fc62bb5bce9 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -2,6 +2,6 @@
= render "header_title"
%h3.page-title
- = "Edit merge request ##{@merge_request.iid}"
+ Edit Merge Request ##{@merge_request.iid}
%hr
= render 'form'
diff --git a/app/views/projects/merge_requests/merge.js.haml b/app/views/projects/merge_requests/merge.js.haml
index 33321651e32..92ce479d463 100644
--- a/app/views/projects/merge_requests/merge.js.haml
+++ b/app/views/projects/merge_requests/merge.js.haml
@@ -1,6 +1,10 @@
-- if @status
+- case @status
+- when :success
:plain
- merge_request_widget.mergeInProgress();
+ merge_request_widget.mergeInProgress(#{params[:should_remove_source_branch] == '1'});
+- when :merge_when_build_succeeds
+ :plain
+ $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_build_succeeds'))}");
- else
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/reload'))}");
diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/new.html.haml
index 9fdde80c6d9..d259968030e 100644
--- a/app/views/projects/merge_requests/new.html.haml
+++ b/app/views/projects/merge_requests/new.html.haml
@@ -1,7 +1,7 @@
- page_title "New Merge Request"
= render "header_title"
-- if @merge_request.can_be_created
+- if @merge_request.can_be_created && !params[:change_branches]
= render 'new_submit'
- else
= render 'new_compare'
diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml
new file mode 100644
index 00000000000..307a75d02ca
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_builds.html.haml
@@ -0,0 +1 @@
+= render "projects/commit/builds", link_to_commit: true
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 478054db517..7f904ec42a0 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,4 +1,4 @@
-.gray-content-block.second-block.oneline-block
+.gray-content-block.middle-block.oneline-block
= icon("sort-amount-desc")
Most recent commits displayed first
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 98f0357ce4e..877cc3d744b 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -8,8 +8,8 @@
%p
%strong Step 1.
Fetch and check out the branch for this merge request
- = clipboard_button
- %pre.dark
+ = clipboard_button(clipboard_target: 'pre#merge-info-1')
+ %pre.dark#merge-info-1
- if @merge_request.for_fork?
:preserve
git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch}
@@ -25,8 +25,8 @@
%p
%strong Step 3.
Merge the branch and fix any conflicts that come up
- = clipboard_button
- %pre.dark
+ = clipboard_button(clipboard_target: 'pre#merge-info-3')
+ %pre.dark#merge-info-3
- if @merge_request.for_fork?
:preserve
git checkout #{h @merge_request.target_branch}
@@ -38,8 +38,8 @@
%p
%strong Step 4.
Push the result of the merge to GitLab
- = clipboard_button
- %pre.dark
+ = clipboard_button(clipboard_target: 'pre#merge-info-4')
+ %pre.dark#merge-info-4
:preserve
git push origin #{h @merge_request.target_branch}
- unless @merge_request.can_be_merged_by?(current_user)
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index b4f62a75890..0f81e5e8914 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,12 +1,12 @@
-.gray-content-block.middle-block
- %h2.issue-title
- = gfm escape_once(@merge_request.title)
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@merge_request.title), pipeline: :single_line
%div
- if @merge_request.description.present?
.description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
.wiki
= preserve do
- = markdown(@merge_request.description)
+ = markdown(@merge_request.description, cache_key: [@merge_request, "description"])
%textarea.hidden.js-task-list-field
= @merge_request.description
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 2bf9cd597a4..473124480ac 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,10 +1,11 @@
-.page-title
- .issue-box{ class: issue_box_class(@merge_request) }
+.detail-page-header
+ .status-box{ class: status_box_class(@merge_request) }
= @merge_request.state_human_name
- %span.issue-id Merge Request ##{@merge_request.iid}
+ %span.identifier
+ Merge Request ##{@merge_request.iid}
%span.creator
&middot;
- created by #{link_to_member(@project, @merge_request.author, size: 24)}
+ opened by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot;
= time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at
@@ -16,9 +17,9 @@
.issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.open?
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
- = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-grouped btn-close', title: 'Close merge request'
+ = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-grouped issuable-edit', id: 'edit_merge_request' do
%i.fa.fa-pencil-square-o
Edit
- if @merge_request.closed?
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/projects/merge_requests/show/_participants.html.haml
deleted file mode 100644
index c67afe963e7..00000000000
--- a/app/views/projects/merge_requests/show/_participants.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-.participants
- %span #{@participants.count} participants
- - @participants.each do |participant|
- = link_to_member(@project, participant, name: false, size: 24)
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index 25583b2cc6f..93db65ddf79 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -1,3 +1,3 @@
-$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @merge_request)}");
-$('.context').effect('highlight')
+$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}");
+$('.issuable-sidebar').parent().effect('highlight')
merge_request = new MergeRequest();
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index ba5ad22bca7..b05ab869215 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,30 +1,33 @@
-- ci_commit = @merge_request.ci_commit
-- if ci_commit
- - status = ci_commit.status
+- if @ci_commit
.mr-widget-heading
- .ci_widget{class: "ci-#{status}"}
- = ci_status_icon(ci_commit)
- %span CI build #{status}
- for #{@merge_request.last_commit_short_sha}.
+ .ci_widget{class: "ci-#{@ci_commit.status}"}
+ = ci_status_icon(@ci_commit)
+ %span
+ Build
+ = ci_status_label(@ci_commit)
+ for
+ = succeed "." do
+ = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
%span.ci-coverage
- = link_to "View build details", ci_status_path(ci_commit)
+ = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
- 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|
+ - %w[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}.
+ = ci_icon_for_status(status)
+ %span
+ CI build
+ = ci_label_for_status(status)
+ for
+ - commit = @merge_request.last_commit
+ = succeed "." do
+ = link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace"
%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 details_path = ci_build_details_path(@merge_request)
+ = link_to "View details", details_path, :"data-no-turbolink" => "data-no-turbolink"
.ci_widget
= icon("spinner spin")
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index a788fcea23f..d1d602eecdc 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -7,18 +7,16 @@
by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
%div
- - if !@merge_request.source_branch_exists?
- = succeed '.' do
- The changes were merged into
- %span.label-branch= @merge_request.target_branch
+ - if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true')
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
The source branch has been removed.
- - elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch)
+ - elsif @merge_request.can_remove_source_branch?(current_user)
.remove_source_branch_widget
%p
- = succeed '.' do
- The changes were merged into
- %span.label-branch= @merge_request.target_branch
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
You can remove the source branch now.
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
%i.fa.fa-times
@@ -31,7 +29,7 @@
.remove_source_branch_in_progress.hide
%p
= icon('spinner spin')
- Removing source branch '#{@merge_request.source_branch}'. Please wait. This page will be automatically reload.
+ Removing source branch '#{@merge_request.source_branch}'. Please wait, this page will be automatically reloaded.
:javascript
$('.remove_source_branch').on('click', function() {
@@ -48,5 +46,3 @@
$('.remove_source_branch_in_progress').hide();
$('.remove_source_branch_widget.failed').show();
});
-
-
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 0aad9bb3e88..55dbae598d3 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -13,6 +13,8 @@
= render 'projects/merge_requests/widget/open/conflicts'
- elsif @merge_request.work_in_progress?
= render 'projects/merge_requests/widget/open/wip'
+ - elsif @merge_request.merge_when_build_succeeds?
+ = render 'projects/merge_requests/widget/open/merge_when_build_succeeds'
- elsif !@merge_request.can_be_merged_by?(current_user)
= render 'projects/merge_requests/widget/open/not_allowed'
- elsif @merge_request.can_be_merged?
@@ -24,4 +26,4 @@
%i.fa.fa-check
Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
= succeed '.' do
- != gfm(issues_sentence(@closes_issues))
+ != markdown issues_sentence(@closes_issues), pipeline: :gfm
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index 9b31014b581..d9a1730a8bc 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -1,28 +1,60 @@
-- status_class = @merge_request.ci_commit ? " ci-#{@merge_request.ci_commit.status}" : nil
+- status_class = @ci_commit ? " ci-#{@ci_commit.status}" : nil
= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token
.accept-merge-holder.clearfix.js-toggle-container
- .accept-action
- = f.button class: "btn btn-create accept_merge_request#{status_class}" do
- Accept Merge Request
- - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
- .accept-control.checkbox
- = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
- = check_box_tag :should_remove_source_branch
- Remove source branch
- .accept-control.right
- = link_to "#", class: "modify-merge-commit-link js-toggle-button" do
- = icon('edit')
- Modify commit message
- .js-toggle-content.hide.prepend-top-20
+ .clearfix
+ .accept-action
+ - if @ci_commit && @ci_commit.active?
+ %span.btn-group
+ = button_tag class: "btn btn-create js-merge-button merge_when_build_succeeds" do
+ Merge When Build Succeeds
+ = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do
+ %span.caret
+ %span.sr-only
+ Select Merge Moment
+ %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
+ %li
+ = link_to "#", class: "merge_when_build_succeeds" do
+ = icon('check fw')
+ Merge When Build Succeeds
+ %li
+ = link_to "#", class: "accept_merge_request" do
+ = icon('warning fw')
+ Merge Immediately
+ - else
+ = f.button class: "btn btn-create btn-grouped js-merge-button accept_merge_request #{status_class}" do
+ Accept Merge Request
+ - if @merge_request.can_remove_source_branch?(current_user)
+ .accept-control.checkbox
+ = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
+ = check_box_tag :should_remove_source_branch
+ Remove source branch
+ .accept-control.right
+ = link_to "#", class: "modify-merge-commit-link js-toggle-button" do
+ = icon('edit')
+ Modify commit message
+ .js-toggle-content.hide.prepend-top-default
= render 'shared/commit_message_container', params: params,
text: @merge_request.merge_commit_message,
rows: 14, hint: true
+ = hidden_field_tag :merge_when_build_succeeds, "", autocomplete: "off"
+
:javascript
- $('.accept-mr-form').on('ajax:before', function() {
- var btn = $('.accept_merge_request');
- btn.disable();
- btn.html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
+ $('.accept-mr-form').on('ajax:send', function() {
+ $(".accept-mr-form :input").disable();
+ });
+
+ $('.accept_merge_request').on('click', function() {
+ $('.js-merge-button').html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
+ });
+
+ $('.merge_when_build_succeeds').on('click', function() {
+ $("#merge_when_build_succeeds").val("1");
+ });
+
+ $('.js-merge-dropdown a').on('click', function(e) {
+ e.preventDefault();
+ $(this).closest("form").submit();
});
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
new file mode 100644
index 00000000000..2168294c683
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
@@ -0,0 +1,26 @@
+%h4
+ Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
+ to be merged automatically when the build succeeds.
+%div
+ - should_remove_source_branch = @merge_request.merge_params["should_remove_source_branch"].present?
+ %p
+ = succeed '.' do
+ The changes will be merged into
+ %span.label-branch= @merge_request.target_branch
+ - if should_remove_source_branch
+ The source branch will be removed.
+ - else
+ The source branch will not be removed.
+
+ - remove_source_branch_button = @merge_request.can_remove_source_branch?(current_user) && !should_remove_source_branch && @merge_request.merge_user == current_user
+ - user_can_cancel_automatic_merge = @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+ - if remove_source_branch_button || user_can_cancel_automatic_merge
+ .clearfix.prepend-top-10
+ - if remove_source_branch_button
+ = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
+ = icon('times')
+ Remove Source Branch When Merged
+
+ - if user_can_cancel_automatic_merge
+ = link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-warning btn-sm" do
+ Cancel Automatic Merge
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 24879b19d2b..39aa2437e18 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,10 +1,3 @@
-%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.iid}"
-.back-link
- = link_to namespace_project_milestones_path(@project.namespace, @project) do
- &larr; To milestones
-
-%hr
-
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-requires-input'} do |f|
-if @milestone.errors.any?
.alert.alert-danger
@@ -16,8 +9,7 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
- %p.hint Required
+ = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 5e93d55b1fb..d6a44c9f0a1 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -18,11 +18,7 @@
.row
.col-sm-6
- - if milestone.expired? and not milestone.closed?
- %span.cred (Expired)
- - if milestone.expires_at
- %span
- = milestone.expires_at
+ = render 'shared/milestone_expired', milestone: milestone
.col-sm-6
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
@@ -31,4 +27,4 @@
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
%i.fa.fa-trash-o
- Remove
+ Delete
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index e9dc0b77462..43f8863163d 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,3 +1,9 @@
- page_title "Edit", @milestone.title, "Milestones"
= render "header_title"
+
+%h3.page-title
+ Edit Milestone ##{@milestone.iid}
+
+%hr
+
= render "form"
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index a207385bd43..114b06457a5 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,15 +1,18 @@
- page_title "Milestones"
= render "header_title"
-= render 'shared/milestones_filter'
-.gray-content-block
- .pull-right
- - if can? current_user, :admin_milestone, @project
+
+.project-issuable-filter
+ .controls
+ - if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
%i.fa.fa-plus
New Milestone
- .oneline
- Milestone allows you to group issues and set due date for it
+
+ = render 'shared/milestones_filter'
+
+.gray-content-block
+ Milestone allows you to group issues and set due date for it
.milestones
%ul.content-list
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index 9ba9acb6f77..0d016f78313 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,3 +1,9 @@
- page_title "New Milestone"
= render "header_title"
+
+%h3.page-title
+ New Milestone
+
+%hr
+
= render "form"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 3a898dfbcfd..7e73ae274e9 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,46 +1,50 @@
- page_title @milestone.title, "Milestones"
= render "header_title"
-%h4.page-title
- .issue-box{ class: issue_box_class(@milestone) }
+.detail-page-header
+ .status-box{ class: status_box_class(@milestone) }
- if @milestone.closed?
Closed
- elsif @milestone.expired?
Expired
- else
Open
- Milestone ##{@milestone.iid}
- %small.creator
- = @milestone.expires_at
+ %span.identifier
+ Milestone ##{@milestone.iid}
+ - if @milestone.expires_at
+ %span.creator
+ &middot;
+ = @milestone.expires_at
.pull-right
- if can?(current_user, :admin_milestone, @project)
- = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
- %i.fa.fa-pencil-square-o
- Edit
- if @milestone.active?
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
- else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+
= link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
%i.fa.fa-trash-o
- Remove
+ Delete
+
+ = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
+ %i.fa.fa-pencil-square-o
+ Edit
+
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@milestone.title), pipeline: :single_line
+ %div
+ - if @milestone.description.present?
+ .description
+ .wiki
+ = preserve do
+ = markdown @milestone.description
-%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
- .alert.alert-success
+ .alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now.
-%h3.issue-title
- = gfm escape_once(@milestone.title)
-%div
- - if @milestone.description.present?
- .description
- .wiki
- = preserve do
- = markdown @milestone.description
-
-%hr
-.context
+.context.prepend-top-default
%p.lead
Progress:
#{@milestone.closed_items_count} closed
@@ -51,8 +55,7 @@
%span.pull-right= @milestone.expires_at
= milestone_progress_bar(@milestone)
-
-%ul.nav.nav-tabs
+%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
@@ -66,17 +69,21 @@
Participants
%span.badge= @users.count
- .pull-right
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
- %i.fa.fa-plus
- New Issue
- - if can?(current_user, :read_issue, @project)
- = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
-
.tab-content
.tab-pane.active#tab-issues
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ - if can?(current_user, :create_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
+ %i.fa.fa-plus
+ New Issue
+ - if can?(current_user, :read_issue, @project)
+ = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All issues in this milestone
+
+ .row.prepend-top-default
.col-md-4
= render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
.col-md-4
@@ -85,7 +92,15 @@
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
.tab-pane#tab-merge-requests
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ - if can?(current_user, :read_merge_request, @project)
+ = link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All merge requests in this milestone
+
+ .row.prepend-top-default
.col-md-3
= render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
.col-md-3
@@ -100,6 +115,10 @@
= render 'merge_request', merge_request: merge_request
.tab-pane#tab-participants
+ .gray-content-block.middle-block
+ .oneline
+ All participants to this milestone
+
%ul.bordered-list
- @users.each do |user|
%li
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
index 415c98ec6a6..28a617538b5 100644
--- a/app/views/projects/network/_head.html.haml
+++ b/app/views/projects/network/_head.html.haml
@@ -1,3 +1,6 @@
-.append-bottom-20
- = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
- .pull-right.visible-lg.light You can move around the graph by using the arrow keys.
+.gray-content-block.append-bottom-default
+ .tree-ref-holder
+ = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
+
+ .oneline
+ You can move around the graph by using the arrow keys.
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 16005161df6..8065663ca2a 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,5 +1,6 @@
- page_title "Network", @ref
-= header_title project_title(@project, "Network", namespace_project_network_path(@project.namespace, @project, current_ref))
+= render "projects/commits/header_title"
+= render "projects/commits/head"
= render "head"
.project-network
.controls
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index c9d1fc3da21..25233112132 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,5 +1,10 @@
- page_title 'New Project'
-- header_title 'New Project'
+- header_title "Projects", root_path
+
+%h3.page-title
+ New Project
+%hr
+
.project-edit-container
.project-edit-errors
= render 'projects/errors'
@@ -11,16 +16,21 @@
Project path
.col-sm-10
.input-group
- = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true, required: true
- .input-group-addon
- \.git
-
- - if current_user.can_select_namespace?
- .form-group
- = f.label :namespace_id, class: 'control-label' do
- %span Namespace
- .col-sm-10
- = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
+ - if current_user.can_select_namespace?
+ .input-group-addon
+ = root_url
+ = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2', tabindex: 1}
+ .input-group-addon
+ \/
+ - else
+ .input-group-addon
+ #{root_url}#{current_user.username}/
+ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
+
+ - if current_user.can_create_group?
+ .help-block
+ Want to house several dependent projects under the same namespace?
+ = link_to "Create a group", new_group_path
- if import_sources_enabled?
.project-import.js-toggle-container
@@ -90,19 +100,12 @@
Description
%span.light (optional)
.col-sm-10
- = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250, tabindex: 3
+ = f.text_area :description, class: "form-control", rows: 3, maxlength: 250, tabindex: 3
= render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project
.form-actions
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
-
- - if current_user.can_create_group?
- .pull-right
- .light.inline
- .space-right
- Need a group for several dependent projects?
- = link_to new_group_path, class: "btn" do
- Create a group
+ = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
.save-project-loader.hide
.center
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index a21c019986a..3ccda1b381c 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -6,6 +6,5 @@
= render 'projects/notes/hints'
.note-form-actions
- .buttons
- = f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button'
- = link_to 'Cancel', '#', class: 'btn btn-cancel note-edit-cancel'
+ = f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button'
+ = link_to 'Cancel', '#', class: 'btn btn-cancel note-edit-cancel'
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 5dd84317e3b..88e711ab534 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -12,8 +12,7 @@
= render 'projects/notes/hints'
.error-alert
- .note-form-actions
- .buttons.clearfix
- = f.submit 'Add Comment', class: "btn btn-green comment-btn btn-grouped js-comment-button"
- = yield(:note_actions)
- %a.btn.grouped.js-close-discussion-note-form Cancel
+ .note-form-actions.clearfix
+ = f.submit 'Add Comment', class: "btn btn-create comment-btn btn-grouped js-comment-button"
+ = yield(:note_actions)
+ %a.btn.btn-cancel.js-close-discussion-note-form Cancel
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index dd0abc8c746..922535e5c4a 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -38,7 +38,7 @@
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
- = markdown(note.note, {no_header_anchors: true})
+ = markdown(note.note, pipeline: :note, cache_key: [note, "note"])
- if note_editable?(note)
= render 'projects/notes/edit_form', note: note
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 99c1b0fa43e..eb378b42603 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -7,4 +7,4 @@
= render "projects/notes/form", view: diff_view
:javascript
- new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
+ var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index 43e92437cf5..1c2458fa144 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -4,12 +4,13 @@
group members
%small
(#{members.count})
- .panel-head-actions
- = link_to group_group_members_path(@group), class: 'btn btn-sm' do
- %i.fa.fa-pencil-square-o
- Edit group members
- %ul.well-list
- - members.each do |member|
+ - if can?(current_user, :admin_group_member, @group)
+ .pull-right
+ = link_to group_group_members_path(@group), class: 'btn' do
+ = icon('pencil-square-o')
+ Manage group members
+ %ul.content-list
+ - members.limit(20).each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false
- if members.count > 20
%li
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index f07cd97e63d..05bf3a7ef6a 100644
--- a/app/views/projects/project_members/_project_member.html.haml
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -4,7 +4,7 @@
%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
%span.list-item-name
- if member.user
- = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 24), class: "avatar s24", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
@@ -14,7 +14,7 @@
%label.label.label-danger
%strong Blocked
- else
- = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: ''
%strong
= member.invite_email
%span.cgray
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index b807fb2cc9d..ccddab13aaf 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,9 +1,21 @@
-.panel.panel-default.prepend-top-20
+.panel.panel-default
.panel-heading
%strong #{@project.name}
project members
%small
(#{members.count})
- %ul.well-list
+ .pull-right
+ = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
+ = button_tag class: 'btn', title: 'Search' do
+ = icon("search")
+ %ul.content-list
- members.each do |project_member|
= render 'project_member', member: project_member
+
+:javascript
+ $('form.member-search-form').on('submit', function (event) {
+ event.preventDefault();
+ Turbolinks.visit(this.action + '?' + $(this).serialize());
+ });
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9fc4be583cc..29225a36364 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,36 +1,21 @@
- page_title "Members"
= render "header_title"
+- @blank_container = true
-.gray-content-block.top-block
- .clearfix.js-toggle-container
- = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
- .form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
- = button_tag 'Search', class: 'btn'
-
- - if can?(current_user, :admin_project_member, @project)
- %span.pull-right
- = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do
- Add members
- %i.fa.fa-chevron-down
- = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
- Import members
-
- .js-toggle-content.hide.new-group-member-holder
+.project-members-page
+ - if can?(current_user, :admin_project_member, @project)
+ .panel.panel-default
+ .panel-heading
+ Add new user to project
+ .pull-right
+ = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
+ Import members
+ .panel-body
+ %p.light
+ Users with access to this project are listed below.
= render "new_project_member"
-%p.prepend-top-default.light
- Users with access to this project are listed below.
- Read more about project permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-
-= render "team", members: @project_members
-
-- if @group
- = render "group_members", members: @group_members
+ = render "team", members: @project_members
-:javascript
- $('form.member-search-form').on('submit', function (event) {
- event.preventDefault();
- Turbolinks.visit(this.action + '?' + $(this).serialize());
- });
+ - if @group
+ = render "group_members", members: @group_members
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 52b3a50c1e6..cfd7e1534ca 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -3,7 +3,7 @@
%p.light Keep stable branches secure and force developers to use Merge Requests
%hr
-.well.append-bottom-20
+.well
%p Protected branches are designed to
%ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
@@ -22,7 +22,7 @@
.form-group
= f.label :name, "Branch", class: 'control-label'
.col-sm-10
- = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"})
+ = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
@@ -33,4 +33,3 @@
.form-actions
= f.submit 'Protect', class: "btn-create btn"
= render 'branches_list'
-
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index f516b65ecd0..bc80f2f29ad 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -14,6 +14,6 @@
= render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit form-control'
= render 'projects/notes/hints'
.error-alert
- .prepend-top-default
+ .form-actions.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save'
= link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index 07c24950ee2..b9486a9b492 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -3,10 +3,10 @@
- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn btn-success col-xs-10', rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
- %a.col-xs-2.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' }
+ %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index f3526ad0747..6ca919f7f80 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -12,7 +12,7 @@
= link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
- = gfm escape_once(truncate(commit.title, length: 40))
+ = markdown escape_once(truncate(commit.title, length: 40)), pipeline: :single_line
%td
%span.pull-right.cgray
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index e6b8a2e6fe7..47ec420189d 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -15,10 +15,10 @@
- if runner.belongs_to_one_project?
= link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- else
- - runner_project = @ci_project.runner_projects.find_by(runner_id: runner)
- = link_to 'Disable for this project', [:ci, @ci_project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+ - runner_project = @project.runner_projects.find_by(runner_id: runner)
+ = link_to 'Disable for this project', namespace_project_runner_project_path(@project.namespace, @project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- elsif runner.specific?
- = form_for [:ci, @ci_project, @ci_project.runner_projects.new] do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @project.runner_projects.new] do |f|
= f.hidden_field :runner_id, value: runner.id
= f.submit 'Enable for this project', class: 'btn btn-sm'
.pull-right
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 316ea747b14..6a37f444bb7 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -3,17 +3,17 @@
.bs-callout.bs-callout-warning
GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
%hr
- - if @ci_project.shared_runners_enabled
- = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-warning', method: :post do
+ - if @project.shared_runners_enabled?
+ = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
Disable shared runners
- else
- = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-success', method: :post do
+ = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-success', method: :post do
Enable shared runners
&nbsp; for this project
- if @shared_runners_count.zero?
- This application has no shared runners yet.
- Please use specific runners or ask administrator to create one
+ This GitLab server does not provide any shared runners yet.
+ Please use specific runners or ask the administrator to create one.
- else
%h4.underlined-title Available shared runners - #{@shared_runners_count}
%ul.bordered-list.available-shared-runners
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index c13625c7e49..30cd1263a12 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -12,7 +12,7 @@
%code #{ci_root_url(only_path: false)}
%li
Use the following registration token during setup:
- %code #{@ci_project.token}
+ %code #{@project.runners_token}
%li
Start runner!
diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml
index dde9e448cb9..eba03028af8 100644
--- a/app/views/projects/runners/edit.html.haml
+++ b/app/views/projects/runners/edit.html.haml
@@ -23,7 +23,7 @@
= label_tag :tag_list, class: 'control-label' do
Tags
.col-sm-10
- = f.text_field :tag_list, class: 'form-control'
+ = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
.help-block You can setup jobs to only use runners with specific tags
.form-actions
- = f.submit 'Save', class: 'btn btn-save'
+ = f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index e1823b51198..1b70880043a 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -4,18 +4,15 @@
%p= @service.description
-.back-link
- = link_to namespace_project_services_path(@project.namespace, @project) do
- &larr; to services
-
%hr
= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
= render 'shared/service_settings', form: form
.form-actions
- = form.submit 'Save', class: 'btn btn-save'
+ = form.submit 'Save changes', class: 'btn btn-save'
&nbsp;
- if @service.valid? && @service.activated?
- disabled = @service.can_test? ? '':'disabled'
= link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: "btn #{disabled}"
+ = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 585caf674c9..9c7a5584da9 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -11,7 +11,7 @@
= render "home_panel"
-.project-stats.gray-content-block
+.project-stats.gray-content-block.second-block
%ul.nav.nav-pills
%li
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index e69f2d99709..dc3ea1fcf12 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -2,6 +2,6 @@
= render "header_title"
%h3.page-title
- Edit snippet
+ Edit Snippet
%hr
= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 67cd69fd215..e57237991b4 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -2,6 +2,6 @@
= render "header_title"
%h3.page-title
- New snippet
+ New Snippet
%hr
= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 5d706942f2d..7c599563ce4 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -10,8 +10,8 @@
%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"
+ = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
+ = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
= render 'shared/snippets/blob'
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index e2c5178185e..28b706c5c7e 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -11,11 +11,17 @@
= strip_gpg_signature(tag.message)
.controls
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do
- = icon("pencil")
- - if can? current_user, :download_code, @project
+ - if can?(current_user, :download_code, @project)
= render 'projects/tags/download', ref: tag.name, project: @project
+ - if can?(current_user, :push_code, @project)
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has_tooltip', title: "Edit release notes" do
+ = icon("pencil")
+
+ - if can?(current_user, :admin_project, @project)
+ = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ = icon("trash-o")
+
- if commit
= render 'projects/branches/commit', commit: commit, project: @project
- else
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 85d76eae3b5..760347de0a9 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -6,7 +6,7 @@
- if can? current_user, :push_code, @project
.pull-right
= link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
- %i.fa.fa-add-sign
+ = icon('plus')
New tag
.oneline
Tags give the ability to mark specific points in history as being important
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 86aa15dc5b3..3a2f75fecaa 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -7,24 +7,24 @@
= @error
%h3.page-title
- New git tag
+ New Tag
%hr
-= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form" do
+= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-requires-input" do
.form-group
- = label_tag :tag_name, 'Name for new tag', class: 'control-label'
+ = label_tag :tag_name, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :tag_name, params[:tag_name], placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control'
+ = text_field_tag :tag_name, params[:tag_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
- = text_field_tag :ref, params[:ref], placeholder: 'master', required: true, tabindex: 2, class: 'form-control'
+ = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
.help-block Branch name or commit SHA
.form-group
- = label_tag :message, 'Message', class: 'control-label'
+ = label_tag :message, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
- .help-block (Optional) Entering a message will create an annotated tag.
+ = text_field_tag :message, nil, required: false, tabindex: 3, class: 'form-control'
+ .help-block Optionally, enter a message to create an annotated tag.
%hr
.form-group
= label_tag :release_description, 'Release notes', class: 'control-label'
@@ -32,16 +32,15 @@
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', attr: :release_description, classes: 'description js-quick-submit form-control'
= render 'projects/notes/hints'
- .help-block (Optional) You can add release notes to your tag. It will be stored in the GitLab database and shown on the tags page
+ .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
- disableButtonIfAnyEmptyField($("#new-tag-form"), ".form-control", ".btn-create");
- var availableTags = #{@project.repository.ref_names.to_json};
+ var availableRefs = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
- source: availableTags,
+ source: availableRefs,
minLength: 1
});
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index ebe3718afcc..b594d4f1f27 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -5,17 +5,17 @@
.gray-content-block
.pull-right
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn', title: 'Edit release notes' do
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has_tooltip', title: 'Edit release notes' do
= icon("pencil")
- = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse source code' do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse files' do
= icon('files-o')
- = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse commits' do
+ = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project)
.pull-right
- = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'} do
+ = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
.title
%strong= @tag.name
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index c64e684df26..1bc90edd8f0 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -12,7 +12,7 @@
%i.fa.fa-angle-right
&nbsp;
%small.light
- = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit)
+ = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
&ndash;
= truncate(@commit.title, length: 50)
= link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 1115ca6b4ca..89b072cea92 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -14,19 +14,31 @@
- if allowed_tree_edit?
%li
%span.dropdown
- %a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+ %a.dropdown-toggle.btn.btn-sm.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
+ New 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
+ %li.divider
+ %li
+ = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+ = icon('code-fork fw')
+ New branch
+ %li
+ = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+ = icon('tags fw')
+ New tag
+ - elsif !on_top_of_branch?
+ %li
+ %span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}}
+ = icon('plus')
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index b3ad79a200e..bd346c4b8e6 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -36,7 +36,8 @@
:plain
curl -X POST \
-F token=TOKEN \
- #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}
+ -F ref=REF_NAME \
+ #{builds_trigger_url(@project.id)}
%h3
Use .gitlab-ci.yml
@@ -51,7 +52,7 @@
trigger:
type: deploy
script:
- - "curl -X POST -F token=TOKEN #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}"
+ - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
%h3
Pass build variables
@@ -65,5 +66,6 @@
:plain
curl -X POST \
-F token=TOKEN \
+ -F "ref=REF_NAME" \
-F "variables[RUN_NIGHTLY_BUILD]=true" \
- #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}
+ #{builds_trigger_url(@project.id)}
diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml
index e052da1ac43..e80dffc1ced 100644
--- a/app/views/projects/variables/show.html.haml
+++ b/app/views/projects/variables/show.html.haml
@@ -10,13 +10,13 @@
%hr
-= nested_form_for @ci_project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
+= nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
- if @project.errors.any?
#error_explanation
- %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:"
+ %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:"
.alert.alert-error
%ul
- - @ci_project.errors.full_messages.each do |msg|
+ - @project.errors.full_messages.each do |msg|
%li= msg
= f.fields_for :variables do |variable_form|
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 9c94c43e747..1d257818dcd 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default' } do |f|
-if @page.errors.any?
#error_explanation
.alert.alert-danger
@@ -11,14 +11,7 @@
.col-sm-10
= f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
- .row
- .col-sm-offset-2.col-sm-10
- %p.cgray
- To link to a (new) page you can just type
- %code [Link Title](page-slug)
- \.
-
- .form-group.wiki-content
+ .form-group
= f.label :content, class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
@@ -27,6 +20,11 @@
.clearfix
.error-alert
+
+ .help-block
+ To link to a (new) page, simply type
+ %code [Link Title](page-slug)
+ \.
.form-group
= f.label :commit_message, class: 'control-label'
.col-sm-10= f.text_field :message, class: 'form-control', rows: 18
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 14f25822259..29bf5d62abe 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,9 +1,4 @@
%span.pull-right
- - if can?(current_user, :create_wiki, @project)
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new btn-grouped", "data-toggle" => "modal" do
- %i.fa.fa-plus
- New Page
-
- if (@page && @page.persisted?)
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
Page History
@@ -11,5 +6,7 @@
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
-
-= render 'projects/wikis/new'
+ - if can?(current_user, :admin_wiki, @project)
+ = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
+ = icon('trash')
+ Delete
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index fffb4eb31ab..e6e6ad5bc4b 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,10 +1,19 @@
-%ul.center-top-menu
- = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
- = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
+.project-issuable-filter
+ .controls
+ - if can?(current_user, :create_wiki, @project)
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+ %i.fa.fa-plus
+ New Page
- = nav_link(path: 'wikis#pages') do
- = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
+ = render 'projects/wikis/new'
- = nav_link(path: 'wikis#git_access') do
- = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
- Git Access
+ %ul.center-top-menu
+ = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
+ = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
+
+ = nav_link(path: 'wikis#pages') do
+ = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
+
+ = nav_link(path: 'wikis#git_access') do
+ = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
+ Git Access
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index dace172438c..f0547e9c057 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -12,5 +12,5 @@
The page slug is invalid. Please don't use characters other then: a-z 0-9 _ - and /
%p.hint
Please don't use spaces.
- .modal-footer
- = link_to 'Build', '#', class: 'build-new-wiki btn btn-create'
+ .form-actions
+ = link_to 'Create Page', '#', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 0b709c3695b..23f64fbbd10 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,16 +1,16 @@
-- page_title "Edit", @page.title, "Wiki"
+- page_title "Edit", @page.title.capitalize, "Wiki"
= render "header_title"
= render 'nav'
-.pull-right
- = render 'main_links'
-%h3.page-title
- Editing -
- %span.light #{@page.title}
-%hr
-= render 'form'
+.gray-content-block
+ .pull-right
+ = render 'main_links'
+
+ %h3.page-title.oneline
+ %span.light Edit Page
+ - if @page.persisted?
+ = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
+ - else
+ = @page.title
-.pull-right
- - if @page.persisted? && can?(current_user, :admin_wiki, @project)
- = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-sm btn-remove" do
- Delete this page
+= render 'form'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 6417ef4a38b..11c8c4f0eba 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -5,7 +5,7 @@
.gray-content-block
.row
.col-sm-6
- %h3.page-title
+ %h3.page-title.oneline
Git access for
%strong= @project_wiki.path_with_namespace
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index d179a1abec1..aae1ad69ad9 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,11 +1,10 @@
-- page_title "All Pages", "Wiki"
+- page_title "Pages", "Wiki"
= render "header_title"
= render 'nav'
.gray-content-block
- = render 'main_links'
- %h3.page-title
- All Pages
+ All pages in this wiki are listed below.
+
%ul.content-list
- @wiki_pages.each do |wiki_page|
%li
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 55fbf5a8b6e..309d40f52bc 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -5,11 +5,12 @@
.gray-content-block
= render 'main_links'
- %h3.page-title
+ %h3.page-title.oneline
= @page.title.capitalize
- .wiki-last-edit-by
- Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+ %span.wiki-last-edit-by
+ &middot;
+ last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
- if @page.historical?
.warning_message
@@ -21,8 +22,3 @@
.wiki
= preserve do
= render_wiki_content(@page)
-
-.gray-content-block.footer-block
- .wiki-last-edit-by
- Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
-
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 8bcb24ae9df..edb5778f424 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -2,25 +2,9 @@
.git-clone-holder.input-group
.input-group-addon.git-protocols
.input-group-btn
- %button{ |
- type: 'button', |
- class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
- :"data-clone" => project.ssh_url_to_repo, |
- :"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH.",
- :"data-html" => "true",
- :"data-container" => "body"}
- SSH
+ = ssh_clone_button(project)
.input-group-btn
- %button{ |
- type: 'button', |
- class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
- :"data-clone" => project.http_url_to_repo, |
- :"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}.",
- :"data-html" => "true",
- :"data-container" => "body"}
- = gitlab_config.protocol.upcase
+ = http_clone_button(project)
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
- - if project.kind_of?(Project)
- .input-group-addon.has_tooltip{title: "#{visibility_level_label(project.visibility_level)} project", data: { container: "body" } }
- .visibility-level-label
- = visibility_level_icon(project.visibility_level)
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#project_clone')
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 2a44817e05a..34241cd8aad 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -3,7 +3,8 @@
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
- %h4 Confirmation required
+ %h3.page-title
+ Confirmation required
.modal-body
%p.cred.lead.js-confirm-text
@@ -18,5 +19,5 @@
.form-group
= text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
- .form-group
+ .form-actions
= submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 57c3aff3e18..2bc98983d67 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -8,5 +8,6 @@
%a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
%i.fa.fa-link
= i
- :preserve
- #{highlight(blob.name, blob.data)}
+ .blob-content{data: {blob_id: blob.id}}
+ :preserve
+ #{highlight(blob.name, blob.data)}
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index c0a9923348e..67072b9fc2a 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -23,7 +23,7 @@
%li It will change the git path to repositories under this group.
.form-group.group-description-holder
- = f.label :description, 'Details', class: 'control-label'
+ = f.label :description, class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250,
class: 'form-control js-gfm-input', rows: 4
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 0dbb6a04393..4b4c9e9eabe 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -3,8 +3,10 @@
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
- = link_to_project project
- = link_to 'show all', namespace_project_issues_path(project.namespace, project), class: 'pull-right'
+ = link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project)
+ - if can?(current_user, :create_issue, project)
+ .pull-right
+ = link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
%ul.well-list.issues-list
- group[1].each do |issue|
@@ -12,4 +14,3 @@
= paginate @issues, theme: "gitlab"
- else
.nothing-here-block No issues to show
-
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index c02c5af008a..be17a511b26 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -3,8 +3,11 @@
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
- = link_to_project project
- = link_to 'show all', namespace_project_merge_requests_path(project.namespace, project), class: 'pull-right'
+ = link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project)
+ - if can?(current_user, :create_merge_request, project)
+ .pull-right
+ = link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
+
%ul.well-list.mr-list
- group[1].each do |merge_request|
= render 'projects/merge_requests/merge_request', merge_request: merge_request
diff --git a/app/views/shared/_milestone_expired.html.haml b/app/views/shared/_milestone_expired.html.haml
new file mode 100644
index 00000000000..b8eef15fbec
--- /dev/null
+++ b/app/views/shared/_milestone_expired.html.haml
@@ -0,0 +1,5 @@
+- if milestone.expired? and not milestone.closed?
+ %span.cred (Expired)
+- if milestone.expires_at
+ %span
+ = milestone.expires_at
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
index 8636341c60d..111219f2064 100644
--- a/app/views/shared/_new_commit_form.html.haml
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -2,17 +2,15 @@
- unless @project.empty_repo?
.form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
+ = label_tag 'new_branch', 'Target branch', class: 'control-label'
.col-sm-10
- = text_field_tag 'new_branch', @new_branch || @ref, class: "form-control js-new-branch"
+ = text_field_tag 'new_branch', @new_branch || tree_edit_branch, required: true, class: "form-control js-new-branch"
- .form-group.js-create-merge-request-form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- - nonce = SecureRandom.hex
- = label_tag "create_merge_request-#{nonce}" do
- = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
- Start a <strong>new merge request</strong> with this commit
+ .js-create-merge-request-container
+ .checkbox
+ - nonce = SecureRandom.hex
+ = label_tag "create_merge_request-#{nonce}" do
+ = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
+ Start a <strong>new merge request</strong> with these changes
= hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
new file mode 100644
index 00000000000..c4431d66927
--- /dev/null
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -0,0 +1,20 @@
+- if @projects.any?
+ .prepend-left-10.new-project-item-select-holder
+ = project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] }
+ %a.btn.btn-new.new-project-item-select-button
+ = icon('plus')
+ = local_assigns[:label]
+ %b.caret
+
+ :javascript
+ $('.new-project-item-select-button').on('click', function() {
+ $('.new-project-item-select').select2('open');
+ });
+
+ var relativePath = '#{local_assigns[:path]}';
+
+ $('.new-project-item-select').on('click', function() {
+ window.location = $(this).val() + '/' + relativePath;
+ });
+
+ new ProjectSelect()
diff --git a/app/views/shared/_project_limit.html.haml b/app/views/shared/_project_limit.html.haml
new file mode 100644
index 00000000000..960ff00b49d
--- /dev/null
+++ b/app/views/shared/_project_limit.html.haml
@@ -0,0 +1,8 @@
+- if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project?
+ .project-limit-message.alert.alert-warning.hidden-xs
+ You won't be able to create new projects because you have reached your project limit.
+
+ .pull-right
+ = link_to "Don't show again", profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link'
+ |
+ = link_to 'Remind later', '#', class: 'hide-project-limit-message alert-link'
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 16a98a7233c..28d6f421fea 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -59,6 +59,15 @@
%strong Merge Request events
%p.light
This url will be triggered when a merge request is created
+ - if @service.supported_events.include?("build")
+ %div
+ = form.check_box :build_events, class: 'pull-left'
+ .prepend-left-20
+ = form.label :build_events, class: 'list-label' do
+ %strong Build events
+ %p.light
+ This url will be triggered when a build status changes
+
- @service.fields.each do |field|
- type = field[:type]
diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml
deleted file mode 100644
index be66256c7b0..00000000000
--- a/app/views/shared/issuable/_context.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
- %div.prepend-top-20
- .issuable-context-title
- %label
- Assignee:
- - if issuable.assignee
- %strong= link_to_member(@project, issuable.assignee, size: 24)
- - else
- none
- .issuable-context-selectbox
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true)
-
- %div.prepend-top-20.clearfix
- .issuable-context-title
- %label
- Milestone:
- - if issuable.milestone
- %span.back-to-milestone
- = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
- %strong
- = icon('clock-o')
- = issuable.milestone.title
- - else
- none
- .issuable-context-selectbox
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- = f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
- = hidden_field_tag :issuable_context
- = f.submit class: 'btn hide'
-
- - if current_user
- - subscribed = issuable.subscribed?(current_user)
- %div.prepend-top-20.clearfix
- .issuable-context-title
- %label
- Subscription:
- %button.btn.btn-block.subscribe-button{:type => 'button'}
- = icon('eye')
- %span= subscribed ? 'Unsubscribe' : 'Subscribe'
- - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
- .subscription-status{data: {status: subscribtion_status}}
- .description-block.unsubscribed{class: ( 'hidden' if subscribed )}
- You're not receiving notifications from this thread.
- .description-block.subscribed{class: ( 'hidden' unless subscribed )}
- You're receiving notifications because you're subscribed to this thread.
-
-:javascript
- new Subscription("#{toggle_subscription_path(issuable)}");
- new IssuableContext();
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index d1231438ee4..ac6c248ccf1 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -31,11 +31,11 @@
.issues-other-filters
.filter-item.inline
= users_select_tag(:assignee_id, selected: params[:assignee_id],
- placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true, first_user: true, current_user: true)
+ placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
.filter-item.inline
= users_select_tag(:author_id, selected: params[:author_id],
- placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true, current_user: true)
+ placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
.filter-item.inline.milestone-filter
= select_tag('milestone_title', projects_milestones_options,
@@ -53,12 +53,16 @@
- if controller.controller_name == 'issues'
.issues_bulk_update.hide
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
- = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control')
- = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true)
- = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
+ .filter-item.inline
+ = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), include_blank: true, data: { placeholder: "Status" })
+ .filter-item.inline
+ = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true)
+ .filter-item.inline
+ = select_tag('update[milestone_id]', bulk_update_milestone_options, include_blank: true, data: { placeholder: "Milestone" })
= hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event]
- = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+ .filter-item.inline
+ = button_tag "Update issues", class: "btn update_selected_issues btn-save"
:javascript
new UsersSelect();
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 0fc74d7d2b1..90dc0062481 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -6,8 +6,7 @@
%span= msg
%br
.form-group
- = f.label :title, class: 'control-label' do
- %strong= 'Title *'
+ = f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
class: 'form-control pad js-gfm-input js-quick-submit', required: true
@@ -20,7 +19,7 @@
- else
Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
<strong>Work In Progress</strong> merge request from being merged before it's ready.
-.form-group.issuable-description
+.form-group.detail-page-description
= f.label :description, 'Description', class: 'control-label'
.col-sm-10
@@ -30,29 +29,25 @@
= render 'projects/notes/hints'
.clearfix
.error-alert
- %hr
- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
+ %hr
.form-group
.issue-assignee
- = f.label :assignee_id, class: 'control-label' do
- %i.fa.fa-user
- Assign to
+ = f.label :assignee_id, "Assignee", class: 'control-label'
.col-sm-10
= users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
- placeholder: 'Select a user', class: 'custom-form-control', null_user: true,
+ placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
selected: issuable.assignee_id, project: @target_project || @project,
- first_user: true, current_user: true)
+ first_user: true, current_user: true, include_blank: true)
&nbsp;
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
.form-group
.issue-milestone
- = f.label :milestone_id, class: 'control-label' do
- %i.fa.fa-clock-o
- Milestone
+ = f.label :milestone_id, "Milestone", class: 'control-label'
.col-sm-10
- if milestone_options(issuable).present?
= f.select(:milestone_id, milestone_options(issuable),
- { include_blank: 'Select milestone' }, { class: 'select2' })
+ { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
- else
.prepend-top-10
%span.light No open milestones available.
@@ -60,13 +55,11 @@
- if can? current_user, :admin_milestone, issuable.project
= link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
.form-group
- = f.label :label_ids, class: 'control-label' do
- %i.fa.fa-tag
- Labels
+ = f.label :label_ids, "Labels", class: 'control-label'
.col-sm-10
- if issuable.project.labels.any?
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
- { selected: issuable.label_ids }, multiple: true, class: 'select2'
+ { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- else
.prepend-top-10
%span.light No labels yet.
@@ -78,31 +71,30 @@
%hr
- if @merge_request.new_record?
.form-group
- = f.label :source_branch, class: 'control-label' do
- %i.fa.fa-code-fork
- Source Branch
+ = f.label :source_branch, class: 'control-label'
.col-sm-10
= f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
.form-group
- = f.label :target_branch, class: 'control-label' do
- %i.fa.fa-code-fork
- Target Branch
+ = f.label :target_branch, class: 'control-label'
.col-sm-10
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record? })
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
- if @merge_request.new_record?
%p.help-block
= link_to 'Change branches', mr_change_branches_path(@merge_request)
-.form-actions
- - if !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) && !issuable.persisted?
- %p
- Please review the
- %strong #{link_to 'guidelines for contribution', guide_url}
- to this repository.
+- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
+.gray-content-block{class: (is_footer ? "footer-block" : "middle-block")}
- if issuable.new_record?
- = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
+ = f.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- else
= f.submit 'Save changes', class: 'btn btn-save'
+
+ - if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project))
+ .inline.prepend-left-10
+ Please review the
+ %strong #{link_to 'contribution guidelines', guide_url}
+ for this project.
+
- if issuable.new_record?
- cancel_project = issuable.source_project
- else
diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml
new file mode 100644
index 00000000000..da6bacbb74a
--- /dev/null
+++ b/app/views/shared/issuable/_participants.html.haml
@@ -0,0 +1,5 @@
+.block.participants
+ .title
+ = pluralize participants.count, "participant"
+ - participants.each do |participant|
+ = link_to_member(@project, participant, name: false, size: 24)
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
new file mode 100644
index 00000000000..79c5cc7f40a
--- /dev/null
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -0,0 +1,83 @@
+.issuable-sidebar.issuable-affix
+ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
+ .block.assignee
+ .title
+ %label
+ Assignee
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .pull-right
+ = link_to 'Edit', '#', class: 'edit-link'
+ .value
+ - if issuable.assignee
+ %strong= link_to_member(@project, issuable.assignee, size: 24)
+ - else
+ .light None
+
+ .selectbox
+ = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
+
+ .block.milestone
+ .title
+ %label
+ Milestone
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .pull-right
+ = link_to 'Edit', '#', class: 'edit-link'
+ .value
+ - if issuable.milestone
+ %span.back-to-milestone
+ = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
+ %strong
+ = icon('clock-o')
+ = issuable.milestone.title
+ - else
+ .light None
+ .selectbox
+ = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
+ = hidden_field_tag :issuable_context
+ = f.submit class: 'btn hide'
+
+ - if issuable.project.labels.any?
+ .block
+ .title
+ %label Labels
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .pull-right
+ = link_to 'Edit', '#', class: 'edit-link'
+ .value.issuable-show-labels
+ - if issuable.labels.any?
+ - issuable.labels.each do |label|
+ = link_to_label(label)
+ - else
+ .light None
+ .selectbox
+ = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
+ { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
+
+ .block
+ .title
+ Cross-project reference
+ .cross-project-reference
+ %span#cross-project-reference
+ = cross_project_reference(@project, issuable)
+ = clipboard_button(clipboard_target: 'span#cross-project-reference')
+
+ = render "shared/issuable/participants", participants: issuable.participants(current_user)
+
+ - if current_user
+ - subscribed = issuable.subscribed?(current_user)
+ .block.light
+ .title
+ %label.light Notifications
+ - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
+ %button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
+ %span= subscribed ? 'Unsubscribe' : 'Subscribe'
+ .subscription-status{data: {status: subscribtion_status}}
+ .unsubscribed{class: ( 'hidden' if subscribed )}
+ You're not receiving notifications from this thread.
+ .subscribed{class: ( 'hidden' unless subscribed )}
+ You're receiving notifications because you're subscribed to this thread.
+
+ :javascript
+ new Subscription("#{toggle_subscription_path(issuable)}");
+ new IssuableContext();
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 913b6744844..1041eccd1df 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -1,5 +1,5 @@
.snippet-form-holder
- = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form" } do |f|
+ = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f|
- if @snippet.errors.any?
.alert.alert-danger
%ul
@@ -8,7 +8,8 @@
.form-group
= f.label :title, class: 'control-label'
- .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
+ .col-sm-10
+ = f.text_field :title, class: 'form-control', required: true, autofocus: true
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet
@@ -27,7 +28,7 @@
- if @snippet.new_record?
= f.submit 'Create snippet', class: "btn-create btn"
- else
- = f.submit 'Save', class: "btn-save btn"
+ = f.submit 'Save changes', class: "btn-save btn"
- if @snippet.project_id
= link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 0a4a790ec5e..aa5acee9c14 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,24 +1,25 @@
-.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')
+.detail-page-header
+ .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
+ = visibility_level_icon(@snippet.visibility_level, fw: false)
+ = visibility_level_label(@snippet.visibility_level)
+ %span.identifier
+ 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)
+ .pull-right
+ - if @snippet.project_id?
+ = render "projects/snippets/actions"
+ - else
+ = render "snippets/actions"
+
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@snippet.title), pipeline: :single_line
diff --git a/app/views/sherlock/transactions/_general.html.haml b/app/views/sherlock/transactions/_general.html.haml
index 4287a0c3203..8533b130da6 100644
--- a/app/views/sherlock/transactions/_general.html.haml
+++ b/app/views/sherlock/transactions/_general.html.haml
@@ -27,6 +27,12 @@
= t('sherlock.seconds')
%li
%span.light
+ #{t('sherlock.query_time')}
+ %strong
+ = @transaction.query_duration.round(2)
+ = t('sherlock.seconds')
+ %li
+ %span.light
#{t('sherlock.finished_at')}:
%strong
= time_ago_in_words(@transaction.finished_at)
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index 1a380035661..82f44a9a5c3 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -1,5 +1,5 @@
- page_title "Edit", @snippet.title, "Snippets"
%h3.page-title
- Edit snippet
+ Edit Snippet
%hr
= render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index a74d5e792ad..79e2392490d 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -1,5 +1,5 @@
- page_title "New Snippet"
%h3.page-title
- New snippet
+ New Snippet
%hr
= render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 69d8899d4c1..a2b36568770 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -9,6 +9,6 @@
%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"
+ = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
+ = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
= render 'shared/snippets/blob'
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index d5a92cb816a..b7a7eb4e6f7 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -73,7 +73,7 @@
.user-calendar-activities
-%ul.center-middle-menu
+%ul.center-top-menu.no-top.no-bottom.bottom-border.wide
%li.active
= link_to "#activity", 'data-toggle' => 'tab' do
Activity
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 7eb27c12d33..6071f1484c6 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -19,7 +19,8 @@
post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"
noteable_type = "#{votable.class.name.underscore}"
noteable_id = "#{votable.id}"
- window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id)
+ aliases = #{AwardEmoji::ALIASES.to_json}
+ window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id, aliases)
$(".awards-menu li").click (e)->
emoji = $(this).data("emoji")
diff --git a/app/workers/build_email_worker.rb b/app/workers/build_email_worker.rb
new file mode 100644
index 00000000000..1c7a04a66a8
--- /dev/null
+++ b/app/workers/build_email_worker.rb
@@ -0,0 +1,19 @@
+class BuildEmailWorker
+ include Sidekiq::Worker
+
+ def perform(build_id, recipients, push_data)
+ recipients.each do |recipient|
+ begin
+ case push_data['build_status']
+ when 'success'
+ Notify.build_success_email(build_id, recipient).deliver_now
+ when 'failed'
+ Notify.build_fail_email(build_id, recipient).deliver_now
+ end
+ # These are input errors and won't be corrected even if Sidekiq retries
+ rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
+ logger.info("Failed to send e-mail for project '#{push_data['project_name']}' to #{recipient}: #{e}")
+ end
+ end
+ end
+end
diff --git a/app/workers/ci/hip_chat_notifier_worker.rb b/app/workers/ci/hip_chat_notifier_worker.rb
deleted file mode 100644
index ebb43570e2a..00000000000
--- a/app/workers/ci/hip_chat_notifier_worker.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Ci
- class HipChatNotifierWorker
- include Sidekiq::Worker
-
- def perform(message, options={})
- room = options.delete('room')
- token = options.delete('token')
- server = options.delete('server')
- name = options.delete('service_name')
- client_opts = {
- api_version: 'v2',
- server_url: server
- }
-
- client = HipChat::Client.new(token, client_opts)
- client[room].send(name, message, options.symbolize_keys)
- end
- end
-end
diff --git a/app/workers/ci/slack_notifier_worker.rb b/app/workers/ci/slack_notifier_worker.rb
deleted file mode 100644
index 3bbb9b4bec7..00000000000
--- a/app/workers/ci/slack_notifier_worker.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Ci
- class SlackNotifierWorker
- include Sidekiq::Worker
-
- def perform(webhook_url, message, options={})
- notifier = Slack::Notifier.new(webhook_url)
- notifier.ping(message, options)
- end
- end
-end
diff --git a/app/workers/ci/web_hook_worker.rb b/app/workers/ci/web_hook_worker.rb
deleted file mode 100644
index 0bb83845572..00000000000
--- a/app/workers/ci/web_hook_worker.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Ci
- class WebHookWorker
- include Sidekiq::Worker
-
- def perform(hook_id, data)
- Ci::WebHook.find(hook_id).execute data
- end
- end
-end
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index 5a921a73fe9..f2649e38eb3 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -46,6 +46,6 @@ class EmailReceiverWorker
return
end
- EmailRejectionMailer.delay.rejection(reason, raw, can_retry)
+ EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
end
end
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 916a99bb273..c4d8595d45d 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -53,7 +53,7 @@ class EmailsOnPushWorker
reverse_compare: reverse_compare,
send_from_committer_email: send_from_committer_email,
disable_diffs: disable_diffs
- ).deliver
+ ).deliver_now
# These are input errors and won't be corrected even if Sidekiq retries
rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}")
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index 5d1a8555b7d..c87c0a252b1 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -8,16 +8,7 @@ class MergeWorker
current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id)
- result = MergeRequests::MergeService.new(merge_request.target_project, current_user).
- execute(merge_request, params[:commit_message])
-
- if result[:status] == :success && params[:should_remove_source_branch].present?
- DeleteBranchService.new(merge_request.source_project, current_user).
- execute(merge_request.source_branch)
-
- merge_request.source_project.repository.expire_branch_names
- end
-
- result
+ MergeRequests::MergeService.new(merge_request.target_project, current_user, params).
+ execute(merge_request)
end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 1de49161997..d18c0706b30 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -51,8 +51,5 @@ class RepositoryImportWorker
end
project.import_finish
-
- # Explicitly update mirror so that upstream remote is created and fetched
- project.update_mirror
end
end
diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb
index ad02a3b16d9..ca594e77e7c 100644
--- a/app/workers/stuck_ci_builds_worker.rb
+++ b/app/workers/stuck_ci_builds_worker.rb
@@ -1,11 +1,8 @@
class StuckCiBuildsWorker
include Sidekiq::Worker
- include Sidetiq::Schedulable
BUILD_STUCK_TIMEOUT = 1.day
- recurrence { daily }
-
def perform
Rails.logger.info 'Cleaning stuck builds'
@@ -14,5 +11,8 @@ class StuckCiBuildsWorker
Rails.logger.debug "Dropping stuck #{build.status} build #{build.id} for runner #{build.runner_id}"
build.drop
end
+
+ # Update builds that failed to drop
+ builds.update_all(status: 'failed')
end
end