summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.ruby-version1
-rw-r--r--CHANGELOG65
-rw-r--r--CONTRIBUTING.md15
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile18
-rw-r--r--Gemfile.lock42
-rw-r--r--PROCESS.md6
-rw-r--r--README.md21
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/activities.js.coffee4
-rw-r--r--app/assets/javascripts/admin.js.coffee4
-rw-r--r--app/assets/javascripts/application.js.coffee15
-rw-r--r--app/assets/javascripts/behaviors/taskable.js.coffee21
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.coffee4
-rw-r--r--app/assets/javascripts/blob.js.coffee5
-rw-r--r--app/assets/javascripts/chart.js.coffee21
-rw-r--r--app/assets/javascripts/commit.js.coffee4
-rw-r--r--app/assets/javascripts/commit/file.js.coffee4
-rw-r--r--app/assets/javascripts/commit/image-file.js.coffee4
-rw-r--r--app/assets/javascripts/commits.js.coffee4
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js.coffee18
-rw-r--r--app/assets/javascripts/dashboard.js.coffee5
-rw-r--r--app/assets/javascripts/diff.js.coffee5
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee36
-rw-r--r--app/assets/javascripts/flash.js.coffee4
-rw-r--r--app/assets/javascripts/group_avatar.js.coffee9
-rw-r--r--app/assets/javascripts/groups.js.coffee15
-rw-r--r--app/assets/javascripts/issue.js.coffee12
-rw-r--r--app/assets/javascripts/labels.js.coffee4
-rw-r--r--app/assets/javascripts/markdown_area.js.coffee26
-rw-r--r--app/assets/javascripts/merge_request.js.coffee31
-rw-r--r--app/assets/javascripts/milestone.js.coffee4
-rw-r--r--app/assets/javascripts/namespace_select.js.coffee43
-rw-r--r--app/assets/javascripts/notes.js.coffee10
-rw-r--r--app/assets/javascripts/notes_votes.js.coffee4
-rw-r--r--app/assets/javascripts/password_strength.js.coffee31
-rw-r--r--app/assets/javascripts/profile.js.coffee45
-rw-r--r--app/assets/javascripts/project.js.coffee71
-rw-r--r--app/assets/javascripts/project_import.js.coffee4
-rw-r--r--app/assets/javascripts/project_new.js.coffee25
-rw-r--r--app/assets/javascripts/project_show.js.coffee15
-rw-r--r--app/assets/javascripts/project_users_select.js.coffee19
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee4
-rw-r--r--app/assets/javascripts/stat_graph.js.coffee2
-rw-r--r--app/assets/javascripts/stat_graph_contributors.js.coffee17
-rw-r--r--app/assets/javascripts/stat_graph_contributors_graph.js.coffee6
-rw-r--r--app/assets/javascripts/stat_graph_contributors_util.js.coffee2
-rw-r--r--app/assets/javascripts/team_members.js.coffee6
-rw-r--r--app/assets/javascripts/tree.js.coffee4
-rw-r--r--app/assets/javascripts/user.js.coffee3
-rw-r--r--app/assets/javascripts/users_select.js.coffee53
-rw-r--r--app/assets/javascripts/wikis.js.coffee5
-rw-r--r--app/assets/javascripts/zen_mode.js.coffee4
-rw-r--r--app/assets/stylesheets/behaviors.scss26
-rw-r--r--app/assets/stylesheets/generic/common.scss3
-rw-r--r--app/assets/stylesheets/generic/gfm.scss20
-rw-r--r--app/assets/stylesheets/generic/issuable.scss9
-rw-r--r--app/assets/stylesheets/generic/issue_box.scss35
-rw-r--r--app/assets/stylesheets/generic/lists.scss4
-rw-r--r--app/assets/stylesheets/gl_bootstrap.scss42
-rw-r--r--app/assets/stylesheets/highlight/white.scss8
-rw-r--r--app/assets/stylesheets/main/fonts.scss2
-rw-r--r--app/assets/stylesheets/main/mixins.scss11
-rw-r--r--app/assets/stylesheets/main/variables.scss1
-rw-r--r--app/assets/stylesheets/sections/issues.scss2
-rw-r--r--app/assets/stylesheets/sections/merge_requests.scss58
-rw-r--r--app/assets/stylesheets/sections/nav.scss41
-rw-r--r--app/assets/stylesheets/sections/notes.scss3
-rw-r--r--app/assets/stylesheets/sections/profile.scss17
-rw-r--r--app/controllers/admin/background_jobs_controller.rb2
-rw-r--r--app/controllers/admin/projects_controller.rb10
-rw-r--r--app/controllers/admin/users_controller.rb1
-rw-r--r--app/controllers/application_controller.rb64
-rw-r--r--app/controllers/confirmations_controller.rb17
-rw-r--r--app/controllers/explore/groups_controller.rb3
-rw-r--r--app/controllers/explore/projects_controller.rb3
-rw-r--r--app/controllers/groups/group_members_controller.rb1
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb48
-rw-r--r--app/controllers/projects/base_tree_controller.rb3
-rw-r--r--app/controllers/projects/blame_controller.rb3
-rw-r--r--app/controllers/projects/blob_controller.rb7
-rw-r--r--app/controllers/projects/branches_controller.rb6
-rw-r--r--app/controllers/projects/commit_controller.rb21
-rw-r--r--app/controllers/projects/commits_controller.rb5
-rw-r--r--app/controllers/projects/compare_controller.rb3
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb2
-rw-r--r--app/controllers/projects/edit_tree_controller.rb4
-rw-r--r--app/controllers/projects/graphs_controller.rb28
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb18
-rw-r--r--app/controllers/projects/network_controller.rb3
-rw-r--r--app/controllers/projects/new_tree_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb3
-rw-r--r--app/controllers/projects/refs_controller.rb3
-rw-r--r--app/controllers/projects/repositories_controller.rb8
-rw-r--r--app/controllers/projects/services_controller.rb4
-rw-r--r--app/controllers/projects/snippets_controller.rb7
-rw-r--r--app/controllers/projects/tags_controller.rb6
-rw-r--r--app/controllers/projects/team_members_controller.rb8
-rw-r--r--app/controllers/projects_controller.rb16
-rw-r--r--app/controllers/registrations_controller.rb4
-rw-r--r--app/controllers/sessions_controller.rb4
-rw-r--r--app/controllers/snippets_controller.rb42
-rw-r--r--app/finders/README.md8
-rw-r--r--app/finders/issuable_finder.rb6
-rw-r--r--app/finders/snippets_finder.rb63
-rw-r--r--app/helpers/application_helper.rb20
-rw-r--r--app/helpers/commits_helper.rb4
-rw-r--r--app/helpers/dashboard_helper.rb33
-rw-r--r--app/helpers/events_helper.rb15
-rw-r--r--app/helpers/gitlab_markdown_helper.rb62
-rw-r--r--app/helpers/icons_helper.rb10
-rw-r--r--app/helpers/issues_helper.rb17
-rw-r--r--app/helpers/merge_requests_helper.rb7
-rw-r--r--app/helpers/notes_helper.rb2
-rw-r--r--app/helpers/notifications_helper.rb8
-rw-r--r--app/helpers/oauth_helper.rb2
-rw-r--r--app/helpers/profile_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb20
-rw-r--r--app/helpers/tab_helper.rb2
-rw-r--r--app/helpers/tags_helper.rb4
-rw-r--r--app/helpers/tree_helper.rb10
-rw-r--r--app/helpers/visibility_level_helper.rb17
-rw-r--r--app/models/ability.rb12
-rw-r--r--app/models/commit.rb37
-rw-r--r--app/models/concerns/issuable.rb5
-rw-r--r--app/models/concerns/mentionable.rb22
-rw-r--r--app/models/concerns/taskable.rb51
-rw-r--r--app/models/event.rb6
-rw-r--r--app/models/hooks/web_hook.rb1
-rw-r--r--app/models/issue.rb10
-rw-r--r--app/models/member.rb15
-rw-r--r--app/models/members/group_member.rb15
-rw-r--r--app/models/members/project_member.rb17
-rw-r--r--app/models/merge_request.rb15
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/namespace.rb2
-rw-r--r--app/models/network/graph.rb2
-rw-r--r--app/models/note.rb107
-rw-r--r--app/models/personal_snippet.rb22
-rw-r--r--app/models/project.rb60
-rw-r--r--app/models/project_services/assembla_service.rb16
-rw-r--r--app/models/project_services/bamboo_service.rb105
-rw-r--r--app/models/project_services/buildbox_service.rb123
-rw-r--r--app/models/project_services/campfire_service.rb16
-rw-r--r--app/models/project_services/ci_service.rb16
-rw-r--r--app/models/project_services/emails_on_push_service.rb16
-rw-r--r--app/models/project_services/flowdock_service.rb19
-rw-r--r--app/models/project_services/gemnasium_service.rb19
-rw-r--r--app/models/project_services/gitlab_ci_service.rb41
-rw-r--r--app/models/project_services/hipchat_service.rb16
-rw-r--r--app/models/project_services/pivotaltracker_service.rb16
-rw-r--r--app/models/project_services/pushover_service.rb113
-rw-r--r--app/models/project_services/slack_service.rb32
-rw-r--r--app/models/project_snippet.rb22
-rw-r--r--app/models/project_team.rb6
-rw-r--r--app/models/repository.rb52
-rw-r--r--app/models/service.rb17
-rw-r--r--app/models/snippet.rb38
-rw-r--r--app/models/tree.rb2
-rw-r--r--app/models/user.rb48
-rw-r--r--app/services/base_service.rb8
-rw-r--r--app/services/files/base_service.rb6
-rw-r--r--app/services/git_push_service.rb49
-rw-r--r--app/services/issuable_base_service.rb13
-rw-r--r--app/services/issues/base_service.rb12
-rw-r--r--app/services/issues/update_service.rb7
-rw-r--r--app/services/merge_requests/base_merge_service.rb3
-rw-r--r--app/services/merge_requests/base_service.rb15
-rw-r--r--app/services/merge_requests/refresh_service.rb67
-rw-r--r--app/services/merge_requests/update_service.rb8
-rw-r--r--app/services/notification_service.rb3
-rw-r--r--app/services/system_hooks_service.rb2
-rw-r--r--app/uploaders/attachment_uploader.rb4
-rw-r--r--app/views/admin/background_jobs/show.html.haml10
-rw-r--r--app/views/admin/broadcast_messages/index.html.haml4
-rw-r--r--app/views/admin/groups/_form.html.haml25
-rw-r--r--app/views/admin/groups/index.html.haml4
-rw-r--r--app/views/admin/groups/show.html.haml10
-rw-r--r--app/views/admin/logs/show.html.haml87
-rw-r--r--app/views/admin/projects/index.html.haml2
-rw-r--r--app/views/admin/projects/show.html.haml8
-rw-r--r--app/views/admin/users/index.html.haml30
-rw-r--r--app/views/admin/users/show.html.haml15
-rw-r--r--app/views/dashboard/_groups.html.haml4
-rw-r--r--app/views/dashboard/_project.html.haml2
-rw-r--r--app/views/dashboard/_projects.html.haml4
-rw-r--r--app/views/dashboard/_projects_filter.html.haml4
-rw-r--r--app/views/dashboard/_sidebar.html.haml2
-rw-r--r--app/views/dashboard/_zero_authorized_projects.html.haml8
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/dashboard/projects.html.haml8
-rw-r--r--app/views/dashboard/show.html.haml2
-rw-r--r--app/views/devise/passwords/edit.html.haml4
-rw-r--r--app/views/devise/registrations/new.html.haml4
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml4
-rw-r--r--app/views/devise/sessions/new.html.haml27
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/events/_event_push.atom.haml2
-rw-r--r--app/views/events/event/_note.html.haml4
-rw-r--r--app/views/events/event/_push.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml4
-rw-r--r--app/views/explore/projects/_project.html.haml3
-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/_projects.html.haml4
-rw-r--r--app/views/groups/_settings_nav.html.haml4
-rw-r--r--app/views/groups/edit.html.haml19
-rw-r--r--app/views/groups/group_members/_group_member.html.haml6
-rw-r--r--app/views/groups/issues.html.haml2
-rw-r--r--app/views/groups/members.html.haml4
-rw-r--r--app/views/groups/merge_requests.html.haml2
-rw-r--r--app/views/groups/milestones/index.html.haml2
-rw-r--r--app/views/groups/new.html.haml25
-rw-r--r--app/views/groups/projects.html.haml2
-rw-r--r--app/views/groups/show.html.haml4
-rw-r--r--app/views/help/_shortcuts.html.haml56
-rw-r--r--app/views/help/index.html.haml4
-rw-r--r--app/views/layouts/_broadcast.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml3
-rw-r--r--app/views/layouts/_head_panel.html.haml18
-rw-r--r--app/views/layouts/_public_head_panel.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/notify/new_ssh_key_email.html.haml2
-rw-r--r--app/views/notify/new_ssh_key_email.text.erb4
-rw-r--r--app/views/notify/repository_push_email.html.haml4
-rw-r--r--app/views/notify/repository_push_email.text.haml4
-rw-r--r--app/views/profiles/accounts/show.html.haml2
-rw-r--r--app/views/profiles/groups/index.html.haml6
-rw-r--r--app/views/profiles/keys/new.html.haml2
-rw-r--r--app/views/profiles/notifications/_settings.html.haml2
-rw-r--r--app/views/profiles/notifications/show.html.haml12
-rw-r--r--app/views/profiles/passwords/edit.html.haml2
-rw-r--r--app/views/profiles/passwords/new.html.haml2
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/projects/_commit_button.html.haml9
-rw-r--r--app/views/projects/_dropdown.html.haml6
-rw-r--r--app/views/projects/_home_panel.html.haml4
-rw-r--r--app/views/projects/_issuable_form.html.haml37
-rw-r--r--app/views/projects/_settings_nav.html.haml12
-rw-r--r--app/views/projects/blame/show.html.haml6
-rw-r--r--app/views/projects/blob/_actions.html.haml24
-rw-r--r--app/views/projects/blob/_blob.html.haml4
-rw-r--r--app/views/projects/blob/_download.html.haml2
-rw-r--r--app/views/projects/blob/_remove.html.haml10
-rw-r--r--app/views/projects/branches/_branch.html.haml6
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/branches/new.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml6
-rw-r--r--app/views/projects/commits/_commit.html.haml4
-rw-r--r--app/views/projects/commits/_commits.html.haml2
-rw-r--r--app/views/projects/commits/_head.html.haml4
-rw-r--r--app/views/projects/commits/_inline_commit.html.haml2
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/compare/_form.html.haml2
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml6
-rw-r--r--app/views/projects/deploy_keys/index.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml24
-rw-r--r--app/views/projects/diffs/_stats.html.haml10
-rw-r--r--app/views/projects/edit.html.haml162
-rw-r--r--app/views/projects/edit_tree/show.html.haml28
-rw-r--r--app/views/projects/fork.html.haml4
-rw-r--r--app/views/projects/graphs/_head.html.haml5
-rw-r--r--app/views/projects/graphs/commits.html.haml85
-rw-r--r--app/views/projects/graphs/show.html.haml32
-rw-r--r--app/views/projects/graphs/show.js.haml19
-rw-r--r--app/views/projects/import.html.haml5
-rw-r--r--app/views/projects/issues/_form.html.haml17
-rw-r--r--app/views/projects/issues/_head.html.haml8
-rw-r--r--app/views/projects/issues/_issue.html.haml12
-rw-r--r--app/views/projects/issues/_issue_context.html.haml3
-rw-r--r--app/views/projects/issues/_issues.html.haml4
-rw-r--r--app/views/projects/issues/index.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml11
-rw-r--r--app/views/projects/merge_requests/_form.html.haml20
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml12
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml8
-rw-r--r--app/views/projects/merge_requests/_show.html.haml6
-rw-r--r--app/views/projects/merge_requests/index.html.haml8
-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.haml6
-rw-r--r--app/views/projects/merge_requests/show/_mr_accept.html.haml26
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_ci.html.haml13
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml11
-rw-r--r--app/views/projects/merge_requests/show/_remove_source_branch.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_state_widget.html.haml18
-rw-r--r--app/views/projects/milestones/_form.html.haml4
-rw-r--r--app/views/projects/milestones/_milestone.html.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml4
-rw-r--r--app/views/projects/milestones/show.html.haml4
-rw-r--r--app/views/projects/network/show.html.haml4
-rw-r--r--app/views/projects/new.html.haml9
-rw-r--r--app/views/projects/new_tree/show.html.haml23
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply.html.haml2
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml4
-rw-r--r--app/views/projects/notes/_form.html.haml4
-rw-r--r--app/views/projects/notes/_note.html.haml18
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml2
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml2
-rw-r--r--app/views/projects/notes/discussions/_diff.html.haml2
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml2
-rw-r--r--app/views/projects/protected_branches/index.html.haml8
-rw-r--r--app/views/projects/repositories/_download_archive.html.haml16
-rw-r--r--app/views/projects/repositories/stats.html.haml33
-rw-r--r--app/views/projects/services/_form.html.haml7
-rw-r--r--app/views/projects/show.html.haml112
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml7
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/new.html.haml4
-rw-r--r--app/views/projects/team_members/_group_members.html.haml2
-rw-r--r--app/views/projects/team_members/_team_member.html.haml4
-rw-r--r--app/views/projects/team_members/import.html.haml2
-rw-r--r--app/views/projects/tree/_readme.html.haml2
-rw-r--r--app/views/projects/tree/_spinner.html.haml2
-rw-r--r--app/views/projects/tree/_submodule_item.html.haml6
-rw-r--r--app/views/projects/tree/_tree.html.haml6
-rw-r--r--app/views/projects/wikis/_form.html.haml4
-rw-r--r--app/views/projects/wikis/_main_links.html.haml2
-rw-r--r--app/views/projects/wikis/_nav.html.haml4
-rw-r--r--app/views/projects/wikis/history.html.haml2
-rw-r--r--app/views/search/_filter.html.haml4
-rw-r--r--app/views/search/_project_filter.html.haml8
-rw-r--r--app/views/search/_snippet_filter.html.haml4
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/search/results/_empty.html.haml2
-rw-r--r--app/views/search/results/_note.html.haml4
-rw-r--r--app/views/search/results/_snippet_blob.html.haml4
-rw-r--r--app/views/search/results/_snippet_title.html.haml2
-rw-r--r--app/views/search/results/_wiki_blob.html.haml2
-rw-r--r--app/views/search/show.html.haml2
-rw-r--r--app/views/shared/_choose_group_avatar_button.html.haml7
-rw-r--r--app/views/shared/_commit_message_container.html.haml17
-rw-r--r--app/views/shared/_confirm_modal.html.haml22
-rw-r--r--app/views/shared/_file_hljs.html.haml2
-rw-r--r--app/views/shared/_filter.html.haml2
-rw-r--r--app/views/shared/_group_form.html.haml12
-rw-r--r--app/views/shared/_group_tips.html.haml6
-rw-r--r--app/views/shared/_project_filter.html.haml6
-rw-r--r--app/views/shared/_promo.html.haml4
-rw-r--r--app/views/shared/snippets/_form.html.haml18
-rw-r--r--app/views/shared/snippets/_visibility_level.html.haml27
-rw-r--r--app/views/snippets/_snippet.html.haml2
-rw-r--r--app/views/snippets/current_user_index.html.haml5
-rw-r--r--app/views/snippets/index.html.haml10
-rw-r--r--app/views/snippets/show.html.haml4
-rw-r--r--app/views/snippets/user_index.html.haml5
-rw-r--r--app/views/users/_groups.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/repository_import_worker.rb2
-rw-r--r--config/application.rb7
-rw-r--r--config/gitlab.yml.example117
-rw-r--r--config/initializers/1_settings.rb26
-rw-r--r--config/initializers/7_omniauth.rb12
-rw-r--r--config/initializers/devise.rb32
-rw-r--r--config/initializers/disable_email_interceptor.rb2
-rw-r--r--config/initializers/gitlab_shell_secret_token.rb19
-rw-r--r--config/initializers/time_zone.rb1
-rw-r--r--config/routes.rb9
-rw-r--r--config/unicorn.rb.example1
-rw-r--r--db/fixtures/development/01_admin.rb22
-rw-r--r--db/fixtures/development/04_project.rb2
-rw-r--r--db/fixtures/development/10_merge_requests.rb18
-rw-r--r--db/fixtures/development/12_snippets.rb34
-rw-r--r--db/fixtures/production/001_admin.rb16
-rw-r--r--db/migrate/20140907220153_serialize_service_properties.rb2
-rw-r--r--db/migrate/20141006143943_move_slack_service_to_webhook.rb17
-rw-r--r--db/migrate/20141007100818_add_visibility_level_to_snippet.rb21
-rw-r--r--db/schema.rb7
-rw-r--r--doc/README.md3
-rw-r--r--doc/api/README.md12
-rw-r--r--doc/api/branches.md8
-rw-r--r--doc/api/issues.md3
-rw-r--r--doc/api/projects.md17
-rw-r--r--doc/api/repositories.md4
-rw-r--r--doc/api/services.md46
-rw-r--r--doc/api/users.md9
-rw-r--r--doc/customization/issue_closing.md5
-rw-r--r--doc/customization/libravatar.md69
-rw-r--r--doc/customization/welcome_message.md4
-rw-r--r--doc/development/architecture.md2
-rw-r--r--doc/development/ci_setup.md28
-rw-r--r--doc/development/shell_commands.md6
-rw-r--r--doc/install/installation.md42
-rw-r--r--doc/install/requirements.md14
-rw-r--r--doc/integration/ldap.md104
-rw-r--r--doc/integration/slack.md28
-rw-r--r--doc/markdown/markdown.md23
-rw-r--r--doc/project_services/bamboo.md60
-rw-r--r--doc/project_services/project_services.md18
-rw-r--r--doc/raketasks/backup_restore.md97
-rw-r--r--doc/raketasks/import.md47
-rw-r--r--doc/release/monthly.md187
-rw-r--r--doc/release/patch.md35
-rw-r--r--doc/release/security.md4
-rw-r--r--doc/update/4.2-to-5.0.md6
-rw-r--r--doc/update/6.x-or-7.x-to-7.4.md (renamed from doc/update/6.0-to-7.3.md)106
-rw-r--r--doc/update/7.2-to-7.3.md5
-rw-r--r--doc/update/7.3-to-7.4.md191
-rw-r--r--doc/update/README.md20
-rw-r--r--doc/update/patch_versions.md6
-rw-r--r--doc/update/upgrader.md17
-rw-r--r--doc/web_hooks/web_hooks.md36
-rw-r--r--doc/workflow/README.md3
-rw-r--r--doc/workflow/ci_mr.pngbin0 -> 40065 bytes
-rw-r--r--doc/workflow/close_issue_mr.pngbin0 -> 146292 bytes
-rw-r--r--doc/workflow/environment_branches.pngbin0 -> 40210 bytes
-rw-r--r--doc/workflow/four_stages.pngbin0 -> 20934 bytes
-rw-r--r--doc/workflow/git_pull.pngbin0 -> 167056 bytes
-rw-r--r--doc/workflow/gitdashflow.pngbin0 -> 184726 bytes
-rw-r--r--doc/workflow/github_flow.pngbin0 -> 20600 bytes
-rw-r--r--doc/workflow/gitlab_flow.md316
-rw-r--r--doc/workflow/gitlab_flow.pngbin0 -> 90883 bytes
-rw-r--r--doc/workflow/good_commit.pngbin0 -> 28433 bytes
-rw-r--r--doc/workflow/merge_commits.pngbin0 -> 41422 bytes
-rw-r--r--doc/workflow/merge_request.pngbin0 -> 169503 bytes
-rw-r--r--doc/workflow/messy_flow.pngbin0 -> 33829 bytes
-rw-r--r--doc/workflow/migrating_from_svn.md17
-rw-r--r--doc/workflow/mr_inline_comments.pngbin0 -> 193311 bytes
-rw-r--r--doc/workflow/notifications.md71
-rw-r--r--doc/workflow/notifications/settings.pngbin0 -> 114727 bytes
-rw-r--r--doc/workflow/production_branch.pngbin0 -> 21716 bytes
-rw-r--r--doc/workflow/rebase.pngbin0 -> 123041 bytes
-rw-r--r--doc/workflow/release_branches.pngbin0 -> 44173 bytes
-rw-r--r--doc/workflow/remove_checkbox.pngbin0 -> 22272 bytes
-rw-r--r--doc/workflow/voting_slider.pngbin0 -> 5329 bytes
-rw-r--r--features/admin/active_tab.feature2
-rw-r--r--features/admin/groups.feature7
-rw-r--r--features/admin/users.feature6
-rw-r--r--features/dashboard/active_tab.feature2
-rw-r--r--features/dashboard/archived_projects.feature2
-rw-r--r--features/dashboard/event_filters.feature2
-rw-r--r--features/dashboard/help.feature2
-rw-r--r--features/dashboard/projects.feature2
-rw-r--r--features/dashboard/shortcuts.feature2
-rw-r--r--features/explore/groups.feature (renamed from features/explore/public_groups.feature)2
-rw-r--r--features/explore/projects.feature2
-rw-r--r--features/groups.feature (renamed from features/group.feature)0
-rw-r--r--features/profile/active_tab.feature2
-rw-r--r--features/profile/profile.feature19
-rw-r--r--features/project/active_tab.feature2
-rw-r--r--features/project/commits/branches.feature2
-rw-r--r--features/project/commits/comments.feature2
-rw-r--r--features/project/commits/commits.feature9
-rw-r--r--features/project/commits/diff_comments.feature2
-rw-r--r--features/project/commits/tags.feature2
-rw-r--r--features/project/commits/user_lookup.feature2
-rw-r--r--features/project/create.feature2
-rw-r--r--features/project/fork.feature2
-rw-r--r--features/project/forked_merge_requests.feature26
-rw-r--r--features/project/graph.feature7
-rw-r--r--features/project/issues/filter_labels.feature2
-rw-r--r--features/project/issues/issues.feature33
-rw-r--r--features/project/issues/labels.feature2
-rw-r--r--features/project/issues/milestones.feature2
-rw-r--r--features/project/merge_requests.feature38
-rw-r--r--features/project/network_graph.feature (renamed from features/project/network.feature)0
-rw-r--r--features/project/project.feature11
-rw-r--r--features/project/service.feature12
-rw-r--r--features/project/shortcuts.feature2
-rw-r--r--features/project/source/browse_files.feature66
-rw-r--r--features/project/source/git_blame.feature4
-rw-r--r--features/project/source/markdown_render.feature2
-rw-r--r--features/project/source/multiselect_blob.feature2
-rw-r--r--features/project/source/search_code.feature2
-rw-r--r--features/project/team_management.feature2
-rw-r--r--features/snippets/discover.feature4
-rw-r--r--features/snippets/public_snippets.feature10
-rw-r--r--features/snippets/snippets.feature4
-rw-r--r--features/snippets/user.feature13
-rw-r--r--features/steps/admin/groups.rb23
-rw-r--r--features/steps/admin/users.rb19
-rw-r--r--features/steps/dashboard/archived_projects.rb (renamed from features/steps/dashboard/with_archived_projects.rb)2
-rw-r--r--features/steps/dashboard/dashboard.rb4
-rw-r--r--features/steps/dashboard/event_filters.rb2
-rw-r--r--features/steps/dashboard/help.rb (renamed from features/steps/help.rb)2
-rw-r--r--features/steps/dashboard/issues.rb10
-rw-r--r--features/steps/dashboard/merge_requests.rb46
-rw-r--r--features/steps/explore/groups.rb (renamed from features/steps/explore/groups_feature.rb)2
-rw-r--r--features/steps/explore/projects.rb2
-rw-r--r--features/steps/groups.rb (renamed from features/steps/group/group.rb)2
-rw-r--r--features/steps/profile/group.rb8
-rw-r--r--features/steps/profile/profile.rb42
-rw-r--r--features/steps/project/browse_files.rb108
-rw-r--r--features/steps/project/commits/branches.rb (renamed from features/steps/project/browse_branches.rb)2
-rw-r--r--features/steps/project/commits/comments.rb (renamed from features/steps/project/comments_on_commits.rb)2
-rw-r--r--features/steps/project/commits/commits.rb (renamed from features/steps/project/browse_commits.rb)4
-rw-r--r--features/steps/project/commits/diff_comments.rb (renamed from features/steps/project/comments_on_commit_diffs.rb)2
-rw-r--r--features/steps/project/commits/tags.rb (renamed from features/steps/project/browse_tags.rb)2
-rw-r--r--features/steps/project/commits/user_lookup.rb (renamed from features/steps/project/browse_commits_user_lookup.rb)2
-rw-r--r--features/steps/project/create.rb2
-rw-r--r--features/steps/project/fork.rb2
-rw-r--r--features/steps/project/forked_merge_requests.rb4
-rw-r--r--features/steps/project/graph.rb10
-rw-r--r--features/steps/project/issues/filter_labels.rb (renamed from features/steps/project/filter_labels.rb)2
-rw-r--r--features/steps/project/issues/issues.rb (renamed from features/steps/project/issues.rb)15
-rw-r--r--features/steps/project/issues/labels.rb (renamed from features/steps/project/labels.rb)2
-rw-r--r--features/steps/project/issues/milestones.rb (renamed from features/steps/project/milestones.rb)2
-rw-r--r--features/steps/project/merge_requests.rb30
-rw-r--r--features/steps/project/project.rb24
-rw-r--r--features/steps/project/services.rb51
-rw-r--r--features/steps/project/snippets.rb4
-rw-r--r--features/steps/project/source/browse_files.rb184
-rw-r--r--features/steps/project/source/git_blame.rb (renamed from features/steps/project/browse_git_repo.rb)6
-rw-r--r--features/steps/project/source/markdown_render.rb (renamed from features/steps/project/markdown_render.rb)2
-rw-r--r--features/steps/project/source/multiselect_blob.rb (renamed from features/steps/project/multiselect_blob.rb)2
-rw-r--r--features/steps/project/source/search_code.rb (renamed from features/steps/project/search_code.rb)2
-rw-r--r--features/steps/search.rb4
-rw-r--r--features/steps/shared/markdown.rb45
-rw-r--r--features/steps/shared/note.rb14
-rw-r--r--features/steps/shared/paths.rb31
-rw-r--r--features/steps/shared/project.rb2
-rw-r--r--features/steps/shared/snippet.rb25
-rw-r--r--features/steps/snippets/discover.rb6
-rw-r--r--features/steps/snippets/public_snippets.rb25
-rw-r--r--features/steps/snippets/snippets.rb4
-rw-r--r--features/steps/snippets/user.rb20
-rw-r--r--features/support/env.rb2
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/branches.rb5
-rw-r--r--lib/api/entities.rb31
-rw-r--r--lib/api/files.rb6
-rw-r--r--lib/api/group_members.rb80
-rw-r--r--lib/api/groups.rb51
-rw-r--r--lib/api/helpers.rb10
-rw-r--r--lib/api/internal.rb17
-rw-r--r--lib/api/issues.rb15
-rw-r--r--lib/api/project_hooks.rb16
-rw-r--r--lib/api/projects.rb19
-rw-r--r--lib/api/repositories.rb5
-rw-r--r--lib/api/services.rb38
-rw-r--r--lib/backup/database.rb2
-rw-r--r--lib/backup/manager.rb29
-rw-r--r--lib/backup/repository.rb27
-rw-r--r--lib/disable_email_interceptor.rb8
-rw-r--r--lib/event_filter.rb8
-rw-r--r--lib/gitlab/app_logger.rb4
-rw-r--r--lib/gitlab/auth.rb16
-rw-r--r--lib/gitlab/backend/grack_auth.rb2
-rw-r--r--lib/gitlab/backend/shell.rb54
-rw-r--r--lib/gitlab/closing_issue_extractor.rb2
-rw-r--r--lib/gitlab/diff/parser.rb2
-rw-r--r--lib/gitlab/git.rb5
-rw-r--r--lib/gitlab/git_access.rb45
-rw-r--r--lib/gitlab/git_access_wiki.rb7
-rw-r--r--lib/gitlab/git_logger.rb4
-rw-r--r--lib/gitlab/git_ref_validator.rb3
-rw-r--r--lib/gitlab/graphs/commits.rb49
-rw-r--r--lib/gitlab/inline_diff.rb6
-rw-r--r--lib/gitlab/issues_labels.rb1
-rw-r--r--lib/gitlab/ldap/access.rb32
-rw-r--r--lib/gitlab/ldap/adapter.rb69
-rw-r--r--lib/gitlab/ldap/authentication.rb71
-rw-r--r--lib/gitlab/ldap/config.rb120
-rw-r--r--lib/gitlab/ldap/person.rb26
-rw-r--r--lib/gitlab/ldap/user.rb93
-rw-r--r--lib/gitlab/logger.rb6
-rw-r--r--lib/gitlab/markdown.rb156
-rw-r--r--lib/gitlab/markdown_helper.rb4
-rw-r--r--lib/gitlab/oauth/auth_hash.rb2
-rw-r--r--lib/gitlab/oauth/user.rb94
-rw-r--r--lib/gitlab/production_logger.rb7
-rw-r--r--lib/gitlab/reference_extractor.rb56
-rw-r--r--lib/gitlab/regex.rb3
-rw-r--r--lib/gitlab/satellite/satellite.rb2
-rw-r--r--lib/gitlab/sidekiq_logger.rb7
-rw-r--r--lib/gitlab/url_builder.rb2
-rw-r--r--lib/gitlab/utils.rb13
-rw-r--r--lib/redcarpet/render/gitlab_html.rb18
-rw-r--r--lib/support/nginx/gitlab10
-rw-r--r--lib/support/nginx/gitlab-ssl48
-rw-r--r--lib/tasks/gitlab/check.rake50
-rw-r--r--lib/tasks/gitlab/import.rake14
-rw-r--r--lib/tasks/gitlab/shell.rake14
-rw-r--r--lib/tasks/test.rake7
-rw-r--r--spec/factories.rb5
-rw-r--r--spec/factories/projects.rb10
-rw-r--r--spec/features/projects_spec.rb21
-rw-r--r--spec/features/users_spec.rb2
-rw-r--r--spec/finders/snippets_finder_spec.rb101
-rw-r--r--spec/helpers/events_helper_spec.rb52
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb253
-rw-r--r--spec/helpers/notifications_helper_spec.rb8
-rw-r--r--spec/lib/disable_email_interceptor_spec.rb26
-rw-r--r--spec/lib/gitlab/auth_spec.rb17
-rw-r--r--spec/lib/gitlab/git_access_spec.rb7
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb22
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb17
-rw-r--r--spec/lib/gitlab/ldap/adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/authentication_spec.rb53
-rw-r--r--spec/lib/gitlab/ldap/config_spec.rb34
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb40
-rw-r--r--spec/lib/gitlab/oauth/auth_hash_spec.rb55
-rw-r--r--spec/lib/gitlab/oauth/user_spec.rb131
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb52
-rw-r--r--spec/lib/gitlab/satellite/action_spec.rb2
-rw-r--r--spec/mailers/notify_spec.rb9
-rw-r--r--spec/models/assembla_service_spec.rb16
-rw-r--r--spec/models/buildbox_service_spec.rb73
-rw-r--r--spec/models/commit_spec.rb16
-rw-r--r--spec/models/concerns/mentionable_spec.rb14
-rw-r--r--spec/models/event_spec.rb3
-rw-r--r--spec/models/flowdock_service_spec.rb16
-rw-r--r--spec/models/gemnasium_service_spec.rb16
-rw-r--r--spec/models/gitlab_ci_service_spec.rb20
-rw-r--r--spec/models/group_member_spec.rb8
-rw-r--r--spec/models/issue_spec.rb4
-rw-r--r--spec/models/merge_request_spec.rb4
-rw-r--r--spec/models/note_spec.rb32
-rw-r--r--spec/models/project_member_spec.rb10
-rw-r--r--spec/models/project_snippet_spec.rb22
-rw-r--r--spec/models/project_spec.rb58
-rw-r--r--spec/models/project_team_spec.rb4
-rw-r--r--spec/models/pushover_service_spec.rb69
-rw-r--r--spec/models/repository_spec.rb21
-rw-r--r--spec/models/service_spec.rb16
-rw-r--r--spec/models/slack_message_spec.rb8
-rw-r--r--spec/models/slack_service_spec.rb38
-rw-r--r--spec/models/snippet_spec.rb22
-rw-r--r--spec/models/user_spec.rb61
-rw-r--r--spec/requests/api/branches_spec.rb1
-rw-r--r--spec/requests/api/fork_spec.rb73
-rw-r--r--spec/requests/api/group_members_spec.rb136
-rw-r--r--spec/requests/api/groups_spec.rb110
-rw-r--r--spec/requests/api/internal_spec.rb14
-rw-r--r--spec/requests/api/issues_spec.rb64
-rw-r--r--spec/requests/api/namespaces_spec.rb3
-rw-r--r--spec/requests/api/projects_spec.rb24
-rw-r--r--spec/requests/api/repositories_spec.rb36
-rw-r--r--spec/requests/api/services_spec.rb26
-rw-r--r--spec/requests/api/session_spec.rb26
-rw-r--r--spec/requests/api/users_spec.rb14
-rw-r--r--spec/services/git_push_service_spec.rb30
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb98
-rw-r--r--spec/services/projects/transfer_service_spec.rb20
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/login_helpers.rb2
-rw-r--r--spec/support/mentionable_shared_examples.rb47
-rw-r--r--spec/support/taskable_shared_examples.rb42
-rw-r--r--spec/support/test_env.rb58
-rwxr-xr-xvendor/assets/javascripts/chart-lib.min.js11
-rw-r--r--vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js659
646 files changed, 9045 insertions, 3392 deletions
diff --git a/.gitignore b/.gitignore
index 4f778371512..2c6b65b7b7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,4 @@ public/assets/
.envrc
dump.rdb
tags
+.gitlab_shell_secret
diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 00000000000..ac2cdeba013
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+2.1.3
diff --git a/CHANGELOG b/CHANGELOG
index 8e5b3965480..977e0df8901 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,11 +1,72 @@
+v 7.5.0
+ - API: Add support for Hipchat (Kevin Houdebert)
+ - Add time zone configuration on gitlab.yml (Sullivan Senechal)
+ - Fix LDAP authentication for Git HTTP access
+ - Fix LDAP config lookup for provider 'ldap'
+ - Project title links to project homepage (Ben Bodenmiller)
+ - Add Atlassian Bamboo CI service (Drew Blessing)
+ - Mentioned @user will receive email even if he is not participating in issue or commit
+ - Session API: Use case-insensitive authentication like in UI (Andrey Krivko)
+ - Tie up loose ends with annotated tags: API & UI (Sean Edge)
+ - Return valid json for deleting branch via API (sponsored by O'Reilly Media)
+ - Expose username in project events API (sponsored by O'Reilly Media)
+ - Adds comments to commits in the API
+
+v 7.4.3
+ - Fix raw snippets view
+ - Fix security issue for member api
+ - Fix buildbox integration
+
+v 7.4.2
+ - Fix internal snippet exposing for unauthenticated users
+
+v 7.4.1
+ - Fix LDAP authentication for Git HTTP access
+ - Fix LDAP config lookup for provider 'ldap'
+ - Fix public snippets
+ - Fix 500 error on projects with nested submodules
+
v 7.4.0
- Refactored membership logic
- - Fix creating new files with web editor
- Improve error reporting on users API (Julien Bianchi)
- Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally
+ - Default branch is protected by default
- Increase unicorn timeout to 60 seconds
- Sort search autocomplete projects by stars count so most popular go first
- - Adds comments to commits in the API
+ - Add README to tab on project show page
+ - Do not delete tmp/repositories itself during clean-up, only its contents
+ - Support for backup uploads to remote storage
+ - Prevent notes polling when there are not notes
+ - API: Add support for forking a project via the API (Bernhard Kaindl)
+ - API: filter project issues by milestone (Julien Bianchi)
+ - Fail harder in the backup script
+ - Changes to Slack service structure, only webhook url needed
+ - Zen mode for wiki and milestones (Robert Schilling)
+ - Move Emoji parsing to html-pipeline-gitlab (Robert Schilling)
+ - Font Awesome 4.2 integration (Sullivan Senechal)
+ - Add Pushover service integration (Sullivan Senechal)
+ - Add select field type for services options (Sullivan Senechal)
+ - Add cross-project references to the Markdown parser (Vinnie Okada)
+ - Add task lists to issue and merge request descriptions (Vinnie Okada)
+ - Snippets can be public, internal or private
+ - Improve danger zone: ask project path to confirm data-loss action
+ - Raise exception on forgery
+ - Show build coverage in Merge Requests (requires GitLab CI v5.1)
+ - New milestone and label links on issue edit form
+ - Improved repository graphs
+ - Improve event note display in dashboard and project activity views (Vinnie Okada)
+ - Add users sorting to admin area
+ - UI improvements
+ - Fix ambiguous sha problem with mentioned commit
+ - Fixed bug with apostrophe when at mentioning users
+ - Add active directory ldap option
+ - Developers can push to wiki repo. Protected branches does not affect wiki repo any more
+ - Faster rev list
+ - Fix branch removal
+
+v 7.3.2
+ - Fix creating new file via web editor
+ - Use gitlab-shell v2.0.1
v 7.3.1
- Fix ref parsing in Gitlab::GitAccess
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3c26b997149..71435bc600d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic
## Security vulnerability disclosure
-Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
+Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Closing policy for issues and merge requests
@@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language
## Issue tracker
-To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
+To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://about.gitlab.com/subscription/) and [consulting services](http://about.gitlab.com/consultancy/) are available from [GitLab.com](http://about.gitlab.com/).
The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
@@ -54,6 +54,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and
Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls).
+If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint.
+
### Merge request guidelines
If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows:
@@ -61,7 +63,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Fork the project on GitLab Cloud
1. Create a feature branch
1. Write [tests](README.md#run-the-tests) and code
-1. Add your changes to the [CHANGELOG](CHANGELOG)
+1. Add your changes to the [CHANGELOG](CHANGELOG) insert your line at a [random point](doc/workflow/gitlab_flow.md#do-not-order-commits-with-rebase) in the current version
1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork
@@ -92,6 +94,7 @@ For examples of feedback on merge requests please look at already [closed merge
1. The change is as small as possible (see the above paragraph for details)
1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
+1. All tests have to pass, if you suspect a failing CI build is unrelated to your contribution ask for tests to be restarted. See [the CI setup document](http://doc.gitlab.com/ce/development/ci_setup.html) on who you can ask for test restart.
1. Initially contains a single commit (please use `git rebase -i` to squash commits)
1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server)
1. Does not break any existing functionality
@@ -100,7 +103,11 @@ For examples of feedback on merge requests please look at already [closed merge
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes
1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging.
-1. It conforms to the following style guides
+1. It conforms to the following style guides.
+ If your change touches a line that does not follow the style,
+ modify the entire line to follow it. This prevents linting tools from generating warnings.
+ Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications
+ may leave style non-compliant.
## Style guides
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 227cea21564..ccbccc3dc62 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.0.0
+2.2.0
diff --git a/Gemfile b/Gemfile
index c49c3d79cc8..dc84b26bc4c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -31,13 +31,13 @@ gem 'omniauth-shibboleth'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '7.0.0.rc2'
+gem "gitlab_git", '7.0.0.rc10'
# Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth
-gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap"
+gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap"
# Git Wiki
gem 'gollum-lib', '~> 3.0.0'
@@ -70,8 +70,8 @@ gem "carrierwave"
gem 'dropzonejs-rails'
# for aws storage
-gem "fog", "~> 1.14", group: :aws
-gem "unf", group: :aws
+gem "fog", "~> 1.14"
+gem "unf"
# Authorization
gem "six"
@@ -79,6 +79,9 @@ gem "six"
# Seed data
gem "seed-fu"
+# Markup pipeline for GitLab
+gem 'html-pipeline-gitlab', '~> 0.1.0'
+
# Markdown to HTML
gem "github-markup"
@@ -86,7 +89,7 @@ gem "github-markup"
gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
-gem 'org-ruby'
+gem 'org-ruby', '= 0.9.9'
gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1'
gem 'asciidoctor', '= 0.1.4'
@@ -140,7 +143,7 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2"
gem "gemnasium-gitlab-service", "~> 0.2"
# Slack integration
-gem "slack-notifier", "~> 0.3.2"
+gem "slack-notifier", "~> 1.0.0"
# d3
gem "d3_rails", "~> 3.1.4"
@@ -177,12 +180,13 @@ gem "jquery-ui-rails"
gem "jquery-scrollto-rails"
gem "raphael-rails", "~> 2.1.2"
gem 'bootstrap-sass', '~> 3.0'
-gem "font-awesome-rails", '~> 3.2'
+gem "font-awesome-rails", '~> 4.2'
gem "gitlab_emoji", "~> 0.0.1.1"
gem "gon", '~> 5.0.0'
gem 'nprogress-rails'
gem 'request_store'
gem "virtus"
+gem 'addressable'
group :development do
gem "annotate", "~> 2.6.0.beta2"
diff --git a/Gemfile.lock b/Gemfile.lock
index 26b406f7fa1..c283d4384fc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -152,7 +152,7 @@ GEM
net-ssh (>= 2.1.3)
fog-json (1.0.0)
multi_json (~> 1.0)
- font-awesome-rails (3.2.1.3)
+ font-awesome-rails (4.2.0.0)
railties (>= 3.2, < 5.0)
foreman (0.63.0)
dotenv (>= 0.7)
@@ -168,7 +168,7 @@ GEM
multi_json
gitlab-grack (2.0.0.pre)
rack (~> 1.5.1)
- gitlab-grit (2.6.11)
+ gitlab-grit (2.6.12)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
@@ -179,18 +179,17 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1)
- gitlab_git (7.0.0.rc2)
+ gitlab_git (7.0.0.rc10)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
- rubyzip (~> 1.1)
rugged (~> 0.21.0)
gitlab_meta (7.0)
- gitlab_omniauth-ldap (1.1.0)
- net-ldap (~> 0.7.0)
+ gitlab_omniauth-ldap (1.2.0)
+ net-ldap (~> 0.9)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
- rubyntlm (~> 0.1.1)
+ rubyntlm (~> 0.3)
gollum-lib (3.0.0)
github-markup (~> 1.1.0)
gitlab-grit (~> 2.6.5)
@@ -239,6 +238,14 @@ GEM
hipchat (0.14.0)
httparty
httparty
+ html-pipeline (1.11.0)
+ activesupport (>= 2)
+ nokogiri (~> 1.4)
+ html-pipeline-gitlab (0.1.5)
+ actionpack (~> 4)
+ gitlab_emoji (~> 0.0.1)
+ html-pipeline (~> 1.11.0)
+ sanitize (~> 2.1)
http_parser.rb (0.5.3)
httparty (0.13.0)
json (~> 1.8)
@@ -292,7 +299,7 @@ GEM
multi_xml (0.5.5)
multipart-post (1.2.0)
mysql2 (0.3.16)
- net-ldap (0.7.0)
+ net-ldap (0.9.0)
net-scp (1.1.2)
net-ssh (>= 2.6.5)
net-ssh (2.8.0)
@@ -327,7 +334,7 @@ GEM
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
- org-ruby (0.9.8)
+ org-ruby (0.9.9)
rubypants (~> 0.2)
orm_adapter (0.5.0)
pg (0.15.1)
@@ -438,9 +445,8 @@ GEM
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
ruby-progressbar (1.2.0)
- rubyntlm (0.1.1)
+ rubyntlm (0.4.0)
rubypants (0.2.0)
- rubyzip (1.1.6)
rugged (0.21.0)
safe_yaml (0.9.7)
sanitize (2.1.0)
@@ -482,7 +488,7 @@ GEM
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
six (0.2.0)
- slack-notifier (0.3.2)
+ slack-notifier (1.0.0)
slim (2.0.2)
temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1)
@@ -586,6 +592,7 @@ DEPENDENCIES
RedCloth
ace-rails-ap
acts-as-taggable-on
+ addressable
annotate (~> 2.6.0.beta2)
asciidoctor (= 0.1.4)
awesome_print
@@ -610,7 +617,7 @@ DEPENDENCIES
factory_girl_rails
ffaker
fog (~> 1.14)
- font-awesome-rails (~> 3.2)
+ font-awesome-rails (~> 4.2)
foreman
gemnasium-gitlab-service (~> 0.2)
github-markup
@@ -618,9 +625,9 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1)
- gitlab_git (= 7.0.0.rc2)
+ gitlab_git (= 7.0.0.rc10)
gitlab_meta (= 7.0)
- gitlab_omniauth-ldap (= 1.1.0)
+ gitlab_omniauth-ldap (= 1.2.0)
gollum-lib (~> 3.0.0)
gon (~> 5.0.0)
grape (~> 0.6.1)
@@ -630,6 +637,7 @@ DEPENDENCIES
guard-spinach
haml-rails
hipchat (~> 0.14.0)
+ html-pipeline-gitlab (~> 0.1.0)
httparty
jasmine (= 2.0.2)
jquery-atwho-rails (~> 0.3.3)
@@ -650,7 +658,7 @@ DEPENDENCIES
omniauth-google-oauth2
omniauth-shibboleth
omniauth-twitter
- org-ruby
+ org-ruby (= 0.9.9)
pg
poltergeist (~> 1.5.1)
pry
@@ -681,7 +689,7 @@ DEPENDENCIES
simplecov
sinatra
six
- slack-notifier (~> 0.3.2)
+ slack-notifier (~> 1.0.0)
slim
spinach-rails
spring (= 1.1.3)
diff --git a/PROCESS.md b/PROCESS.md
index c3a787662f7..1dd28d6b670 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -18,7 +18,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
- Responds to merge requests the issue team mentions them in and monitors for new merge requests
- Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.)
- Mark merge requests 'ready-for-merge' when they meet the contribution guidelines
-- Mention developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/)
+- Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/)
- Closes merge requests with no feedback from the reporter for two weeks
## Priorities of the issue team
@@ -30,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Mentioning people
-The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
+The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://about.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
## Workflow labels
@@ -79,7 +79,7 @@ Thanks for the issue report but we only support issues for the latest stable ver
### Support requests and configuration questions
-Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
+Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://about.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
### Code format
diff --git a/README.md b/README.md
index fbcde347be6..63fa5e3da86 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@
- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
-- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
+- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
- [![PullReview stats](https://www.pullreview.com/gitlab/gitlab-org/gitlab-ce/badges/master.svg?)](https://www.pullreview.com/gitlab.gitlab.com/gitlab-org/gitlab-ce/reviews/master)
@@ -38,17 +38,6 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
-## Third-party applications
-
-Access GitLab from multiple platforms with applications below.
-These applications are maintained by contributors, GitLab B.V. does not offer support for them.
-
-- [iPhone app](http://gitlabcontrol.com/)
-- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en)
-- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi)
-- [Command line client](https://github.com/drewblessing/gitlab-cli)
-- [Ruby API wrapper](https://github.com/NARKOZ/gitlab)
-
## Requirements
- Ubuntu/Debian/CentOS/RHEL**
@@ -61,7 +50,13 @@ These applications are maintained by contributors, GitLab B.V. does not offer su
## Installation
-Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/).
+Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options.
+Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
+
+## Third-party applications
+
+There are a lot of applications and API wrappers for GitLab.
+Find them [on our website](https://about.gitlab.com/applications/).
### New versions
diff --git a/VERSION b/VERSION
index 8b258729874..027a8b7b332 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-7.4.0-pre
+7.5.0.pre
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
index fdefbfb92bd..4f76d8ce486 100644
--- a/app/assets/javascripts/activities.js.coffee
+++ b/app/assets/javascripts/activities.js.coffee
@@ -1,4 +1,4 @@
-class Activities
+class @Activities
constructor: ->
Pager.init 20, true
$(".event_filter_link").bind "click", (event) =>
@@ -27,5 +27,3 @@ class Activities
event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(","), { path: '/' }
-
-@Activities = Activities
diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee
index a333eed87f2..bcb2e6df7c0 100644
--- a/app/assets/javascripts/admin.js.coffee
+++ b/app/assets/javascripts/admin.js.coffee
@@ -1,4 +1,4 @@
-class Admin
+class @Admin
constructor: ->
$('input#user_force_random_password').on 'change', (elem) ->
elems = $('#user_password, #user_password_confirmation')
@@ -51,5 +51,3 @@ class Admin
$('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
-
-@Admin = Admin
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index a3da875b90a..e9a28c12159 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -18,10 +18,12 @@
#= require jquery.turbolinks
#= require turbolinks
#= require bootstrap
+#= require password_strength
#= require select2
#= require raphael
#= require g.raphael-min
#= require g.bar-min
+#= require chart-lib.min
#= require branch-graph
#= require highlight.pack
#= require ace/ace
@@ -62,7 +64,7 @@ window.extractLast = (term) ->
return split( term ).pop()
window.rstrip = (val) ->
- return val.replace(/\s+$/, '')
+ return if val then val.replace(/\s+$/, '') else val
# Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) ->
@@ -171,11 +173,18 @@ $ ->
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
$(@).find('i').
- toggleClass('icon-chevron-down').
- toggleClass('icon-chevron-up')
+ toggleClass('fa fa-chevron-down').
+ toggleClass('fa fa-chevron-up')
$(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault()
+ $(document).on "click", '.js-confirm-danger', (e) ->
+ e.preventDefault()
+ btn = $(e.target)
+ text = btn.data("confirm-danger-message")
+ form = btn.closest("form")
+ new ConfirmDangerModal(form, text)
+
(($) ->
# Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: ->
diff --git a/app/assets/javascripts/behaviors/taskable.js.coffee b/app/assets/javascripts/behaviors/taskable.js.coffee
new file mode 100644
index 00000000000..ddce71c1886
--- /dev/null
+++ b/app/assets/javascripts/behaviors/taskable.js.coffee
@@ -0,0 +1,21 @@
+window.updateTaskState = (taskableType) ->
+ objType = taskableType.data
+ isChecked = $(this).prop("checked")
+ if $(this).is(":checked")
+ stateEvent = "task_check"
+ else
+ stateEvent = "task_uncheck"
+
+ taskableUrl = $("form.edit-" + objType).first().attr("action")
+ taskableNum = taskableUrl.match(/\d+$/)
+ taskNum = 0
+ $("li.task-list-item input:checkbox").each( (index, e) =>
+ if e == this
+ taskNum = index + 1
+ )
+
+ $.ajax
+ type: "PATCH"
+ url: taskableUrl
+ data: objType + "[state_event]=" + stateEvent +
+ "&" + objType + "[task_num]=" + taskNum
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee
index 1b2ed9efc25..177b6918270 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.coffee
+++ b/app/assets/javascripts/behaviors/toggler_behavior.coffee
@@ -8,7 +8,7 @@ $ ->
#
$("body").on "click", ".js-toggle-button", (e) ->
$(@).find('i').
- toggleClass('icon-chevron-down').
- toggleClass('icon-chevron-up')
+ toggleClass('fa fa-chevron-down').
+ toggleClass('fa fa-chevron-up')
$(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
e.preventDefault()
diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee
index 9db919e5a62..a5f15f80c5c 100644
--- a/app/assets/javascripts/blob.js.coffee
+++ b/app/assets/javascripts/blob.js.coffee
@@ -1,4 +1,4 @@
-class BlobView
+class @BlobView
constructor: ->
# handle multi-line select
handleMultiSelect = (e) ->
@@ -71,6 +71,3 @@ class BlobView
# Highlight the correct lines when the hash part of the URL changes
$(window).on("hashchange", highlightBlobLines)
-
-
-@BlobView = BlobView
diff --git a/app/assets/javascripts/chart.js.coffee b/app/assets/javascripts/chart.js.coffee
deleted file mode 100644
index 989f48e5e75..00000000000
--- a/app/assets/javascripts/chart.js.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-@Chart =
- labels: []
- values: []
-
- init: (labels, values, title) ->
- r = Raphael('activity-chart')
-
- fin = ->
- @flag = r.popup(@bar.x, @bar.y, @bar.value or "0").insertBefore(this)
-
- fout = ->
- @flag.animate
- opacity: 0, 300, -> @remove()
-
- r.text(160, 10, title).attr font: "13px sans-serif"
- r.barchart(
- 10, 20, 560, 200,
- [values],
- {colors:["#456"]}
- ).label(labels, true)
- .hover(fin, fout)
diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee
index 5f53439ca4b..0566e239191 100644
--- a/app/assets/javascripts/commit.js.coffee
+++ b/app/assets/javascripts/commit.js.coffee
@@ -1,6 +1,4 @@
-class Commit
+class @Commit
constructor: ->
$('.files .diff-file').each ->
new CommitFile(this)
-
-@Commit = Commit
diff --git a/app/assets/javascripts/commit/file.js.coffee b/app/assets/javascripts/commit/file.js.coffee
index 4db9116a9de..83e793863b6 100644
--- a/app/assets/javascripts/commit/file.js.coffee
+++ b/app/assets/javascripts/commit/file.js.coffee
@@ -1,7 +1,5 @@
-class CommitFile
+class @CommitFile
constructor: (file) ->
if $('.image', file).length
new ImageFile(file)
-
-@CommitFile = CommitFile
diff --git a/app/assets/javascripts/commit/image-file.js.coffee b/app/assets/javascripts/commit/image-file.js.coffee
index 607b85eb45c..9e5f49b1f69 100644
--- a/app/assets/javascripts/commit/image-file.js.coffee
+++ b/app/assets/javascripts/commit/image-file.js.coffee
@@ -1,4 +1,4 @@
-class ImageFile
+class @ImageFile
# Width where images must fits in, for 2-up this gets divided by 2
@availWidth = 900
@@ -124,5 +124,3 @@ class ImageFile
else
img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
-
-@ImageFile = ImageFile
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index 784d7d20bb1..c183e78e513 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -1,4 +1,4 @@
-class CommitsList
+class @CommitsList
@data =
ref: null
limit: 0
@@ -53,5 +53,3 @@ class CommitsList
@disable
callback: =>
this.getOld()
-
-this.CommitsList = CommitsList
diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee
new file mode 100644
index 00000000000..bb99edbd09e
--- /dev/null
+++ b/app/assets/javascripts/confirm_danger_modal.js.coffee
@@ -0,0 +1,18 @@
+class @ConfirmDangerModal
+ constructor: (form, text) ->
+ @form = form
+ $('.js-confirm-text').text(text || '')
+ $('.js-confirm-danger-input').val('')
+ $('#modal-confirm-danger').modal('show')
+ project_path = $('.js-confirm-danger-match').text()
+ submit = $('.js-confirm-danger-submit')
+ submit.disable()
+
+ $('.js-confirm-danger-input').on 'input', ->
+ if rstrip($(@).val()) is project_path
+ submit.enable()
+ else
+ submit.disable()
+
+ $('.js-confirm-danger-submit').on 'click', =>
+ @form.submit()
diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee
index c4a0ccd9c2a..6ef5a539b8f 100644
--- a/app/assets/javascripts/dashboard.js.coffee
+++ b/app/assets/javascripts/dashboard.js.coffee
@@ -1,4 +1,4 @@
-class Dashboard
+class @Dashboard
constructor: ->
@initSidebarTab()
@@ -28,6 +28,3 @@ class Dashboard
# show tab from cookie
sidebar_filter = $.cookie(key)
$("#" + sidebar_filter).tab('show') if sidebar_filter
-
-
-@Dashboard = Dashboard
diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee
index dbe00c487dc..52b4208524f 100644
--- a/app/assets/javascripts/diff.js.coffee
+++ b/app/assets/javascripts/diff.js.coffee
@@ -1,4 +1,4 @@
-class Diff
+class @Diff
UNFOLD_COUNT = 20
constructor: ->
$(document).on('click', '.js-unfold', (event) =>
@@ -41,6 +41,3 @@ class Diff
lines = line.children().slice(0, 2)
line_numbers = ($(l).attr('data-linenumber') for l in lines)
(parseInt(line_number) for line_number in line_numbers)
-
-
-@Diff = Diff
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 086c09f196e..fb1adbc4b3d 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -27,6 +27,8 @@ class Dispatcher
new ZenMode()
when 'projects:milestones:show'
new Milestone()
+ when 'projects:milestones:new'
+ new ZenMode()
when 'projects:issues:new','projects:issues:edit'
GitLab.GfmAutoComplete.setup()
shortcut_handler = new ShortcutsNavigation()
@@ -56,15 +58,11 @@ class Dispatcher
when 'groups:show', 'projects:show'
new Activities()
shortcut_handler = new ShortcutsNavigation()
- when 'projects:new'
- new Project()
- when 'projects:edit'
- new Project()
- shortcut_handler = new ShortcutsNavigation()
- when 'projects:teams:members:index'
- new TeamMembers()
when 'groups:members'
new GroupMembers()
+ new UsersSelect()
+ when 'groups:new', 'groups:edit', 'admin:groups:edit'
+ new GroupAvatar()
when 'projects:tree:show'
new TreeView()
shortcut_handler = new ShortcutsNavigation()
@@ -77,20 +75,42 @@ class Dispatcher
# Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created.
shortcut_handler = true
+ when 'users:show'
+ new User()
switch path.first()
- when 'admin' then new Admin()
+ when 'admin'
+ new Admin()
+ switch path[1]
+ when 'groups'
+ new UsersSelect()
+ when 'projects'
+ new NamespaceSelect()
when 'dashboard'
shortcut_handler = new ShortcutsDashboardNavigation()
+ when 'profiles'
+ new Profile()
when 'projects'
+ new Project()
switch path[1]
+ when 'edit'
+ shortcut_handler = new ShortcutsNavigation()
+ new ProjectNew()
+ when 'new'
+ new ProjectNew()
+ when 'show'
+ new ProjectShow()
+ when 'issues', 'merge_requests'
+ new ProjectUsersSelect()
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
+ new ZenMode()
when 'snippets', 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation()
when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
+ new UsersSelect()
# If we haven't installed a custom shortcut handler, install the default one
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index cf1a37eae3e..b39ab0c4475 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,4 +1,4 @@
-class Flash
+class @Flash
constructor: (message, type)->
flash = $(".flash-container")
flash.html("")
@@ -10,5 +10,3 @@ class Flash
flash.click -> $(@).fadeOut()
flash.show()
-
-@Flash = Flash
diff --git a/app/assets/javascripts/group_avatar.js.coffee b/app/assets/javascripts/group_avatar.js.coffee
new file mode 100644
index 00000000000..0825fd3ce52
--- /dev/null
+++ b/app/assets/javascripts/group_avatar.js.coffee
@@ -0,0 +1,9 @@
+class @GroupAvatar
+ constructor: ->
+ $('.js-choose-group-avatar-button').bind "click", ->
+ form = $(this).closest("form")
+ form.find(".js-group-avatar-input").click()
+ $('.js-group-avatar-input').bind "change", ->
+ form = $(this).closest("form")
+ filename = $(this).val().replace(/^.*[\\\/]/, '')
+ form.find(".js-avatar-filename").text(filename)
diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee
index 4b1000f9a6a..cc905e91ea2 100644
--- a/app/assets/javascripts/groups.js.coffee
+++ b/app/assets/javascripts/groups.js.coffee
@@ -1,17 +1,4 @@
-class GroupMembers
+class @GroupMembers
constructor: ->
$('li.group_member').bind 'ajax:success', ->
$(this).fadeOut()
-
-@GroupMembers = GroupMembers
-
-$ ->
- # avatar
- $('.js-choose-group-avatar-button').bind "click", ->
- form = $(this).closest("form")
- form.find(".js-group-avatar-input").click()
-
- $('.js-group-avatar-input').bind "change", ->
- form = $(this).closest("form")
- filename = $(this).val().replace(/^.*[\\\/]/, '')
- form.find(".js-avatar-filename").text(filename)
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 36935a0a159..597b4695a6d 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -1,4 +1,4 @@
-class Issue
+class @Issue
constructor: ->
$('.edit-issue.inline-update input[type="submit"]').hide()
$(".issue-box .inline-update").on "change", "select", ->
@@ -6,4 +6,12 @@ class Issue
$(".issue-box .inline-update").on "change", "#issue_assignee_id", ->
$(this).submit()
-@Issue = Issue
+ if $("a.btn-close").length
+ $("li.task-list-item input:checkbox").prop("disabled", false)
+
+ $(".task-list-item input:checkbox").on(
+ "click"
+ null
+ "issue"
+ updateTaskState
+ )
diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee
index d306ad64f5b..1bc8840f9ac 100644
--- a/app/assets/javascripts/labels.js.coffee
+++ b/app/assets/javascripts/labels.js.coffee
@@ -1,4 +1,4 @@
-class Labels
+class @Labels
constructor: ->
form = $('.label-form')
@setupLabelForm(form)
@@ -31,5 +31,3 @@ class Labels
# Notify the form, that color has changed
$('.label-form').trigger('keyup')
e.preventDefault()
-
-@Labels = Labels
diff --git a/app/assets/javascripts/markdown_area.js.coffee b/app/assets/javascripts/markdown_area.js.coffee
index a971e5dbf1d..a0ebfc98ce6 100644
--- a/app/assets/javascripts/markdown_area.js.coffee
+++ b/app/assets/javascripts/markdown_area.js.coffee
@@ -7,18 +7,18 @@ $(document).ready ->
divHover = "<div class=\"div-dropzone-hover\"></div>"
divSpinner = "<div class=\"div-dropzone-spinner\"></div>"
divAlert = "<div class=\"" + alertClass + "\"></div>"
- iconPicture = "<i class=\"icon-picture div-dropzone-icon\"></i>"
- iconSpinner = "<i class=\"icon-spinner icon-spin div-dropzone-icon\"></i>"
+ iconPicture = "<i class=\"fa fa-picture-o div-dropzone-icon\"></i>"
+ iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_image_path_upload = window.project_image_path_upload or null
- $("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>"
-
+ $("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>"
+
$(".div-dropzone").parent().addClass "div-dropzone-wrapper"
$(".div-dropzone").append divHover
$(".div-dropzone-hover").append iconPicture
- $(".div-dropzone").append divSpinner
+ $(".div-dropzone").append divSpinner
$(".div-dropzone-spinner").append iconSpinner
$(".div-dropzone-spinner").css
"opacity": 0
@@ -27,12 +27,12 @@ $(document).ready ->
dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload
dictDefaultMessage: ""
- clickable: false
+ clickable: true
paramName: "markdown_img"
maxFilesize: 10
uploadMultiple: false
acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png"
- headers:
+ headers:
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
previewContainer: false
@@ -91,7 +91,7 @@ $(document).ready ->
handlePaste = (e) ->
e.preventDefault()
my_event = e.originalEvent
-
+
if my_event.clipboardData and my_event.clipboardData.items
processItem(my_event)
@@ -115,7 +115,7 @@ $(document).ready ->
return item
i++
return false
-
+
pasteText = (text) ->
caretStart = $(child)[0].selectionStart
caretEnd = $(child)[0].selectionEnd
@@ -126,12 +126,12 @@ $(document).ready ->
$(child).val beforeSelection + text + afterSelection
$(".markdown-area").trigger "input"
- getFilename = (e) ->
+ getFilename = (e) ->
if window.clipboardData and window.clipboardData.getData
value = window.clipboardData.getData("Text")
else if e.clipboardData and e.clipboardData.getData
value = e.clipboardData.getData("text/plain")
-
+
value = value.split("\r")
value.first()
@@ -154,7 +154,7 @@ $(document).ready ->
success: (e, textStatus, response) ->
insertToTextArea(filename, formatLink(response.responseJSON.link))
-
+
error: (response) ->
showError(response.responseJSON.message)
@@ -190,7 +190,7 @@ $(document).ready ->
$(".markdown-selector").click (e) ->
e.preventDefault()
- $(@).closest(".div-dropzone-wrapper").find(".div-dropzone").click()
+ $(@).closest('.gfm-form').find('.div-dropzone').click()
return
return
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 59e53b69e3d..46e06424e5a 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -1,4 +1,4 @@
-class MergeRequest
+class @MergeRequest
constructor: (@opts) ->
@initContextWidget()
this.$el = $('.merge-request')
@@ -15,8 +15,10 @@ class MergeRequest
modal = $('#modal_merge_info').modal(show: false)
- disableButtonIfEmptyField '#merge_commit_message', '.accept_merge_request'
+ disableButtonIfEmptyField '#commit_message', '.accept_merge_request'
+ if $("a.btn-close").length
+ $("li.task-list-item input:checkbox").prop("disabled", false)
# Local jQuery finder
$: (selector) ->
@@ -40,6 +42,8 @@ class MergeRequest
if @opts.ci_enable
$.get @opts.url_to_ci_check, (data) =>
this.showCiState data.status
+ if data.coverage
+ this.showCiCoverage data.coverage
, 'json'
bindEvents: ->
@@ -70,6 +74,13 @@ class MergeRequest
this.$('.remove_source_branch_in_progress').hide()
this.$('.remove_source_branch_widget.failed').show()
+ $(".task-list-item input:checkbox").on(
+ "click"
+ null
+ "merge_request"
+ updateTaskState
+ )
+
activateTab: (action) ->
this.$('.merge-request-tabs li').removeClass 'active'
this.$('.tab-content').hide()
@@ -94,15 +105,11 @@ class MergeRequest
else
$('.ci_widget.ci-error').show()
- switch state
- when "success"
- $('.mr-state-widget').addClass("panel-success")
- when "failed"
- $('.mr-state-widget').addClass("panel-danger")
- when "running", "pending"
- $('.mr-state-widget').addClass("panel-warning")
-
-
+ showCiCoverage: (coverage) ->
+ cov_html = $('<span>')
+ cov_html.addClass('ci-coverage')
+ cov_html.text('Coverage ' + coverage + '%')
+ $('.ci_widget:visible').append(cov_html)
loadDiff: (event) ->
$.ajax
@@ -125,5 +132,3 @@ class MergeRequest
this.$('.automerge_widget').hide()
this.$('.merge-in-progress').hide()
this.$('.automerge_widget.already_cannot_be_merged').show()
-
-this.MergeRequest = MergeRequest
diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee
index ea01c318d4f..c42f31933d3 100644
--- a/app/assets/javascripts/milestone.js.coffee
+++ b/app/assets/javascripts/milestone.js.coffee
@@ -1,4 +1,4 @@
-class Milestone
+class @Milestone
@updateIssue: (li, issue_url, data) ->
$.ajax
type: "PUT"
@@ -115,5 +115,3 @@ class Milestone
Milestone.updateMergeRequest(ui.item, merge_request_url, data)
).disableSelection()
-
-@Milestone = Milestone
diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee
index 00d135d1449..a02c4515ccc 100644
--- a/app/assets/javascripts/namespace_select.js.coffee
+++ b/app/assets/javascripts/namespace_select.js.coffee
@@ -1,24 +1,25 @@
-$ ->
- namespaceFormatResult = (namespace) ->
- markup = "<div class='namespace-result'>"
- markup += "<span class='namespace-kind'>" + namespace.kind + "</span>"
- markup += "<span class='namespace-path'>" + namespace.path + "</span>"
- markup += "</div>"
- markup
+class @NamespaceSelect
+ constructor: ->
+ namespaceFormatResult = (namespace) ->
+ markup = "<div class='namespace-result'>"
+ markup += "<span class='namespace-kind'>" + namespace.kind + "</span>"
+ markup += "<span class='namespace-path'>" + namespace.path + "</span>"
+ markup += "</div>"
+ markup
- formatSelection = (namespace) ->
- namespace.kind + ": " + namespace.path
+ formatSelection = (namespace) ->
+ namespace.kind + ": " + namespace.path
- $('.ajax-namespace-select').each (i, select) ->
- $(select).select2
- placeholder: "Search for namespace"
- multiple: $(select).hasClass('multiselect')
- minimumInputLength: 0
- query: (query) ->
- Api.namespaces query.term, (namespaces) ->
- data = { results: namespaces }
- query.callback(data)
+ $('.ajax-namespace-select').each (i, select) ->
+ $(select).select2
+ placeholder: "Search for namespace"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.namespaces query.term, (namespaces) ->
+ data = { results: namespaces }
+ query.callback(data)
- dropdownCssClass: "ajax-namespace-dropdown"
- formatResult: namespaceFormatResult
- formatSelection: formatSelection
+ dropdownCssClass: "ajax-namespace-dropdown"
+ formatResult: namespaceFormatResult
+ formatSelection: formatSelection
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 597d6d26b69..978f83dd442 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -1,4 +1,4 @@
-class Notes
+class @Notes
@interval: null
constructor: (notes_url, note_ids, last_fetched_at) ->
@@ -6,6 +6,7 @@ class Notes
@notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root?
@note_ids = note_ids
@last_fetched_at = last_fetched_at
+ @noteable_url = document.URL
@initRefresh()
@setupMainTargetNoteForm()
@cleanBinding()
@@ -95,7 +96,8 @@ class Notes
, 15000
refresh: ->
- @getContent() unless document.hidden
+ unless document.hidden or (@noteable_url != document.URL)
+ @getContent()
getContent: ->
$.ajax
@@ -512,7 +514,3 @@ class Notes
else
form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close')
-
-
-
-@Notes = Notes
diff --git a/app/assets/javascripts/notes_votes.js.coffee b/app/assets/javascripts/notes_votes.js.coffee
index b31eb9ac9de..65c149b7886 100644
--- a/app/assets/javascripts/notes_votes.js.coffee
+++ b/app/assets/javascripts/notes_votes.js.coffee
@@ -1,4 +1,4 @@
-class NotesVotes
+class @NotesVotes
updateVotes: ->
votes = $("#votes .votes")
notes = $("#notes-list .note .vote")
@@ -18,5 +18,3 @@ class NotesVotes
# replace vote numbers
votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes)
votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes)
-
-@NotesVotes = NotesVotes
diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee
new file mode 100644
index 00000000000..825f5630266
--- /dev/null
+++ b/app/assets/javascripts/password_strength.js.coffee
@@ -0,0 +1,31 @@
+#= require pwstrength-bootstrap-1.2.2
+overwritten_messages =
+ wordSimilarToUsername: "Your password should not contain your username"
+
+overwritten_rules =
+ wordSequences: false
+
+options =
+ showProgressBar: false
+ showVerdicts: false
+ showPopover: true
+ showErrors: true
+ showStatus: true
+ errorMessages: overwritten_messages
+
+$(document).ready ->
+ profileOptions = {}
+ profileOptions.ui = options
+ profileOptions.rules =
+ activated: overwritten_rules
+
+ deviseOptions = {}
+ deviseOptions.common =
+ usernameField: "#user_username"
+ deviseOptions.ui = options
+ deviseOptions.rules =
+ activated: overwritten_rules
+
+ $("#user_password_profile").pwstrength profileOptions
+ $("#user_password_sign_up").pwstrength deviseOptions
+ $("#user_password_recover").pwstrength deviseOptions
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index 0e99921f899..de356fbec77 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -1,30 +1,29 @@
-$ ->
- $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
- # Submit the form
- $('.edit_user').submit()
+class @Profile
+ constructor: ->
+ $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
+ # Submit the form
+ $('.edit_user').submit()
- new Flash("Appearance settings saved", "notice")
+ new Flash("Appearance settings saved", "notice")
- $('.update-username form').on 'ajax:before', ->
- $('.loading-gif').show()
- $(this).find('.update-success').hide()
- $(this).find('.update-failed').hide()
+ $('.update-username form').on 'ajax:before', ->
+ $('.loading-gif').show()
+ $(this).find('.update-success').hide()
+ $(this).find('.update-failed').hide()
- $('.update-username form').on 'ajax:complete', ->
- $(this).find('.btn-save').enableButton()
- $(this).find('.loading-gif').hide()
+ $('.update-username form').on 'ajax:complete', ->
+ $(this).find('.btn-save').enableButton()
+ $(this).find('.loading-gif').hide()
- $('.update-notifications').on 'ajax:complete', ->
- $(this).find('.btn-save').enableButton()
+ $('.update-notifications').on 'ajax:complete', ->
+ $(this).find('.btn-save').enableButton()
- $('.js-choose-user-avatar-button').bind "click", ->
- form = $(this).closest("form")
- form.find(".js-user-avatar-input").click()
+ $('.js-choose-user-avatar-button').bind "click", ->
+ form = $(this).closest("form")
+ form.find(".js-user-avatar-input").click()
- $('.js-user-avatar-input').bind "change", ->
- form = $(this).closest("form")
- filename = $(this).val().replace(/^.*[\\\/]/, '')
- form.find(".js-avatar-filename").text(filename)
-
- $('.profile-groups-avatars').tooltip("placement": "top")
+ $('.js-user-avatar-input').bind "change", ->
+ form = $(this).closest("form")
+ filename = $(this).val().replace(/^.*[\\\/]/, '')
+ form.find(".js-avatar-filename").text(filename)
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index d81cc087df9..5a9cc66c8f0 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -1,53 +1,20 @@
-class Project
+class @Project
constructor: ->
- $('.project-edit-container').on 'ajax:before', =>
- $('.project-edit-container').hide()
- $('.save-project-loader').show()
-
- @initEvents()
-
-
- initEvents: ->
- disableButtonIfEmptyField '#project_name', '.project-submit'
-
- $('#project_issues_enabled').change ->
- if ($(this).is(':checked') == true)
- $('#project_issues_tracker').removeAttr('disabled')
- else
- $('#project_issues_tracker').attr('disabled', 'disabled')
-
- $('#project_issues_tracker').change()
-
- $('#project_issues_tracker').change ->
- if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
- $('#project_issues_tracker_id').attr('disabled', 'disabled')
- else
- $('#project_issues_tracker_id').removeAttr('disabled')
-
-
-@Project = Project
-
-$ ->
- # Git clone panel switcher
- scope = $ '.git-clone-holder'
- if scope.length > 0
- $('a, button', scope).click ->
- $('a, button', scope).removeClass 'active'
- $(@).addClass 'active'
- $('#project_clone', scope).val $(@).data 'clone'
- $(".clone").text("").append $(@).data 'clone'
-
- # Ref switcher
- $('.project-refs-select').on 'change', ->
- $(@).parents('form').submit()
-
- $('.hide-no-ssh-message').on 'click', (e) ->
- path = '/'
- $.cookie('hide_no_ssh_message', 'false', { path: path })
- $(@).parents('.no-ssh-key-message').hide()
- e.preventDefault()
-
- $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
- $(@).toggleClass('on').find('.count').html(data.star_count)
- .on 'ajax:error', (e, xhr, status, error) ->
- new Flash('Star toggle failed. Try again later.', 'alert')
+ # Git clone panel switcher
+ scope = $ '.git-clone-holder'
+ if scope.length > 0
+ $('a, button', scope).click ->
+ $('a, button', scope).removeClass 'active'
+ $(@).addClass 'active'
+ $('#project_clone', scope).val $(@).data 'clone'
+ $(".clone").text("").append $(@).data 'clone'
+
+ # Ref switcher
+ $('.project-refs-select').on 'change', ->
+ $(@).parents('form').submit()
+
+ $('.hide-no-ssh-message').on 'click', (e) ->
+ path = '/'
+ $.cookie('hide_no_ssh_message', 'false', { path: path })
+ $(@).parents('.no-ssh-key-message').hide()
+ e.preventDefault()
diff --git a/app/assets/javascripts/project_import.js.coffee b/app/assets/javascripts/project_import.js.coffee
index 7cf44da99fe..6633564a079 100644
--- a/app/assets/javascripts/project_import.js.coffee
+++ b/app/assets/javascripts/project_import.js.coffee
@@ -1,7 +1,5 @@
-class ProjectImport
+class @ProjectImport
constructor: ->
setTimeout ->
Turbolinks.visit(location.href)
, 5000
-
-@ProjectImport = ProjectImport
diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee
new file mode 100644
index 00000000000..f4a2ca813d2
--- /dev/null
+++ b/app/assets/javascripts/project_new.js.coffee
@@ -0,0 +1,25 @@
+class @ProjectNew
+ constructor: ->
+ $('.project-edit-container').on 'ajax:before', =>
+ $('.project-edit-container').hide()
+ $('.save-project-loader').show()
+
+ @initEvents()
+
+
+ initEvents: ->
+ disableButtonIfEmptyField '#project_name', '.project-submit'
+
+ $('#project_issues_enabled').change ->
+ if ($(this).is(':checked') == true)
+ $('#project_issues_tracker').removeAttr('disabled')
+ else
+ $('#project_issues_tracker').attr('disabled', 'disabled')
+
+ $('#project_issues_tracker').change()
+
+ $('#project_issues_tracker').change ->
+ if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
+ $('#project_issues_tracker_id').attr('disabled', 'disabled')
+ else
+ $('#project_issues_tracker_id').removeAttr('disabled')
diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee
new file mode 100644
index 00000000000..02a7d7b731d
--- /dev/null
+++ b/app/assets/javascripts/project_show.js.coffee
@@ -0,0 +1,15 @@
+class @ProjectShow
+ constructor: ->
+ $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
+ $(@).toggleClass('on').find('.count').html(data.star_count)
+ .on 'ajax:error', (e, xhr, status, error) ->
+ new Flash('Star toggle failed. Try again later.', 'alert')
+
+ $("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
+ $.cookie "default_view", $(e.target).attr("href")
+
+ defaultView = $.cookie("default_view")
+ if defaultView
+ $("a[href=" + defaultView + "]").tab "show"
+ else
+ $("a[data-toggle='tab']:first").tab "show"
diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee
index cfbcd5108c8..7fb33926096 100644
--- a/app/assets/javascripts/project_users_select.js.coffee
+++ b/app/assets/javascripts/project_users_select.js.coffee
@@ -1,6 +1,6 @@
-@projectUsersSelect =
- init: ->
- $('.ajax-project-users-select').each (i, select) ->
+class @ProjectUsersSelect
+ constructor: ->
+ $('.ajax-project-users-select').each (i, select) =>
project_id = $(select).data('project-id') || $('body').data('project-id')
$(select).select2
@@ -28,14 +28,16 @@
Api.user(id, callback)
- formatResult: projectUsersSelect.projectUserFormatResult
- formatSelection: projectUsersSelect.projectUserFormatSelection
+ formatResult: (args...) =>
+ @formatResult(args...)
+ formatSelection: (args...) =>
+ @formatSelection(args...)
dropdownCssClass: "ajax-project-users-dropdown"
dropdownAutoWidth: true
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m
- projectUserFormatResult: (user) ->
+ formatResult: (user) ->
if user.avatar_url
avatar = user.avatar_url
else
@@ -52,8 +54,5 @@
<div class='user-username'>#{user.username}</div>
</div>"
- projectUserFormatSelection: (user) ->
+ formatSelection: (user) ->
user.name
-
-$ ->
- projectUsersSelect.init()
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
index e144dfa1d68..c1801365266 100644
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -1,4 +1,4 @@
-class SearchAutocomplete
+class @SearchAutocomplete
constructor: (search_autocomplete_path, project_id, project_ref) ->
project_id = '' unless project_id
project_ref = '' unless project_ref
@@ -9,5 +9,3 @@ class SearchAutocomplete
minLength: 1
select: (event, ui) ->
location.href = ui.item.url
-
-@SearchAutocomplete = SearchAutocomplete
diff --git a/app/assets/javascripts/stat_graph.js.coffee b/app/assets/javascripts/stat_graph.js.coffee
index b129619696f..f36c71fd25e 100644
--- a/app/assets/javascripts/stat_graph.js.coffee
+++ b/app/assets/javascripts/stat_graph.js.coffee
@@ -1,4 +1,4 @@
-class window.StatGraph
+class @StatGraph
@log: {}
@get_log: ->
@log
diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee
index 168b7337041..27f0fd31d50 100644
--- a/app/assets/javascripts/stat_graph_contributors.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors.js.coffee
@@ -1,4 +1,4 @@
-class window.ContributorsStatGraph
+class @ContributorsStatGraph
init: (log) ->
@parsed_log = ContributorsStatGraphUtil.parse_log(log)
@set_current_field("commits")
@@ -24,22 +24,7 @@ class window.ContributorsStatGraph
class: 'graph-author-commits-count'
})
commits.text(author.commits + " commits")
-
- additions = $('<span/>', {
- class: 'graph-additions'
- })
- additions.text(author.additions + " ++")
-
- deletions = $('<span/>', {
- class: 'graph-deletions'
- })
- deletions.text(author.deletions + " --")
-
$('<span/>').append(commits)
- .append(" / ")
- .append(additions)
- .append(" / ")
- .append(deletions)
create_author_header: (author) ->
list_item = $('<li/>', {
diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
index 834c7e5dab0..9952fa0b00a 100644
--- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
@@ -1,4 +1,4 @@
-class window.ContributorsGraph
+class @ContributorsGraph
MARGIN:
top: 20
right: 20
@@ -44,7 +44,7 @@ class window.ContributorsGraph
set_data: (data) ->
@data = data
-class window.ContributorsMasterGraph extends ContributorsGraph
+class @ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.container').width() - 70
@height = 200
@@ -117,7 +117,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph
@svg.select("path").attr("d", @area)
@svg.select(".y.axis").call(@y_axis)
-class window.ContributorsAuthorGraph extends ContributorsGraph
+class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.container').width()/2 - 100
@height = 200
diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
index 364cab18377..1670f5c7bc1 100644
--- a/app/assets/javascripts/stat_graph_contributors_util.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
@@ -90,4 +90,4 @@ window.ContributorsStatGraphUtil =
true
else
false
-
+
diff --git a/app/assets/javascripts/team_members.js.coffee b/app/assets/javascripts/team_members.js.coffee
deleted file mode 100644
index 5eaa8ad4ff9..00000000000
--- a/app/assets/javascripts/team_members.js.coffee
+++ /dev/null
@@ -1,6 +0,0 @@
-class TeamMembers
- constructor: ->
- $('.team-members .project-access-select').on "change", ->
- $(this.form).submit()
-
-@TeamMembers = TeamMembers
diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee
index 4852e879b68..d428db5b422 100644
--- a/app/assets/javascripts/tree.js.coffee
+++ b/app/assets/javascripts/tree.js.coffee
@@ -1,4 +1,4 @@
-class TreeView
+class @TreeView
constructor: ->
@initKeyNav()
@@ -39,5 +39,3 @@ class TreeView
else if e.which is 13
path = $('.tree-item.selected .tree-item-file-name a').attr('href')
Turbolinks.visit(path)
-
-@TreeView = TreeView
diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee
new file mode 100644
index 00000000000..8a2e2421c2e
--- /dev/null
+++ b/app/assets/javascripts/user.js.coffee
@@ -0,0 +1,3 @@
+class @User
+ constructor: ->
+ $('.profile-groups-avatars').tooltip("placement": "top")
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 86318bd7d94..9eee7406511 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -1,5 +1,30 @@
-$ ->
- userFormatResult = (user) ->
+class @UsersSelect
+ constructor: ->
+ $('.ajax-users-select').each (i, select) =>
+ $(select).select2
+ placeholder: "Search for a user"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.users query.term, (users) ->
+ data = { results: users }
+ query.callback(data)
+
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id isnt ""
+ Api.user(id, callback)
+
+
+ formatResult: (args...) =>
+ @formatResult(args...)
+ formatSelection: (args...) =>
+ @formatSelection(args...)
+ dropdownCssClass: "ajax-users-dropdown"
+ escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
+ m
+
+ formatResult: (user) ->
if user.avatar_url
avatar = user.avatar_url
else
@@ -11,27 +36,5 @@ $ ->
<div class='user-username'>#{user.username}</div>
</div>"
- userFormatSelection = (user) ->
+ formatSelection: (user) ->
user.name
-
- $('.ajax-users-select').each (i, select) ->
- $(select).select2
- placeholder: "Search for a user"
- multiple: $(select).hasClass('multiselect')
- minimumInputLength: 0
- query: (query) ->
- Api.users query.term, (users) ->
- data = { results: users }
- query.callback(data)
-
- initSelection: (element, callback) ->
- id = $(element).val()
- if id isnt ""
- Api.user(id, callback)
-
-
- formatResult: userFormatResult
- formatSelection: userFormatSelection
- dropdownCssClass: "ajax-users-dropdown"
- escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
- m
diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee
index 17e790e5b7c..66757565d3a 100644
--- a/app/assets/javascripts/wikis.js.coffee
+++ b/app/assets/javascripts/wikis.js.coffee
@@ -1,4 +1,4 @@
-class Wikis
+class @Wikis
constructor: ->
$('.build-new-wiki').bind "click", ->
field = $('#new_wiki_path')
@@ -7,6 +7,3 @@ class Wikis
if(slug.length > 0)
location.href = path + "/" + slug
-
-
-@Wikis = Wikis
diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee
index 21ac212df0e..0c9942a4014 100644
--- a/app/assets/javascripts/zen_mode.js.coffee
+++ b/app/assets/javascripts/zen_mode.js.coffee
@@ -32,6 +32,8 @@ class @ZenMode
@active_zen_area = @active_checkbox.parent().find('textarea')
@active_zen_area.focus()
window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id')
+ # Disable dropzone in ZEN mode
+ Dropzone.forElement('.div-dropzone').disable()
exitZenMode: =>
if @active_zen_area isnt null
@@ -41,6 +43,8 @@ class @ZenMode
@active_checkbox = null
window.location.hash = ''
window.scrollTo(window.pageXOffset, @scroll_position)
+ # Enable dropzone when leaving ZEN mode
+ Dropzone.forElement('.div-dropzone').enable()
checkboxFromLocationHash: (e) ->
id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, ''))
diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss
index be4c4d07f1c..469f4f296ae 100644
--- a/app/assets/stylesheets/behaviors.scss
+++ b/app/assets/stylesheets/behaviors.scss
@@ -1,12 +1,22 @@
// Details
//--------
-.js-details-container .content { display: none; }
-.js-details-container .content.hide { display: block; }
-.js-details-container.open .content { display: block; }
-.js-details-container.open .content.hide { display: none; }
+.js-details-container {
+ .content {
+ display: none;
+ &.hide { display: block; }
+ }
+ &.open .content {
+ display: block;
+ &.hide { display: none; }
+ }
+}
// Toggle between two states.
-.js-toggler-container .turn-on { display: block; }
-.js-toggler-container .turn-off { display: none; }
-.js-toggler-container.on .turn-on { display: none; }
-.js-toggler-container.on .turn-off { display: block; }
+.js-toggler-container {
+ .turn-on { display: block; }
+ .turn-off { display: none; }
+ &.on {
+ .turn-on { display: none; }
+ .turn-off { display: block; }
+ }
+}
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index 803219a2e86..cd2f4e45e3c 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -356,3 +356,6 @@ table {
font-size: 42px;
}
+.task-status {
+ margin-left: 10px;
+}
diff --git a/app/assets/stylesheets/generic/gfm.scss b/app/assets/stylesheets/generic/gfm.scss
new file mode 100644
index 00000000000..e257f053618
--- /dev/null
+++ b/app/assets/stylesheets/generic/gfm.scss
@@ -0,0 +1,20 @@
+/**
+ * Styles that apply to all GFM related forms.
+ */
+.issue-form, .merge-request-form, .wiki-form {
+ .description {
+ height: 20em;
+ }
+}
+
+.wiki-form {
+ .description {
+ height: 26em;
+ }
+}
+
+.milestone-form {
+ .description {
+ height: 14em;
+ }
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/generic/issuable.scss b/app/assets/stylesheets/generic/issuable.scss
deleted file mode 100644
index f456b3ace1a..00000000000
--- a/app/assets/stylesheets/generic/issuable.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * Styles that apply to both issues and merge requests.
- */
-
-.issue-form, .merge-request-form {
- .description {
- height: 20em;
- }
-}
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss
index 0679690c05f..94149594e24 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/generic/issue_box.scss
@@ -8,43 +8,50 @@
*/
.issue-box {
- color: #666;
+ color: #555;
margin:20px 0;
- background: #FFF;
- border: 1px solid #EEE;
- @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
+ background: $box_bg;
+ @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
&.issue-box-closed {
- border-color: $border_danger;
.state {
+ background-color: #F3CECE;
+ border-color: $border_danger;
+ }
+ .state-label {
background-color: $bg_danger;
color: #FFF;
- border-color: $border_danger;
}
}
&.issue-box-merged {
- border-color: $border_primary;
.state {
+ background-color: #B7CEE7;
+ border-color: $border_primary;
+ }
+ .state-label {
background-color: $bg_primary;
color: #FFF;
- border-color: $border_primary;
}
}
&.issue-box-open {
- border-color: $border_success;
.state {
- border-color: $border_success;
+ background-color: #D6F1D7;
+ border-color: $bg_success;
+ }
+ .state-label {
background-color: $bg_success;
color: #FFF;
}
}
&.issue-box-expired {
- border-color: #cea61b;
.state {
+ background-color: #EEE9B3;
border-color: #faebcc;
+ }
+ .state-label {
background: #cea61b;
color: #FFF;
}
@@ -55,8 +62,7 @@
}
.state {
- border-bottom: 1px solid #DDD;
- padding: 10px 15px;
+ background-color: #f9f9f9;
}
.title {
@@ -104,12 +110,13 @@
font-size: 14px;
float: left;
font-weight: bold;
+ padding: 10px 15px;
}
.creator {
float: right;
+ padding: 10px 15px;
a {
- color: #FFF;
text-decoration: underline;
}
}
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss
index d347ab2c2e4..2653bfbf831 100644
--- a/app/assets/stylesheets/generic/lists.scss
+++ b/app/assets/stylesheets/generic/lists.scss
@@ -122,3 +122,7 @@ ul.bordered-list {
}
}
}
+
+li.task-list-item {
+ list-style-type: none;
+}
diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss
index 34b0608646c..9c5e76ab8e2 100644
--- a/app/assets/stylesheets/gl_bootstrap.scss
+++ b/app/assets/stylesheets/gl_bootstrap.scss
@@ -124,7 +124,7 @@ $list-group-active-bg: $bg_primary;
color: #888;
text-shadow: 0 1px 1px #fff;
}
- i[class^="icon-"] {
+ i[class~="fa"] {
line-height: 14px;
}
}
@@ -233,8 +233,8 @@ $list-group-active-bg: $bg_primary;
}
.form-actions {
- margin-bottom: 0;
- background: #FFF;
+ margin: -15px;
+ margin-top: 18px;
}
}
@@ -262,53 +262,33 @@ $list-group-active-bg: $bg_primary;
}
.panel-danger {
- border-color: $border_danger;
+ @include panel-colored;
.panel-heading {
- color: #ffffff;
- background-color: $bg_danger;
+ color: $border_danger;
border-color: $border_danger;
- a {
- color: #FFF;
- text-decoration: underline;
- }
}
}
.panel-success {
- border-color: $border_success;
+ @include panel-colored;
.panel-heading {
- color: #ffffff;
- background-color: $bg_success;
+ color: $border_success;
border-color: $border_success;
- a {
- color: #FFF;
- text-decoration: underline;
- }
}
}
.panel-primary {
- border-color: $border_primary;
+ @include panel-colored;
.panel-heading {
- color: #ffffff;
- background-color: $bg_primary;
+ color: $border_primary;
border-color: $border_primary;
- a {
- color: #FFF;
- text-decoration: underline;
- }
}
}
.panel-warning {
- border-color: $border_warning;
+ @include panel-colored;
.panel-heading {
- color: #ffffff;
- background-color: $bg_warning;
+ color: $border_warning;
border-color: $border_warning;
- a {
- color: #FFF;
- text-decoration: underline;
- }
}
}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 815cf367ae8..8d5822937a0 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -186,3 +186,11 @@
}
}
}
+
+.readme-holder .wiki, .note-body, .wiki-holder {
+ .white {
+ .highlight, pre, .hljs {
+ background: #F9F9F9;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/main/fonts.scss b/app/assets/stylesheets/main/fonts.scss
index d90274a0db9..f945aaca848 100644
--- a/app/assets/stylesheets/main/fonts.scss
+++ b/app/assets/stylesheets/main/fonts.scss
@@ -1,3 +1,3 @@
/** Typo **/
-$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
+$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/main/mixins.scss
index 93faf5ced65..7f607fc4e8b 100644
--- a/app/assets/stylesheets/main/mixins.scss
+++ b/app/assets/stylesheets/main/mixins.scss
@@ -132,3 +132,14 @@
white-space: nowrap;
max-width: $max_width;
}
+
+@mixin panel-colored {
+ border: none;
+ background: $box_bg;
+ @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
+
+ .panel-heading {
+ font-weight: bold;
+ background-color: $box_bg;
+ }
+}
diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss
index 72d84226fe7..c71984a5665 100644
--- a/app/assets/stylesheets/main/variables.scss
+++ b/app/assets/stylesheets/main/variables.scss
@@ -3,6 +3,7 @@
*/
$style_color: #474D57;
$hover: #FFECDB;
+$box_bg: #F9F9F9;
/*
* Link colors
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss
index a7fa715d2e0..ebf8a6125c7 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/sections/issues.scss
@@ -75,7 +75,7 @@
}
.participants {
- margin-bottom: 10px;
+ margin-bottom: 20px;
}
.issues_bulk_update {
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss
index 3db6da2a9f9..ec844cc00b0 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/sections/merge_requests.scss
@@ -104,15 +104,69 @@
}
.mr-state-widget {
- .panel-body {
+ background: $box_bg;
+ margin-bottom: 20px;
+ @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
+
+ .ci_widget {
+ padding: 10px 15px;
+ font-size: 15px;
+ border-bottom: 1px solid #BBB;
+ color: #777;
+ background-color: #F5F5F5;
+
+ &.ci-success {
+ color: $bg_success;
+ border-color: $border_success;
+ background-color: #F1FAF1;
+ }
+
+ &.ci-pending {
+ color: #548;
+ border-color: #548;
+ background-color: #F4F1FA;
+ }
+
+ &.ci-running {
+ color: $bg_warning;
+ border-color: $border_warning;
+ background-color: #FAF5F1;
+ }
+
+ &.ci-failed {
+ color: $bg_danger;
+ border-color: $border_danger;
+ background-color: #FAF1F1;
+ }
+
+ &.ci-error {
+ color: $bg_danger;
+ border-color: $border_danger;
+ background-color: #FAF1F1;
+ }
+ }
+
+ .mr-widget-body {
+ padding: 10px 15px;
+
h4 {
- margin-top: 0px;
+ font-size: 20px;
+ font-weight: normal;
}
p:last-child {
margin-bottom: 0;
}
}
+
+ .mr-widget-footer {
+ padding: 10px 15px;
+ border-top: 1px solid #EEE;
+ }
+
+ .ci-coverage {
+ float: right;
+ }
}
.merge-request-show-labels .label {
diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss
index 778953984d6..31c0a0835db 100644
--- a/app/assets/stylesheets/sections/nav.scss
+++ b/app/assets/stylesheets/sections/nav.scss
@@ -8,8 +8,6 @@
ul {
padding: 0;
margin: auto;
- height: 40px;
- overflow: hidden;
.count {
font-weight: normal;
display: inline-block;
@@ -37,53 +35,28 @@
a {
color: $link_color;
font-weight: bold;
- &:after {
- content: '';
- display: block;
- position: relative;
- bottom: -1px;
- border-color: $link_color;
- border-style: solid;
- border-width: 2px;
- }
+ border-bottom: 3px solid $link_color;
}
}
&:hover {
a {
color: $link_hover_color;
- &:after {
- content: '';
- display: block;
- position: relative;
- bottom: -1px;
- border-color: $link_hover_color;
- border-style: solid;
- border-width: 2px;
- }
- }
- }
-
- &.home {
- a {
- i {
- font-size: 20px;
- position: relative;
- top: 4px;
- }
+ border-bottom: 3px solid $link_hover_color;
}
}
}
a {
display: block;
text-align: center;
- font-weight: 500;
- height: 38px;
- line-height: 34px;
+ font-weight: bold;
+ height: 42px;
+ line-height: 39px;
color: #777;
text-shadow: 0 1px 1px white;
text-decoration: none;
- padding-top: 2px;
+ overflow: hidden;
+ margin-bottom: -1px;
}
}
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index d1bdc9aa179..7eb42fddade 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/sections/notes.scss
@@ -119,8 +119,7 @@ ul.notes {
display: none;
float: right;
- [class^="icon-"],
- [class*="icon-"] {
+ [class~="fa"] {
font-size: 16px;
line-height: 16px;
vertical-align: middle;
diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss
index 086875582f3..b9f4e317e9c 100644
--- a/app/assets/stylesheets/sections/profile.scss
+++ b/app/assets/stylesheets/sections/profile.scss
@@ -111,3 +111,20 @@
height: 50px;
}
}
+
+//CSS for password-strength indicator
+#password-strength {
+ margin-bottom: 0;
+}
+
+.has-success input {
+ background-color: #D6F1D7 !important;
+}
+
+.has-error input {
+ background-color: #F3CECE !important;
+}
+
+.has-warning input {
+ background-color: #FFE9A4 !important;
+}
diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb
index 4c1d0df4110..338496013a0 100644
--- a/app/controllers/admin/background_jobs_controller.rb
+++ b/app/controllers/admin/background_jobs_controller.rb
@@ -1,6 +1,6 @@
class Admin::BackgroundJobsController < Admin::ApplicationController
def show
- ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
+ ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
@sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
end
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 2f0d344802f..7c2388e81be 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -31,17 +31,11 @@ class Admin::ProjectsController < Admin::ApplicationController
protected
def project
- id = params[:project_id] || params[:id]
-
- @project = Project.find_with_namespace(id)
+ @project = Project.find_with_namespace(params[:id])
@project || render_404
end
def group
- @group ||= project.group
- end
-
- def repository
- @repository ||= project.repository
+ @group ||= @project.group
end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index f63df27eebd..baad9095b70 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController
def index
@users = User.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present?
+ @users = @users.sort(@sort = params[:sort])
@users = @users.alphabetically.page(params[:page])
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5ffec7f75bf..f1e1bebe5ce 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -5,15 +5,13 @@ class ApplicationController < ActionController::Base
before_filter :authenticate_user!
before_filter :reject_blocked!
before_filter :check_password_expiration
- before_filter :add_abilities
before_filter :ldap_security_check
- before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers
before_filter :add_gon_variables
before_filter :configure_permitted_parameters, if: :devise_controller?
before_filter :require_email, unless: :devise_controller?
- protect_from_forgery
+ protect_from_forgery with: :exception
helper_method :abilities, :can?
@@ -62,7 +60,7 @@ class ApplicationController < ActionController::Base
end
end
- def after_sign_in_path_for resource
+ def after_sign_in_path_for(resource)
if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked?
sign_out resource
flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
@@ -73,7 +71,7 @@ class ApplicationController < ActionController::Base
end
def abilities
- @abilities ||= Six.new
+ Ability.abilities
end
def can?(object, action, subject)
@@ -81,28 +79,31 @@ class ApplicationController < ActionController::Base
end
def project
- id = params[:project_id] || params[:id]
-
- # Redirect from
- # localhost/group/project.git
- # to
- # localhost/group/project
- #
- if id =~ /\.git\Z/
- redirect_to request.original_url.gsub(/\.git\Z/, '') and return
- end
+ unless @project
+ id = params[:project_id] || params[:id]
+
+ # Redirect from
+ # localhost/group/project.git
+ # to
+ # localhost/group/project
+ #
+ if id =~ /\.git\Z/
+ redirect_to request.original_url.gsub(/\.git\Z/, '') and return
+ end
- @project = Project.find_with_namespace(id)
+ @project = Project.find_with_namespace(id)
- if @project and can?(current_user, :read_project, @project)
- @project
- elsif current_user.nil?
- @project = nil
- authenticate_user!
- else
- @project = nil
- render_404 and return
+ if @project and can?(current_user, :read_project, @project)
+ @project
+ elsif current_user.nil?
+ @project = nil
+ authenticate_user!
+ else
+ @project = nil
+ render_404 and return
+ end
end
+ @project
end
def repository
@@ -111,22 +112,10 @@ class ApplicationController < ActionController::Base
nil
end
- def add_abilities
- abilities << Ability
- end
-
def authorize_project!(action)
return access_denied! unless can?(current_user, action, project)
end
- def authorize_code_access!
- return access_denied! unless can?(current_user, :download_code, project)
- end
-
- def authorize_push!
- return access_denied! unless can?(current_user, :push_code, project)
- end
-
def authorize_labels!
# Labels should be accessible for issues and/or merge requests
authorize_read_issue! || authorize_read_merge_request!
@@ -170,9 +159,6 @@ class ApplicationController < ActionController::Base
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
- def dev_tools
- end
-
def default_headers
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
new file mode 100644
index 00000000000..bc98eab133c
--- /dev/null
+++ b/app/controllers/confirmations_controller.rb
@@ -0,0 +1,17 @@
+class ConfirmationsController < Devise::ConfirmationsController
+
+ protected
+
+ def after_confirmation_path_for(resource_name, resource)
+ if signed_in?(resource_name)
+ signed_in_root_path(resource)
+ else
+ sign_in(resource)
+ if signed_in?(resource_name)
+ signed_in_root_path(resource)
+ else
+ new_session_path(resource_name)
+ end
+ end
+ end
+end
diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb
index f8e1a31e0b3..ada7031fea4 100644
--- a/app/controllers/explore/groups_controller.rb
+++ b/app/controllers/explore/groups_controller.rb
@@ -1,7 +1,6 @@
class Explore::GroupsController < ApplicationController
skip_before_filter :authenticate_user!,
- :reject_blocked, :set_current_user_for_observers,
- :add_abilities
+ :reject_blocked, :set_current_user_for_observers
layout "explore"
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index b6fa8b7e387..d75fd8e72fa 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -1,7 +1,6 @@
class Explore::ProjectsController < ApplicationController
skip_before_filter :authenticate_user!,
- :reject_blocked,
- :add_abilities
+ :reject_blocked
layout 'explore'
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 63c05d4f33b..ca88d033878 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -19,6 +19,7 @@ class Groups::GroupMembersController < ApplicationController
def destroy
@users_group = @group.group_members.find(params[:id])
+
if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy
respond_to do |format|
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 3ed6a69c2d8..bd4b310fcbf 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
error.to_s.humanize if error
end
+ # We only find ourselves here
+ # if the authentication to LDAP was successful.
def ldap
- # We only find ourselves here
- # if the authentication to LDAP was successful.
- @user = Gitlab::LDAP::User.find_or_create(oauth)
- @user.remember_me = true if @user.persisted?
+ @user = Gitlab::LDAP::User.new(oauth)
+ @user.save if @user.changed? # will also save new users
+ gl_user = @user.gl_user
+ gl_user.remember_me = true if @user.persisted?
# Do additional LDAP checks for the user filter and EE features
- if Gitlab::LDAP::Access.allowed?(@user)
- sign_in_and_redirect(@user)
+ if @user.allowed?
+ sign_in_and_redirect(gl_user)
else
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
@@ -46,26 +48,28 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
current_user.save
redirect_to profile_path
else
- @user = Gitlab::OAuth::User.find(oauth)
+ @user = Gitlab::OAuth::User.new(oauth)
+ @user.save
- # Create user if does not exist
- # and allow_single_sign_on is true
- if Gitlab.config.omniauth['allow_single_sign_on'] && !@user
- @user, errors = Gitlab::OAuth::User.create(oauth)
- end
-
- if @user && !errors
- sign_in_and_redirect(@user)
+ # Only allow properly saved users to login.
+ if @user.persisted? && @user.valid?
+ sign_in_and_redirect(@user.gl_user)
else
- if errors
- error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ")
- redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
- else
- flash[:notice] = "There's no such user!"
- end
- redirect_to new_user_session_path
+ error_message =
+ if @user.gl_user.errors.any?
+ @user.gl_user.errors.map do |attribute, message|
+ "#{attribute} #{message}"
+ end.join(", ")
+ else
+ ''
+ end
+
+ redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
+ rescue StandardError
+ flash[:notice] = "There's no such user!"
+ redirect_to new_user_session_path
end
def oauth
diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb
index 5e305934433..a7b1b7b40e8 100644
--- a/app/controllers/projects/base_tree_controller.rb
+++ b/app/controllers/projects/base_tree_controller.rb
@@ -1,8 +1,7 @@
class Projects::BaseTreeController < Projects::ApplicationController
include ExtractsPath
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
end
diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb
index a3c41301676..367d1295f34 100644
--- a/app/controllers/projects/blame_controller.rb
+++ b/app/controllers/projects/blame_controller.rb
@@ -3,8 +3,7 @@ class Projects::BlameController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 7009e3b1bc8..2412800c493 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -3,10 +3,9 @@ class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
- before_filter :authorize_push!, only: [:destroy]
+ before_filter :authorize_push_code!, only: [:destroy]
before_filter :blob
@@ -20,7 +19,7 @@ class Projects::BlobController < Projects::ApplicationController
flash[:notice] = "Your changes have been successfully committed"
redirect_to project_tree_path(@project, @ref)
else
- flash[:alert] = result[:error]
+ flash[:alert] = result[:message]
render :show
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index faa0ce67ca8..9ebd498e7fa 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -1,10 +1,9 @@
class Projects::BranchesController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
before_filter :require_non_empty_project
- before_filter :authorize_code_access!
- before_filter :authorize_push!, only: [:create, :destroy]
+ before_filter :authorize_download_code!
+ before_filter :authorize_push_code!, only: [:create, :destroy]
def index
@sort = params[:sort] || 'name'
@@ -19,6 +18,7 @@ class Projects::BranchesController < Projects::ApplicationController
def create
result = CreateBranchService.new(project, current_user).
execute(params[:branch_name], params[:ref])
+
if result[:status] == :success
@branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name)
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 34bd682bd90..dac858d8e16 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -3,26 +3,19 @@
# Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
before_filter :commit
def show
return git_not_found! unless @commit
- @line_notes = project.notes.for_commit_id(commit.id).inline
-
- @branches = begin
- project.repository.branch_names_contains(commit.id)
- rescue Grit::Git::GitTimeout
- []
- end
-
+ @line_notes = @project.notes.for_commit_id(commit.id).inline
+ @branches = @project.repository.branch_names_contains(commit.id)
@diffs = @commit.diffs
- @note = project.build_commit_note(commit)
- @notes_count = project.notes.for_commit_id(commit.id).count
- @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
+ @note = @project.build_commit_note(commit)
+ @notes_count = @project.notes.for_commit_id(commit.id).count
+ @notes = @project.notes.for_commit_id(@commit.id).not_inline.fresh
@noteable = @commit
@comments_allowed = @reply_allowed = true
@comments_target = {
@@ -38,6 +31,6 @@ class Projects::CommitController < Projects::ApplicationController
end
def commit
- @commit ||= project.repository.commit(params[:id])
+ @commit ||= @project.repository.commit(params[:id])
end
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 038645aa497..9476b6c0284 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -4,8 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
@@ -17,7 +16,7 @@ class Projects::CommitsController < Projects::ApplicationController
group(:commit_id).count
respond_to do |format|
- format.html # index.html.erb
+ format.html
format.json { pager_json("projects/commits/_commits", @commits.size) }
format.atom { render layout: false }
end
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 7a671e8455d..ffb8c2e4af1 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -1,7 +1,6 @@
class Projects::CompareController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def index
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index d20937ea8ea..024b9520d30 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -42,7 +42,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def enable
- project.deploy_keys << available_keys.find(params[:id])
+ @project.deploy_keys << available_keys.find(params[:id])
redirect_to project_deploy_keys_path(@project)
end
diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb
index 8976d7c7be8..65661c80410 100644
--- a/app/controllers/projects/edit_tree_controller.rb
+++ b/app/controllers/projects/edit_tree_controller.rb
@@ -1,7 +1,7 @@
class Projects::EditTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :blob
- before_filter :authorize_push!
+ before_filter :authorize_push_code!
before_filter :from_merge_request
before_filter :after_edit_path
@@ -22,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
redirect_to after_edit_path
else
- flash[:alert] = result[:error]
+ flash[:alert] = result[:message]
render :show
end
end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 252d47d939e..4a318cb7d56 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -1,25 +1,39 @@
class Projects::GraphsController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
respond_to do |format|
format.html
- format.js do
+ format.json do
fetch_graph
end
end
end
+ def commits
+ @commits = @project.repository.commits(nil, nil, 2000, 0, true)
+ @commits_graph = Gitlab::Graphs::Commits.new(@commits)
+ @commits_per_week_days = @commits_graph.commits_per_week_days
+ @commits_per_time = @commits_graph.commits_per_time
+ @commits_per_month = @commits_graph.commits_per_month
+ end
+
private
def fetch_graph
- @log = @project.repository.graph_log.to_json
- @success = true
- rescue => ex
+ @commits = @project.repository.commits(nil, nil, 6000, 0, true)
@log = []
- @success = false
+
+ @commits.each do |commit|
+ @log << {
+ author_name: commit.author_name.force_encoding('UTF-8'),
+ author_email: commit.author_email.force_encoding('UTF-8'),
+ date: commit.committed_date.strftime("%Y-%m-%d")
+ }
+ end
+
+ render json: @log.to_json
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 9e7a55b23fd..c6d526f05c5 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -152,7 +152,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
:title, :assignee_id, :position, :description,
- :milestone_id, :state_event, label_ids: []
+ :milestone_id, :state_event, :task_num, label_ids: []
)
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 8dca0400693..20a733b10e1 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -122,7 +122,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.open? && @merge_request.can_be_merged?
@merge_request.should_remove_source_branch = params[:should_remove_source_branch]
- @merge_request.automerge!(current_user, params[:merge_commit_message])
+ @merge_request.automerge!(current_user, params[:commit_message])
@status = true
else
@status = false
@@ -143,7 +143,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def update_branches
@target_project = selected_target_project
@target_branches = @target_project.repository.branch_names
- @target_branches
respond_to do |format|
format.js
@@ -151,8 +150,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def ci_status
- status = @merge_request.source_project.ci_service.commit_status(merge_request.last_commit.sha)
- response = {status: status}
+ ci_service = @merge_request.source_project.ci_service
+ status = ci_service.commit_status(merge_request.last_commit.sha)
+
+ if ci_service.respond_to?(:commit_coverage)
+ coverage = ci_service.commit_coverage(merge_request.last_commit.sha)
+ end
+
+ response = {
+ status: status,
+ coverage: coverage
+ }
render json: response
end
@@ -242,7 +250,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id,
- :state_event, :description, label_ids: []
+ :state_event, :description, :task_num, label_ids: []
)
end
end
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 9832495c64f..ada1aed0df7 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -3,8 +3,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ApplicationHelper
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb
index 71a5c6499ec..ffba706b2f6 100644
--- a/app/controllers/projects/new_tree_controller.rb
+++ b/app/controllers/projects/new_tree_controller.rb
@@ -1,6 +1,6 @@
class Projects::NewTreeController < Projects::BaseTreeController
before_filter :require_branch_head
- before_filter :authorize_push!
+ before_filter :authorize_push_code!
def show
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 5ec9c576a66..fdbc4c5a098 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -3,8 +3,7 @@ class Projects::RawController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 7997c726fbb..5d9336bdc49 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -2,8 +2,7 @@ class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def switch
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index c030320d037..bcd14a1c847 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -1,14 +1,8 @@
class Projects::RepositoriesController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
- def stats
- @stats = Gitlab::Git::Stats.new(@repository.raw_repository, @repository.root_ref)
- @graph = @stats.graph
- end
-
def archive
unless can?(current_user, :download_code, @project)
render_404 and return
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index b143dec3a93..a5f30dcfd9d 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -40,7 +40,9 @@ class Projects::ServicesController < Projects::ApplicationController
def service_params
params.require(:service).permit(
:title, :token, :type, :active, :api_key, :subdomain,
- :room, :recipients, :project_url
+ :room, :recipients, :project_url, :webhook,
+ :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
+ :build_key
)
end
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index cba058fe214..9d5dd8a95cc 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController
respond_to :html
def index
- @snippets = @project.snippets.fresh.non_expired
+ @snippets = SnippetsFinder.new.execute(current_user, {
+ filter: :by_project,
+ project: @project
+ })
end
def new
@@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def snippet_params
- params.require(:project_snippet).permit(:title, :content, :file_name, :private)
+ params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
end
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 537c94bda20..162ddef0fec 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -1,10 +1,8 @@
class Projects::TagsController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
before_filter :require_non_empty_project
-
- before_filter :authorize_code_access!
- before_filter :authorize_push!, only: [:create]
+ before_filter :authorize_download_code!
+ before_filter :authorize_push_code!, only: [:create]
before_filter :authorize_admin_project!, only: [:destroy]
def index
diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb
index 7bb799eba64..0791e6080fb 100644
--- a/app/controllers/projects/team_members_controller.rb
+++ b/app/controllers/projects/team_members_controller.rb
@@ -10,7 +10,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def new
- @user_project_relation = project.project_members.new
+ @user_project_relation = @project.project_members.new
end
def create
@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def update
- @user_project_relation = project.project_members.find_by(user_id: member)
+ @user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params)
unless @user_project_relation.valid?
@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def destroy
- @user_project_relation = project.project_members.find_by(user_id: member)
+ @user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.destroy
respond_to do |format|
@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def leave
- project.project_members.find_by(user_id: current_user).destroy
+ @project.project_members.find_by(user_id: current_user).destroy
respond_to do |format|
format.html { redirect_to :back }
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index b3380a6ff23..b5910c902e4 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -4,9 +4,7 @@ class ProjectsController < ApplicationController
before_filter :repository, except: [:new, :create]
# Authorize
- before_filter :authorize_read_project!, except: [:index, :new, :create]
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
- before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
layout 'navless', only: [:new, :create, :fork]
before_filter :set_title, only: [:new, :create]
@@ -53,8 +51,6 @@ class ProjectsController < ApplicationController
return
end
- return authenticate_user! unless @project.public? || current_user
-
limit = (params[:limit] || 20).to_i
@events = @project.events.recent
@events = event_filter.apply_filter(@events)
@@ -76,7 +72,7 @@ class ProjectsController < ApplicationController
end
def import
- if project.import_finished?
+ if @project.import_finished?
redirect_to @project
return
end
@@ -98,7 +94,7 @@ class ProjectsController < ApplicationController
end
def destroy
- return access_denied! unless can?(current_user, :remove_project, project)
+ return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
@@ -148,8 +144,8 @@ class ProjectsController < ApplicationController
end
def archive
- return access_denied! unless can?(current_user, :archive_project, project)
- project.archive!
+ return access_denied! unless can?(current_user, :archive_project, @project)
+ @project.archive!
respond_to do |format|
format.html { redirect_to @project }
@@ -157,8 +153,8 @@ class ProjectsController < ApplicationController
end
def unarchive
- return access_denied! unless can?(current_user, :archive_project, project)
- project.unarchive!
+ return access_denied! unless can?(current_user, :archive_project, @project)
+ @project.unarchive!
respond_to do |format|
format.html { redirect_to @project }
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 9e70978992f..6d3214b70a8 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -15,11 +15,11 @@ class RegistrationsController < Devise::RegistrationsController
super
end
- def after_sign_up_path_for resource
+ def after_sign_up_path_for(resource)
new_user_session_path
end
- def after_inactive_sign_up_path_for resource
+ def after_inactive_sign_up_path_for(resource)
new_user_session_path
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 1bdba75c5e7..5ced98152a5 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -18,6 +18,10 @@ class SessionsController < Devise::SessionsController
store_location_for(:redirect, redirect_path)
end
+ if Gitlab.config.ldap.enabled
+ @ldap_servers = Gitlab::LDAP::Config.servers
+ end
+
super
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 3927584235e..bf3312fedc8 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -9,12 +9,14 @@ class SnippetsController < ApplicationController
before_filter :set_title
+ skip_before_filter :authenticate_user!, only: [:index, :user_index, :show, :raw]
+
respond_to :html
- layout 'navless'
+ layout :determine_layout
def index
- @snippets = Snippet.are_public.fresh.non_expired.page(params[:page]).per(20)
+ @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(20)
end
def user_index
@@ -22,22 +24,11 @@ class SnippetsController < ApplicationController
render_404 and return unless @user
- @snippets = @user.snippets.fresh.non_expired
-
- if @user == current_user
- @snippets = case params[:scope]
- when 'are_public' then
- @snippets.are_public
- when 'are_private' then
- @snippets.are_private
- else
- @snippets
- end
- else
- @snippets = @snippets.are_public
- end
-
- @snippets = @snippets.page(params[:page]).per(20)
+ @snippets = SnippetsFinder.new.execute(current_user, {
+ filter: :by_user,
+ user: @user,
+ scope: params[:scope]}).
+ page(params[:page]).per(20)
if @user == current_user
render 'current_user_index'
@@ -95,7 +86,14 @@ class SnippetsController < ApplicationController
protected
def snippet
- @snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id])
+ @snippet ||= if current_user
+ PersonalSnippet.where("author_id = ? OR visibility_level IN (?)",
+ current_user.id,
+ [Snippet::PUBLIC, Snippet::INTERNAL]).
+ find(params[:id])
+ else
+ PersonalSnippet.are_public.find(params[:id])
+ end
end
def authorize_modify_snippet!
@@ -111,6 +109,10 @@ class SnippetsController < ApplicationController
end
def snippet_params
- params.require(:personal_snippet).permit(:title, :content, :file_name, :private)
+ params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
+ end
+
+ def determine_layout
+ current_user ? 'navless' : 'public_users'
end
end
diff --git a/app/finders/README.md b/app/finders/README.md
index 47823c51efb..1f46518d230 100644
--- a/app/finders/README.md
+++ b/app/finders/README.md
@@ -1,7 +1,7 @@
# Finders
-This type of classes responsible for collectiong items based on different conditions.
-To prevent lookup methods in models like this:
+This type of classes responsible for collection items based on different conditions.
+To prevent lookup methods in models like this:
```ruby
class Project
@@ -13,10 +13,10 @@ end
issues = project.issues_for_user_filtered_by(user, params)
```
-Better use this:
+Better use this:
```ruby
issues = IssuesFinder.new.execute(project, user, filter)
```
-It will help keep models thiner
+It will help keep models thiner.
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 56c4f22120d..d0574240511 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -48,7 +48,7 @@ class IssuableFinder
else
[]
end
- elsif current_user && params[:authorized_only].presence
+ elsif current_user && params[:authorized_only].presence && !current_user_related?
klass.of_projects(current_user.authorized_projects).references(:project)
else
klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
@@ -142,4 +142,8 @@ class IssuableFinder
def project
Project.where(id: params[:project_id]).first if params[:project_id].present?
end
+
+ def current_user_related?
+ params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
+ end
end
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
new file mode 100644
index 00000000000..4b0c69f2d2f
--- /dev/null
+++ b/app/finders/snippets_finder.rb
@@ -0,0 +1,63 @@
+class SnippetsFinder
+ def execute(current_user, params = {})
+ filter = params[:filter]
+
+ case filter
+ when :all then
+ snippets(current_user).fresh.non_expired
+ when :by_user then
+ by_user(current_user, params[:user], params[:scope])
+ when :by_project
+ by_project(current_user, params[:project])
+ end
+ end
+
+ private
+
+ def snippets(current_user)
+ if current_user
+ Snippet.public_and_internal
+ else
+ # Not authenticated
+ #
+ # Return only:
+ # public snippets
+ Snippet.are_public
+ end
+ end
+
+ def by_user(current_user, user, scope)
+ snippets = user.snippets.fresh.non_expired
+
+ return snippets.are_public unless current_user
+
+ if user == current_user
+ case scope
+ when 'are_internal' then
+ snippets.are_internal
+ when 'are_private' then
+ snippets.are_private
+ when 'are_public' then
+ snippets.are_public
+ else
+ snippets
+ end
+ else
+ snippets.public_and_internal
+ end
+ end
+
+ def by_project(current_user, project)
+ snippets = project.snippets.fresh.non_expired
+
+ if current_user
+ if project.team.member?(current_user.id)
+ snippets
+ else
+ snippets.public_and_internal
+ end
+ else
+ snippets.are_public
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 07938b8065d..021bd0a494c 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -151,12 +151,6 @@ module ApplicationHelper
sanitize(str, tags: %w(a span))
end
- def image_url(source)
- # prevent relative_root_path being added twice (it's part of root_url and path_to_image)
- root_url.sub(/#{root_path}$/, path_to_image(source))
- end
-
- alias_method :url_to_image, :image_url
def body_data_page
path = controller.controller_path.split('/')
@@ -235,7 +229,7 @@ module ApplicationHelper
css_class << " hide" unless visible
content_tag :div, class: css_class do
- content_tag(:i, nil, class: 'icon-spinner icon-spin') + text
+ content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text
end
end
@@ -265,4 +259,16 @@ module ApplicationHelper
super
end
+
+ def escaped_autolink(text)
+ auto_link ERB::Util.html_escape(text), link: :urls
+ end
+
+ def promo_host
+ 'about.gitlab.com'
+ end
+
+ def promo_url
+ 'https://' + promo_host
+ end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index cab2984a4c4..0e0532b65b2 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -120,4 +120,8 @@ module CommitsHelper
class: 'commit-short-id')
end
end
+
+ def truncate_sha(sha)
+ Commit.truncate_sha(sha)
+ end
end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index c4e33e3308f..acc0eeb76b3 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -37,40 +37,31 @@ module DashboardHelper
end
def assigned_entities_count(current_user, entity, scope = nil)
- items = current_user.send("assigned_" + entity.pluralize).opened
-
- if scope.kind_of?(Group)
- items = items.of_group(scope)
- elsif scope.kind_of?(Project)
- items = items.of_projects(scope)
- end
-
- items.count
+ items = current_user.send('assigned_' + entity.pluralize)
+ get_count(items, scope)
end
def authored_entities_count(current_user, entity, scope = nil)
- items = current_user.send(entity.pluralize).opened
-
- if scope.kind_of?(Group)
- items = items.of_group(scope)
- elsif scope.kind_of?(Project)
- items = items.of_projects(scope)
- end
-
- items.count
+ items = current_user.send(entity.pluralize)
+ get_count(items, scope)
end
def authorized_entities_count(current_user, entity, scope = nil)
- items = entity.classify.constantize.opened
+ items = entity.classify.constantize
+ get_count(items, scope, true, current_user)
+ end
+
+ protected
+ def get_count(items, scope, get_authorized = false, current_user = nil)
+ items = items.opened
if scope.kind_of?(Group)
items = items.of_group(scope)
elsif scope.kind_of?(Project)
items = items.of_projects(scope)
- else
+ elsif get_authorized
items = items.of_projects(current_user.authorized_projects)
end
-
items.count
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 6f738764b0e..71f97fbb8c8 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -19,7 +19,7 @@ module EventsHelper
[event.action_name, target].join(" ")
end
- def event_filter_link key, tooltip
+ def event_filter_link(key, tooltip)
key = key.to_s
inactive = if @event_filter.active? key
nil
@@ -36,10 +36,10 @@ module EventsHelper
def icon_for_event
{
- EventFilter.push => "icon-upload-alt",
- EventFilter.merged => "icon-check",
- EventFilter.comments => "icon-comments",
- EventFilter.team => "icon-user",
+ EventFilter.push => 'fa fa-upload',
+ EventFilter.merged => 'fa fa-check-square-o',
+ EventFilter.comments => 'fa fa-comments',
+ EventFilter.team => 'fa fa-user',
}
end
@@ -136,9 +136,8 @@ module EventsHelper
end
def event_note(text)
- text = first_line_in_markdown(text)
- text = truncate(text, length: 150)
- sanitize(markdown(text), tags: %w(a img b pre p))
+ text = first_line_in_markdown(text, 150)
+ sanitize(text, tags: %w(a img b pre code p))
end
def event_commit_title(message)
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 0365681a128..7d3cb749829 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -51,12 +51,14 @@ module GitlabMarkdownHelper
@markdown.render(text).html_safe
end
- def first_line_in_markdown(text)
- line = text.split("\n").detect do |i|
- i.present? && markdown(i).present?
- end
- line += '...' unless line.nil?
- line
+ # Return the first line of +text+, up to +max_chars+, after parsing the line
+ # as Markdown. HTML tags in the parsed output are not counted toward the
+ # +max_chars+ limit. If the length limit falls within a tag's contents, then
+ # the tag contents are truncated without removing the closing tag.
+ def first_line_in_markdown(text, max_chars = nil)
+ md = markdown(text).strip
+
+ truncate_visible(md, max_chars || md.length) if md.present?
end
def render_wiki_content(wiki_page)
@@ -204,4 +206,52 @@ module GitlabMarkdownHelper
def correct_ref
@ref ? @ref : "master"
end
+
+ private
+
+ # Return +text+, truncated to +max_chars+ characters, excluding any HTML
+ # tags.
+ def truncate_visible(text, max_chars)
+ doc = Nokogiri::HTML.fragment(text)
+ content_length = 0
+ truncated = false
+
+ doc.traverse do |node|
+ if node.text? || node.content.empty?
+ if truncated
+ node.remove
+ next
+ end
+
+ # Handle line breaks within a node
+ if node.content.strip.lines.length > 1
+ node.content = "#{node.content.lines.first.chomp}..."
+ truncated = true
+ end
+
+ num_remaining = max_chars - content_length
+ if node.content.length > num_remaining
+ node.content = node.content.truncate(num_remaining)
+ truncated = true
+ end
+ content_length += node.content.length
+ end
+
+ truncated = truncate_if_block(node, truncated)
+ end
+
+ doc.to_html
+ end
+
+ # Used by #truncate_visible. If +node+ is the first block element, and the
+ # text hasn't already been truncated, then append "..." to the node contents
+ # and return true. Otherwise return false.
+ def truncate_if_block(node, truncated)
+ if node.element? && node.description.block? && !truncated
+ node.content = "#{node.content}..." if node.next_sibling
+ true
+ else
+ truncated
+ end
+ end
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index f0f771b4ba0..aaa8f8d0077 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -1,21 +1,21 @@
module IconsHelper
def boolean_to_icon(value)
if value.to_s == "true"
- content_tag :i, nil, class: 'icon-circle cgreen'
+ content_tag :i, nil, class: 'fa fa-circle cgreen'
else
- content_tag :i, nil, class: 'icon-off clgray'
+ content_tag :i, nil, class: 'fa fa-power-off clgray'
end
end
def public_icon
- content_tag :i, nil, class: 'icon-globe'
+ content_tag :i, nil, class: 'fa fa-globe'
end
def internal_icon
- content_tag :i, nil, class: 'icon-shield'
+ content_tag :i, nil, class: 'fa fa-shield'
end
def private_icon
- content_tag :i, nil, class: 'icon-lock'
+ content_tag :i, nil, class: 'fa fa-lock'
end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 2031519c32f..d513e0ba58e 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -1,5 +1,5 @@
module IssuesHelper
- def issue_css_classes issue
+ def issue_css_classes(issue)
classes = "issue"
classes << " closed" if issue.closed?
classes << " today" if issue.today?
@@ -62,6 +62,19 @@ module IssuesHelper
''
end
+ def issue_timestamp(issue)
+ # Shows the created at time and the updated at time if different
+ ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}"
+ if issue.updated_at != issue.created_at
+ ts << capture_haml do
+ haml_tag :small do
+ haml_concat " (Edited #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')})"
+ end
+ end
+ end
+ ts.html_safe
+ end
+
# Checks if issues_tracker setting exists in gitlab.yml
def external_issues_tracker_enabled?
Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any?
@@ -84,7 +97,7 @@ module IssuesHelper
'id', 'name', object.assignee_id)
end
- def milestone_options object
+ def milestone_options(object)
options_from_collection_for_select(object.project.milestones.active,
'id', 'title', object.milestone_id)
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index cc63db2035e..fe6fd5832fc 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -19,19 +19,18 @@ module MergeRequestsHelper
source_project_id: event.project.id,
target_project_id: target_project.id,
source_branch: event.branch_name,
- target_branch: target_project.repository.root_ref,
- title: event.branch_name.titleize.humanize
+ target_branch: target_project.repository.root_ref
}
end
- def mr_css_classes mr
+ def mr_css_classes(mr)
classes = "merge-request"
classes << " closed" if mr.closed?
classes << " merged" if mr.merged?
classes
end
- def ci_build_details_path merge_request
+ def ci_build_details_path(merge_request)
merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha)
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 15d4b875c4c..901052edec6 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -69,7 +69,7 @@ module NotesHelper
button_tag class: 'btn reply-btn js-discussion-reply-button',
data: data, title: 'Add a reply' do
- link_text = content_tag(:i, nil, class: 'icon-comment')
+ link_text = content_tag(:i, nil, class: 'fa fa-comment')
link_text << ' Reply'
end
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 6c43f97446b..bad380e98a8 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -1,13 +1,13 @@
module NotificationsHelper
def notification_icon(notification)
if notification.disabled?
- content_tag :i, nil, class: 'icon-volume-off ns-mute'
+ content_tag :i, nil, class: 'fa fa-volume-off ns-mute'
elsif notification.participating?
- content_tag :i, nil, class: 'icon-volume-down ns-part'
+ content_tag :i, nil, class: 'fa fa-volume-down ns-part'
elsif notification.watch?
- content_tag :i, nil, class: 'icon-volume-up ns-watch'
+ content_tag :i, nil, class: 'fa fa-volume-up ns-watch'
else
- content_tag :i, nil, class: 'icon-circle-blank ns-default'
+ content_tag :i, nil, class: 'fa fa-circle-o ns-default'
end
end
end
diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb
index c0177dacbf8..7024483b8b3 100644
--- a/app/helpers/oauth_helper.rb
+++ b/app/helpers/oauth_helper.rb
@@ -1,6 +1,6 @@
module OauthHelper
def ldap_enabled?
- Devise.omniauth_providers.include?(:ldap)
+ Gitlab.config.ldap.enabled
end
def default_providers
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
index 297ae83d895..0b375558305 100644
--- a/app/helpers/profile_helper.rb
+++ b/app/helpers/profile_helper.rb
@@ -1,5 +1,5 @@
module ProfileHelper
- def oauth_active_class provider
+ def oauth_active_class(provider)
if current_user.provider == provider.to_s
'active'
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index f7da30bcc4b..fb5470d98e5 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -3,7 +3,7 @@ module ProjectsHelper
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
end
- def link_to_project project
+ def link_to_project(project)
link_to project do
title = content_tag(:span, project.name, class: 'project-name')
@@ -39,15 +39,15 @@ module ProjectsHelper
end
end
- def project_title project
+ def project_title(project)
if project.group
content_tag :span do
- link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
+ link_to(simple_sanitize(project.group.name), group_path(project.group)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project))
end
else
owner = project.namespace.owner
content_tag :span do
- link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name
+ link_to(simple_sanitize(owner.name), user_path(owner)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project))
end
end
end
@@ -56,6 +56,10 @@ module ProjectsHelper
"You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?"
end
+ def transfer_project_message(project)
+ "You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
+ end
+
def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user)
end
@@ -128,12 +132,12 @@ module ProjectsHelper
toggle_html = content_tag('span', class: 'toggle') do
toggle_text = if starred
- 'Unstar'
+ ' Unstar'
else
- 'Star'
+ ' Star'
end
- content_tag('i', ' ', class: 'icon-star') + toggle_text
+ content_tag('i', ' ', class: 'fa fa-star') + toggle_text
end
count_html = content_tag('span', class: 'count') do
@@ -157,7 +161,7 @@ module ProjectsHelper
end
def link_to_toggle_fork
- out = content_tag(:i, '', class: 'icon-code-fork')
+ out = content_tag(:i, '', class: 'fa fa-code-fork')
out << ' Fork'
out << content_tag(:span, class: 'count') do
@project.forks_count.to_s
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 610175f8447..bc43e078568 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -89,7 +89,7 @@ module TabHelper
end
# Use nav_tab for save controller/action but different params
- def nav_tab key, value, &block
+ def nav_tab(key, value, &block)
o = {}
o[:class] = ""
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index ebed6a83746..ef89bb32c6d 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -1,9 +1,9 @@
module TagsHelper
- def tag_path tag
+ def tag_path(tag)
"/tags/#{tag}"
end
- def tag_list project
+ def tag_list(project)
html = ''
project.tag_list.each do |tag|
html += link_to tag, tag_path(tag)
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index d815257a4e3..8e209498323 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -36,9 +36,9 @@ module TreeHelper
# type - String type of the tree item; either 'folder' or 'file'
def tree_icon(type)
icon_class = if type == 'folder'
- 'icon-folder-close'
+ 'fa fa-folder'
else
- 'icon-file-alt'
+ 'fa fa-file-o'
end
content_tag :i, nil, class: icon_class
@@ -66,7 +66,7 @@ module TreeHelper
def tree_breadcrumbs(tree, max_links = 2)
if @path.present?
part_path = ""
- parts = @path.split("\/")
+ parts = @path.split('/')
yield('..', nil) if parts.count > max_links
@@ -80,7 +80,7 @@ module TreeHelper
end
end
- def up_dir_path tree
+ def up_dir_path(tree)
file = File.join(@path, "..")
tree_join(@ref, file)
end
@@ -90,7 +90,7 @@ module TreeHelper
end
def editing_preview_title(filename)
- if gitlab_markdown?(filename) || markup?(filename)
+ if Gitlab::MarkdownHelper.previewable?(filename)
'Preview'
else
'Diff'
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 8b83b8ff640..deb9c8b4d49 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -28,6 +28,23 @@ module VisibilityLevelHelper
end
end
+ def snippet_visibility_level_description(level)
+ capture_haml do
+ haml_tag :span do
+ case level
+ when Gitlab::VisibilityLevel::PRIVATE
+ haml_concat "The snippet is visible only for me"
+ when Gitlab::VisibilityLevel::INTERNAL
+ haml_concat "The snippet is visible for any logged in user."
+ when Gitlab::VisibilityLevel::PUBLIC
+ haml_concat "The snippet can be accessed"
+ haml_concat "without any"
+ haml_concat "authentication."
+ end
+ end
+ end
+ end
+
def visibility_level_icon(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 716a23a4284..97a72bf3635 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -184,7 +184,7 @@ class Ability
]
end
- def group_abilities user, group
+ def group_abilities(user, group)
rules = []
if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any?
@@ -209,7 +209,7 @@ class Ability
rules.flatten
end
- def namespace_abilities user, namespace
+ def namespace_abilities(user, namespace)
rules = []
# Only namespace owner and administrators can manage it
@@ -262,5 +262,13 @@ class Ability
end
rules
end
+
+ def abilities
+ @abilities ||= begin
+ abilities = Six.new
+ abilities << self
+ abilities
+ end
+ end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index c8b2e0475ff..212229649fc 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -19,13 +19,24 @@ class Commit
class << self
def decorate(commits)
- commits.map { |c| self.new(c) }
+ commits.map do |commit|
+ if commit.kind_of?(Commit)
+ commit
+ else
+ self.new(commit)
+ end
+ end
end
# Calculate number of lines to render for diffs
def diff_line_count(diffs)
diffs.reduce(0) { |sum, d| sum + d.diff.lines.count }
end
+
+ # Truncate sha to 8 characters
+ def truncate_sha(sha)
+ sha[0..7]
+ end
end
attr_accessor :raw
@@ -88,15 +99,30 @@ class Commit
description.present?
end
+ def hook_attrs(project)
+ path_with_namespace = project.path_with_namespace
+
+ {
+ id: id,
+ message: safe_message,
+ timestamp: committed_date.xmlschema,
+ url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}",
+ author: {
+ name: author_name,
+ email: author_email
+ }
+ }
+ end
+
# Discover issues should be closed when this commit is pushed to a project's
# default branch.
- def closes_issues project
+ def closes_issues(project)
Gitlab::ClosingIssueExtractor.closed_by_message_in_project(safe_message, project)
end
# Mentionable override.
def gfm_reference
- "commit #{sha[0..5]}"
+ "commit #{id}"
end
def method_missing(m, *args, &block)
@@ -109,6 +135,11 @@ class Commit
super
end
+ # Truncate sha to 8 characters
+ def short_id
+ @raw.short_id(7)
+ end
+
def parents
@parents ||= Commit.decorate(super)
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 698b5b8c30a..f49708fd6eb 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -131,10 +131,11 @@ module Issuable
users.concat(mentions.reduce([], :|)).uniq
end
- def to_hook_data
+ def to_hook_data(user)
{
object_kind: self.class.name.underscore,
- object_attributes: self.attributes
+ user: user.hook_attrs,
+ object_attributes: hook_attrs
}
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 81414959f3b..6c1aa99668a 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -10,7 +10,7 @@ module Mentionable
module ClassMethods
# Indicate which attributes of the Mentionable to search for GFM references.
- def attr_mentionable *attrs
+ def attr_mentionable(*attrs)
mentionable_attrs.concat(attrs.map(&:to_s))
end
@@ -38,7 +38,7 @@ module Mentionable
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
- def has_mentioned? target
+ def has_mentioned?(target)
Note.cross_reference_exists?(target, local_reference)
end
@@ -52,11 +52,7 @@ module Mentionable
if identifier == "all"
users += project.team.members.flatten
else
- if has_project
- id = project.team.members.find_by(username: identifier).try(:id)
- else
- id = User.find_by(username: identifier).try(:id)
- end
+ id = User.find_by(username: identifier).try(:id)
users << User.find(id) unless id.blank?
end
end
@@ -64,15 +60,17 @@ module Mentionable
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
- def references p = project, text = mentionable_text
+ def references(p = project, text = mentionable_text)
return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new
- ext.analyze(text)
- (ext.issues_for(p) + ext.merge_requests_for(p) + ext.commits_for(p)).uniq - [local_reference]
+ ext.analyze(text, p)
+ (ext.issues_for +
+ ext.merge_requests_for +
+ ext.commits_for).uniq - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
- def create_cross_references! p = project, a = author, without = []
+ def create_cross_references!(p = project, a = author, without = [])
refs = references(p) - without
refs.each do |ref|
Note.create_cross_reference_note(ref, local_reference, a, p)
@@ -81,7 +79,7 @@ module Mentionable
# If the mentionable_text field is about to change, locate any *added* references and create cross references for
# them. Invoke from an observer's #before_save implementation.
- def notice_added_references p = project, a = author
+ def notice_added_references(p = project, a = author)
ch = changed_attributes
original, mentionable_changed = "", false
self.class.mentionable_attrs.each do |attr|
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
new file mode 100644
index 00000000000..410e8dc820b
--- /dev/null
+++ b/app/models/concerns/taskable.rb
@@ -0,0 +1,51 @@
+# Contains functionality for objects that can have task lists in their
+# descriptions. Task list items can be added with Markdown like "* [x] Fix
+# bugs".
+#
+# Used by MergeRequest and Issue
+module Taskable
+ TASK_PATTERN_MD = /^(?<bullet> *[*-] *)\[(?<checked>[ xX])\]/.freeze
+ TASK_PATTERN_HTML = /^<li>\[(?<checked>[ xX])\]/.freeze
+
+ # Change the state of a task list item for this Taskable. Edit the object's
+ # description by finding the nth task item and changing its checkbox
+ # placeholder to "[x]" if +checked+ is true, or "[ ]" if it's false.
+ # Note: task numbering starts with 1
+ def update_nth_task(n, checked)
+ index = 0
+ check_char = checked ? 'x' : ' '
+
+ # Do this instead of using #gsub! so that ActiveRecord detects that a field
+ # has changed.
+ self.description = self.description.gsub(TASK_PATTERN_MD) do |match|
+ index += 1
+ case index
+ when n then "#{$LAST_MATCH_INFO[:bullet]}[#{check_char}]"
+ else match
+ end
+ end
+
+ save
+ end
+
+ # Return true if this object's description has any task list items.
+ def tasks?
+ description && description.match(TASK_PATTERN_MD)
+ end
+
+ # Return a string that describes the current state of this Taskable's task
+ # list items, e.g. "20 tasks (12 done, 8 unfinished)"
+ def task_status
+ return nil unless description
+
+ num_tasks = 0
+ num_done = 0
+
+ description.scan(TASK_PATTERN_MD) do
+ num_tasks += 1
+ num_done += 1 unless $LAST_MATCH_INFO[:checked] == ' '
+ end
+
+ "#{num_tasks} tasks (#{num_done} done, #{num_tasks - num_done} unfinished)"
+ end
+end
diff --git a/app/models/event.rb b/app/models/event.rb
index 9e296c00281..65b4c2edfee 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -186,10 +186,6 @@ class Event < ActiveRecord::Base
data[:ref]["refs/heads"]
end
- def new_branch?
- commit_from =~ /^00000/
- end
-
def new_ref?
commit_from =~ /^00000/
end
@@ -266,7 +262,7 @@ class Event < ActiveRecord::Base
end
def note_short_commit_id
- note_commit_id[0..8]
+ Commit.truncate_sha(note_commit_id)
end
def note_commit?
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 752eb8074ac..23fa01e0b70 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -21,6 +21,7 @@ class WebHook < ActiveRecord::Base
default_value_for :push_events, true
default_value_for :issues_events, false
default_value_for :merge_requests_events, false
+ default_value_for :tag_push_events, false
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 45a8e43b03d..8a9e969248c 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -23,6 +23,7 @@ require 'file_size_validator'
class Issue < ActiveRecord::Base
include Issuable
include InternalId
+ include Taskable
ActsAsTaggableOn.strict_case_match = true
@@ -48,6 +49,10 @@ class Issue < ActiveRecord::Base
state :closed
end
+ def hook_attrs
+ attributes
+ end
+
# Mentionable overrides.
def gfm_reference
@@ -65,4 +70,9 @@ class Issue < ActiveRecord::Base
def reset_events_cache
Event.reset_event_cache_for(self)
end
+
+ # To allow polymorphism with MergeRequest.
+ def source_project
+ project
+ end
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 7dc13c18bf3..671ef466baa 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -1,3 +1,18 @@
+# == Schema Information
+#
+# Table name: members
+#
+# id :integer not null, primary key
+# access_level :integer not null
+# source_id :integer not null
+# source_type :string(255) not null
+# user_id :integer not null
+# notification_level :integer not null
+# type :string(255)
+# created_at :datetime
+# updated_at :datetime
+#
+
class Member < ActiveRecord::Base
include Notifiable
include Gitlab::Access
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index e72393c4278..b7f296b13fb 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,3 +1,18 @@
+# == Schema Information
+#
+# Table name: members
+#
+# id :integer not null, primary key
+# access_level :integer not null
+# source_id :integer not null
+# source_type :string(255) not null
+# user_id :integer not null
+# notification_level :integer not null
+# type :string(255)
+# created_at :datetime
+# updated_at :datetime
+#
+
class GroupMember < Member
SOURCE_TYPE = 'Namespace'
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index f14900ad3e6..30c09f768d7 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -1,3 +1,18 @@
+# == Schema Information
+#
+# Table name: members
+#
+# id :integer not null, primary key
+# access_level :integer not null
+# source_id :integer not null
+# source_type :string(255) not null
+# user_id :integer not null
+# notification_level :integer not null
+# type :string(255)
+# created_at :datetime
+# updated_at :datetime
+#
+
class ProjectMember < Member
SOURCE_TYPE = 'Project'
@@ -77,7 +92,7 @@ class ProjectMember < Member
false
end
- def truncate_team project
+ def truncate_team(project)
truncate_teams [project.id]
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 4894c617674..7c525b02f48 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -25,6 +25,7 @@ require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base
include Issuable
+ include Taskable
include InternalId
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
@@ -211,6 +212,20 @@ class MergeRequest < ActiveRecord::Base
Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
end
+ def hook_attrs
+ attrs = {
+ source: source_project.hook_attrs,
+ target: target_project.hook_attrs,
+ last_commit: nil
+ }
+
+ unless last_commit.nil?
+ attrs.merge!(last_commit: last_commit.hook_attrs(source_project))
+ end
+
+ attributes.merge!(attrs)
+ end
+
def for_fork?
target_project != source_project
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 409e82ed1ef..a71122d5e07 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -55,7 +55,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def last_commit_short_sha
- @last_commit_short_sha ||= last_commit.sha[0..10]
+ @last_commit_short_sha ||= last_commit.short_id
end
private
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b19b72906e7..c0c6de0ee7d 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -38,7 +38,7 @@ class Namespace < ActiveRecord::Base
scope :root, -> { where('type IS NULL') }
- def self.search query
+ def self.search(query)
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index 9c95470beb1..43979b5e807 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -6,7 +6,7 @@ module Network
@max_count ||= 650
end
- def initialize project, ref, commit, filter_ref
+ def initialize(project, ref, commit, filter_ref)
@project = project
@ref = ref
@commit = commit
diff --git a/app/models/note.rb b/app/models/note.rb
index d70ebcd8e6d..5bf645bbd1d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -47,7 +47,7 @@ class Note < ActiveRecord::Base
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: [nil, '']) }
-
+ scope :system, ->{ where(system: true) }
scope :common, ->{ where(noteable_type: ["", nil]) }
scope :fresh, ->{ order("created_at ASC, id ASC") }
scope :inc_author_project, ->{ includes(:project, :author) }
@@ -70,13 +70,17 @@ class Note < ActiveRecord::Base
)
end
- # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
- # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
+ # +noteable+ was referenced from +mentioner+, by including GFM in either
+ # +mentioner+'s description or an associated Note.
+ # Create a system Note associated with +noteable+ with a GFM back-reference
+ # to +mentioner+.
def create_cross_reference_note(noteable, mentioner, author, project)
+ gfm_reference = mentioner_gfm_ref(noteable, mentioner, project)
+
note_options = {
project: project,
author: author,
- note: "_mentioned in #{mentioner.gfm_reference}_",
+ note: cross_reference_note_content(gfm_reference),
system: true
}
@@ -86,7 +90,7 @@ class Note < ActiveRecord::Base
note_options.merge!(noteable: noteable)
end
- create(note_options)
+ create(note_options) unless cross_reference_disallowed?(noteable, mentioner)
end
def create_milestone_change_note(noteable, project, author, milestone)
@@ -161,24 +165,111 @@ class Note < ActiveRecord::Base
[:discussion, type.try(:underscore), id, line_code].join("-").to_sym
end
+ # Determine if cross reference note should be created.
+ # eg. mentioning a commit in MR comments which exists inside a MR
+ # should not create "mentioned in" note.
+ def cross_reference_disallowed?(noteable, mentioner)
+ if mentioner.kind_of?(MergeRequest)
+ mentioner.commits.map(&:id).include? noteable.id
+ end
+ end
+
# Determine whether or not a cross-reference note already exists.
def cross_reference_exists?(noteable, mentioner)
- where(noteable_id: noteable.id, system: true, note: "_mentioned in #{mentioner.gfm_reference}_").any?
+ gfm_reference = mentioner_gfm_ref(noteable, mentioner)
+ notes = if noteable.is_a?(Commit)
+ where(commit_id: noteable.id)
+ else
+ where(noteable_id: noteable.id)
+ end
+
+ notes.where('note like ?', cross_reference_note_content(gfm_reference)).
+ system.any?
end
def search(query)
where("note like :query", query: "%#{query}%")
end
+
+ def cross_reference_note_prefix
+ '_mentioned in '
+ end
+
+ private
+
+ def cross_reference_note_content(gfm_reference)
+ cross_reference_note_prefix + "#{gfm_reference}_"
+ end
+
+ # Prepend the mentioner's namespaced project path to the GFM reference for
+ # cross-project references. For same-project references, return the
+ # unmodified GFM reference.
+ def mentioner_gfm_ref(noteable, mentioner, project = nil)
+ if mentioner.is_a?(Commit)
+ if project.nil?
+ return mentioner.gfm_reference.sub('commit ', 'commit %')
+ else
+ mentioning_project = project
+ end
+ else
+ mentioning_project = mentioner.project
+ end
+
+ noteable_project_id = noteable_project_id(noteable, mentioning_project)
+
+ full_gfm_reference(mentioning_project, noteable_project_id, mentioner)
+ end
+
+ # Return the ID of the project that +noteable+ belongs to, or nil if
+ # +noteable+ is a commit and is not part of the project that owns
+ # +mentioner+.
+ def noteable_project_id(noteable, mentioning_project)
+ if noteable.is_a?(Commit)
+ if mentioning_project.repository.commit(noteable.id)
+ # The noteable commit belongs to the mentioner's project
+ mentioning_project.id
+ else
+ nil
+ end
+ else
+ noteable.project.id
+ end
+ end
+
+ # Return the +mentioner+ GFM reference. If the mentioner and noteable
+ # projects are not the same, add the mentioning project's path to the
+ # returned value.
+ def full_gfm_reference(mentioning_project, noteable_project_id, mentioner)
+ if mentioning_project.id == noteable_project_id
+ mentioner.gfm_reference
+ else
+ if mentioner.is_a?(Commit)
+ mentioner.gfm_reference.sub(
+ /(commit )/,
+ "\\1#{mentioning_project.path_with_namespace}@"
+ )
+ else
+ mentioner.gfm_reference.sub(
+ /(issue |merge request )/,
+ "\\1#{mentioning_project.path_with_namespace}"
+ )
+ end
+ end
+ end
end
def commit_author
@commit_author ||=
- project.users.find_by(email: noteable.author_email) ||
- project.users.find_by(name: noteable.author_name)
+ project.team.users.find_by(email: noteable.author_email) ||
+ project.team.users.find_by(name: noteable.author_name)
rescue
nil
end
+ def cross_reference?
+ note.start_with?(self.class.cross_reference_note_prefix)
+ end
+
def find_diff
return nil unless noteable && noteable.diffs.present?
diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb
index a3c0d201ee5..9cee3b70cb3 100644
--- a/app/models/personal_snippet.rb
+++ b/app/models/personal_snippet.rb
@@ -2,17 +2,17 @@
#
# Table name: snippets
#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# expires_at :datetime
-# private :boolean default(TRUE), not null
-# type :string(255)
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# file_name :string(255)
+# expires_at :datetime
+# type :string(255)
+# visibility_level :integer default(0), not null
#
class PersonalSnippet < Snippet
diff --git a/app/models/project.rb b/app/models/project.rb
index f9278f19dad..d2576bb85d0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -64,6 +64,9 @@ class Project < ActiveRecord::Base
has_one :assembla_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
+ has_one :buildbox_service, dependent: :destroy
+ has_one :bamboo_service, dependent: :destroy
+ has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it
@@ -171,7 +174,7 @@ class Project < ActiveRecord::Base
end
def with_push
- includes(:events).where('events.action = ?', Event::PUSHED)
+ joins(:events).where('events.action = ?', Event::PUSHED)
end
def active
@@ -311,7 +314,7 @@ class Project < ActiveRecord::Base
end
def available_services_names
- %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack)
+ %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox bamboo)
end
def gitlab_ci?
@@ -331,7 +334,7 @@ class Project < ActiveRecord::Base
path
end
- def items_for entity
+ def items_for(entity)
case entity
when 'issue' then
issues
@@ -399,43 +402,8 @@ class Project < ActiveRecord::Base
end
def update_merge_requests(oldrev, newrev, ref, user)
- return true unless ref =~ /heads/
- branch_name = ref.gsub("refs/heads/", "")
- commits = self.repository.commits_between(oldrev, newrev)
- c_ids = commits.map(&:id)
-
- # Close merge requests
- mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
- mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
-
- mrs.uniq.each do |merge_request|
- MergeRequests::MergeService.new.execute(merge_request, user, nil)
- end
-
- # Update code for merge requests into project between project branches
- mrs = self.merge_requests.opened.by_branch(branch_name).to_a
- # Update code for merge requests between project and project fork
- mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
-
- mrs.uniq.each do |merge_request|
- merge_request.reload_code
- merge_request.mark_as_unchecked
- end
-
- # Add comment about pushing new commits to merge requests
- comment_mr_with_commits(branch_name, commits, user)
-
- true
- end
-
- def comment_mr_with_commits(branch_name, commits, user)
- mrs = self.origin_merge_requests.opened.where(source_branch: branch_name).to_a
- mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a
-
- mrs.uniq.each do |merge_request|
- Note.create_new_commits_note(merge_request, merge_request.project,
- user, commits)
- end
+ MergeRequests::RefreshService.new(self, user).
+ execute(oldrev, newrev, ref)
end
def valid_repo?
@@ -504,7 +472,7 @@ class Project < ActiveRecord::Base
end
# Check if current branch name is marked as protected in the system
- def protected_branch? branch_name
+ def protected_branch?(branch_name)
protected_branches_names.include?(branch_name)
end
@@ -544,6 +512,16 @@ class Project < ActiveRecord::Base
end
end
+ def hook_attrs
+ {
+ name: name,
+ ssh_url: ssh_url_to_repo,
+ http_url: http_url_to_repo,
+ namespace: namespace.name,
+ visibility_level: visibility_level
+ }
+ end
+
# Reset events cache related to this project
#
# Since we do cache @event we need to reset cache in special cases:
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index 3421a0330aa..0b90a14f39c 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
class AssemblaService < Service
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
new file mode 100644
index 00000000000..b9eec9ab21e
--- /dev/null
+++ b/app/models/project_services/bamboo_service.rb
@@ -0,0 +1,105 @@
+class BambooService < CiService
+ include HTTParty
+
+ prop_accessor :bamboo_url, :build_key, :username, :password
+
+ validates :bamboo_url, presence: true,
+ format: { with: URI::regexp }, if: :activated?
+ validates :build_key, presence: true, if: :activated?
+ validates :username, presence: true,
+ if: ->(service) { service.password? }, if: :activated?
+ validates :password, presence: true,
+ if: ->(service) { service.username? }, if: :activated?
+
+ attr_accessor :response
+
+ after_save :compose_service_hook, if: :activated?
+
+ def compose_service_hook
+ hook = service_hook || build_service_hook
+ hook.save
+ end
+
+ def title
+ 'Atlassian Bamboo CI'
+ end
+
+ def description
+ 'A continuous integration and build server'
+ end
+
+ def help
+ 'You must set up automatic revision labeling and a repository trigger in Bamboo.'
+ end
+
+ def to_param
+ 'bamboo'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'bamboo_url',
+ placeholder: 'Bamboo root URL like https://bamboo.example.com' },
+ { type: 'text', name: 'build_key',
+ placeholder: 'Bamboo build plan key like KEY' },
+ { type: 'text', name: 'username',
+ placeholder: 'A user with API access, if applicable' },
+ { type: 'password', name: 'password' },
+ ]
+ end
+
+ def build_info(sha)
+ url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
+
+ if username.blank? && password.blank?
+ @response = HTTParty.get(parsed_url.to_s, verify: false)
+ else
+ get_url = "#{url}&os_authType=basic"
+ auth = {
+ username: username,
+ password: password,
+ }
+ @response = HTTParty.get(get_url, verify: false, basic_auth: auth)
+ end
+ end
+
+ def build_page(sha)
+ build_info(sha) if @response.nil? || !@response.code
+
+ if @response.code != 200 || @response['results']['results']['size'] == '0'
+ # If actual build link can't be determined, send user to build summary page.
+ "#{bamboo_url}/browse/#{build_key}"
+ else
+ # If actual build link is available, go to build result page.
+ result_key = @response['results']['results']['result']['planResultKey']['key']
+ "#{bamboo_url}/browse/#{result_key}"
+ end
+ end
+
+ def commit_status(sha)
+ build_info(sha) if @response.nil? || !@response.code
+ return :error unless @response.code == 200 || @response.code == 404
+
+ status = if @response.code == 404 || @response['results']['results']['size'] == '0'
+ 'Pending'
+ else
+ @response['results']['results']['result']['buildState']
+ end
+
+ if status.include?('Success')
+ 'success'
+ elsif status.include?('Failed')
+ 'failed'
+ elsif status.include?('Pending')
+ 'pending'
+ else
+ :error
+ end
+ end
+
+ def execute(_data)
+ # Bamboo requires a GET and does not take any data.
+ self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
+ verify: false)
+ end
+end
diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb
new file mode 100644
index 00000000000..0ab67b79fe4
--- /dev/null
+++ b/app/models/project_services/buildbox_service.rb
@@ -0,0 +1,123 @@
+# == Schema Information
+#
+# Table name: 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
+#
+
+require "addressable/uri"
+
+class BuildboxService < CiService
+ prop_accessor :project_url, :token
+
+ validates :project_url, presence: true, if: :activated?
+ validates :token, presence: true, if: :activated?
+
+ after_save :compose_service_hook, if: :activated?
+
+ def webhook_url
+ "#{buildbox_endpoint('webhook')}/deliver/#{webhook_token}"
+ end
+
+ def compose_service_hook
+ hook = service_hook || build_service_hook
+ hook.url = webhook_url
+ hook.save
+ end
+
+ def execute(data)
+ service_hook.execute(data)
+ end
+
+ def commit_status(sha)
+ response = HTTParty.get(commit_status_path(sha), verify: false)
+
+ if response.code == 200 && response['status']
+ response['status']
+ else
+ :error
+ end
+ end
+
+ def commit_status_path(sha)
+ "#{buildbox_endpoint('gitlab')}/status/#{status_token}.json?commit=#{sha}"
+ end
+
+ def build_page(sha)
+ "#{project_url}/builds?commit=#{sha}"
+ end
+
+ def builds_path
+ "#{project_url}/builds?branch=#{project.default_branch}"
+ end
+
+ def status_img_path
+ "#{buildbox_endpoint('badge')}/#{status_token}.svg"
+ end
+
+ def title
+ 'Buildbox'
+ end
+
+ def description
+ 'Continuous integration and deployments'
+ end
+
+ def to_param
+ 'buildbox'
+ end
+
+ def fields
+ [
+ { type: 'text',
+ name: 'token',
+ placeholder: 'Buildbox project GitLab token' },
+
+ { type: 'text',
+ name: 'project_url',
+ placeholder: 'https://buildbox.io/example/project' }
+ ]
+ end
+
+ private
+
+ def webhook_token
+ token_parts.first
+ end
+
+ def status_token
+ token_parts.second
+ end
+
+ def token_parts
+ if token.present?
+ token.split(':')
+ else
+ []
+ end
+ end
+
+ def buildbox_endpoint(subdomain = nil)
+ endpoint = 'https://buildbox.io'
+
+ if subdomain.present?
+ uri = Addressable::URI.parse(endpoint)
+ new_endpoint = "#{uri.scheme || 'http'}://#{subdomain}.#{uri.host}"
+
+ if uri.port.present?
+ "#{new_endpoint}:#{uri.port}"
+ else
+ new_endpoint
+ end
+ else
+ endpoint
+ end
+ end
+end
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index 2d8950db491..0736ddab99b 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
class CampfireService < Service
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 829f495abc6..b1d5e49ede3 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
# Base class for CI services
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index 5c4537cfca5..b9071b98295 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
class EmailsOnPushService < Service
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 4d11b00c192..86705f5dabd 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
require "flowdock-git-hook"
@@ -37,13 +37,12 @@ class FlowdockService < Service
end
def execute(push_data)
- repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
Flowdock::Git.post(
push_data[:ref],
push_data[:before],
push_data[:after],
token: token,
- repo: repo_path,
+ repo: project.repository.path_to_repo,
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",
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 7b6c87e4cec..18fdd204ecd 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
require "gemnasium/gitlab_service"
@@ -38,14 +38,13 @@ class GemnasiumService < Service
end
def execute(push_data)
- repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
Gemnasium::GitlabService.execute(
ref: push_data[:ref],
before: push_data[:before],
after: push_data[:after],
token: token,
api_key: api_key,
- repo: repo_path
+ 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 0f327e75289..fadebf968bc 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
-# property :text
+# 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
#
class GitlabCiService < CiService
@@ -27,12 +27,17 @@ class GitlabCiService < CiService
hook.save
end
- def commit_status_path sha
- project_url + "/builds/#{sha}/status.json?token=#{token}"
+ def commit_status_path(sha)
+ project_url + "/commits/#{sha}/status.json?token=#{token}"
end
- def commit_status sha
- response = HTTParty.get(commit_status_path(sha), verify: false)
+ def get_ci_build(sha)
+ @ci_builds ||= {}
+ @ci_builds[sha] ||= HTTParty.get(commit_status_path(sha), verify: false)
+ end
+
+ def commit_status(sha)
+ response = get_ci_build(sha)
if response.code == 200 and response["status"]
response["status"]
@@ -41,8 +46,16 @@ class GitlabCiService < CiService
end
end
- def build_page sha
- project_url + "/builds/#{sha}"
+ def commit_coverage(sha)
+ response = get_ci_build(sha)
+
+ if response.code == 200 and response["coverage"]
+ response["coverage"]
+ end
+ end
+
+ def build_page(sha)
+ project_url + "/commits/#{sha}"
end
def builds_path
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 3a1ba168e6a..4078938cdbb 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
class HipchatService < Service
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 3aa928b92a0..09e114f9cca 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
class PivotaltrackerService < Service
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
new file mode 100644
index 00000000000..f247fde7762
--- /dev/null
+++ b/app/models/project_services/pushover_service.rb
@@ -0,0 +1,113 @@
+# == Schema Information
+#
+# Table name: 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
+#
+
+class PushoverService < Service
+ include HTTParty
+ base_uri 'https://api.pushover.net/1'
+
+ prop_accessor :api_key, :user_key, :device, :priority, :sound
+ validates :api_key, :user_key, :priority, presence: true, if: :activated?
+
+ def title
+ 'Pushover'
+ end
+
+ def description
+ 'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.'
+ end
+
+ def to_param
+ 'pushover'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'api_key', placeholder: 'Your application key' },
+ { type: 'text', name: 'user_key', placeholder: 'Your user key' },
+ { type: 'text', name: 'device', placeholder: 'Leave blank for all active devices' },
+ { type: 'select', name: 'priority', choices:
+ [
+ ['Lowest Priority', -2],
+ ['Low Priority', -1],
+ ['Normal Priority', 0],
+ ['High Priority', 1]
+ ],
+ default_choice: 0
+ },
+ { type: 'select', name: 'sound', choices:
+ [
+ ['Device default sound', nil],
+ ['Pushover (default)', 'pushover'],
+ ['Bike', 'bike'],
+ ['Bugle', 'bugle'],
+ ['Cash Register', 'cashregister'],
+ ['Classical', 'classical'],
+ ['Cosmic', 'cosmic'],
+ ['Falling', 'falling'],
+ ['Gamelan', 'gamelan'],
+ ['Incoming', 'incoming'],
+ ['Intermission', 'intermission'],
+ ['Magic', 'magic'],
+ ['Mechanical', 'mechanical'],
+ ['Piano Bar', 'pianobar'],
+ ['Siren', 'siren'],
+ ['Space Alarm', 'spacealarm'],
+ ['Tug Boat', 'tugboat'],
+ ['Alien Alarm (long)', 'alien'],
+ ['Climb (long)', 'climb'],
+ ['Persistent (long)', 'persistent'],
+ ['Pushover Echo (long)', 'echo'],
+ ['Up Down (long)', 'updown'],
+ ['None (silent)', 'none']
+ ]
+ },
+ ]
+ end
+
+ def execute(push_data)
+ ref = push_data[:ref].gsub('refs/heads/', '')
+ before = push_data[:before]
+ after = push_data[:after]
+
+ if before =~ /000000/
+ message = "#{push_data[:user_name]} pushed new branch \"#{ref}\"."
+ elsif after =~ /000000/
+ message = "#{push_data[:user_name]} deleted branch \"#{ref}\"."
+ else
+ message = "#{push_data[:user_name]} push to branch \"#{ref}\"."
+ end
+
+ if push_data[:total_commits_count] > 0
+ message << "\nTotal commits count: #{push_data[:total_commits_count]}"
+ end
+
+ pushover_data = {
+ token: api_key,
+ user: user_key,
+ device: device,
+ priority: priority,
+ title: "#{project.name_with_namespace}",
+ message: message,
+ url: push_data[:repository][:homepage],
+ url_title: "See project #{project.name_with_namespace}"
+ }
+
+ # Sound parameter MUST NOT be sent to API if not selected
+ if sound
+ pushover_data.merge!(sound: sound)
+ end
+
+ PushoverService.post('/messages.json', body: pushover_data)
+ end
+end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 4bda93f6006..963f5440b6f 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -2,21 +2,19 @@
#
# Table name: 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
+# 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
#
class SlackService < Service
- prop_accessor :room, :subdomain, :token
- validates :room, presence: true, if: :activated?
- validates :subdomain, presence: true, if: :activated?
- validates :token, presence: true, if: :activated?
+ prop_accessor :webhook
+ validates :webhook, presence: true, if: :activated?
def title
'Slack'
@@ -32,21 +30,19 @@ class SlackService < Service
def fields
[
- { type: 'text', name: 'subdomain', placeholder: '' },
- { type: 'text', name: 'token', placeholder: '' },
- { type: 'text', name: 'room', placeholder: 'Ex. #general' },
+ { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' }
]
end
def execute(push_data)
+ return unless webhook.present?
+
message = SlackMessage.new(push_data.merge(
project_url: project_url,
project_name: project_name
))
- notifier = Slack::Notifier.new(subdomain, token)
- notifier.channel = room
- notifier.username = 'GitLab'
+ notifier = Slack::Notifier.new(webhook)
notifier.ping(message.pretext, attachments: message.attachments)
end
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 14c88046423..9e2c1b0e18e 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -2,17 +2,17 @@
#
# Table name: snippets
#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# expires_at :datetime
-# private :boolean default(TRUE), not null
-# type :string(255)
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# file_name :string(255)
+# expires_at :datetime
+# type :string(255)
+# visibility_level :integer default(0), not null
#
class ProjectSnippet < Snippet
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index e2af10c6899..657ee23ae23 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -11,7 +11,7 @@ class ProjectTeam
# @team << [@user, :master]
# @team << [@users, :master]
#
- def << args
+ def <<(args)
users = args.first
if users.respond_to?(:each)
@@ -133,6 +133,10 @@ class ProjectTeam
max_tm_access(user.id) == Gitlab::Access::MASTER
end
+ def member?(user_id)
+ !!find_tm(user_id)
+ end
+
def max_tm_access(user_id)
access = []
access << project.project_members.find_by(user_id: user_id).try(:access_field)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a2bdfe87469..93994123a90 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -30,9 +30,11 @@ class Repository
commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit) if commit
commit
+ rescue Rugged::OdbError => ex
+ nil
end
- def commits(ref, path = nil, limit = nil, offset = nil)
+ def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
commits = Gitlab::Git::Commit.where(
repo: raw_repository,
ref: ref,
@@ -137,8 +139,18 @@ class Repository
def graph_log
Rails.cache.fetch(cache_key(:graph_log)) do
- stats = Gitlab::Git::GitStats.new(raw_repository, root_ref, Gitlab.config.git.timeout)
- stats.parsed_log
+ commits = raw_repository.log(limit: 6000, skip_merges: true,
+ ref: root_ref)
+ commits.map do |rugged_commit|
+ commit = Gitlab::Git::Commit.new(rugged_commit)
+
+ {
+ author_name: commit.author_name.force_encoding('UTF-8'),
+ author_email: commit.author_email.force_encoding('UTF-8'),
+ additions: commit.stats.additions,
+ deletions: commit.stats.deletions
+ }
+ end
end
end
@@ -223,12 +235,15 @@ class Repository
end
def last_commit_for_path(sha, path)
- commits(sha, path, 1).last
+ args = %W(git rev-list --max-count 1 #{sha} -- #{path})
+ sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
+ commit(sha)
end
# Remove archives older than 2 hours
def clean_old_archives
- Gitlab::Popen.popen(%W(find #{Gitlab.config.gitlab.repository_downloads_path} -mmin +120 -delete))
+ repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
+ Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end
def branches_sorted_by(value)
@@ -247,20 +262,18 @@ class Repository
end
def contributors
- log = graph_log.group_by { |i| i[:author_email] }
+ commits = self.commits(nil, nil, 2000, 0, true)
- log.map do |email, contributions|
+ commits.group_by(&:author_email).map do |email, commits|
contributor = Gitlab::Contributor.new
contributor.email = email
- contributions.each do |contribution|
+ commits.each do |commit|
if contributor.name.blank?
- contributor.name = contribution[:author_name]
+ contributor.name = commit.author_name
end
contributor.commits += 1
- contributor.additions += contribution[:additions] || 0
- contributor.deletions += contribution[:deletions] || 0
end
contributor
@@ -282,4 +295,21 @@ class Repository
blob_at(commit.parent_id, diff.old_path)
end
end
+
+ def branch_names_contains(sha)
+ args = %W(git branch --contains #{sha})
+ names = Gitlab::Popen.popen(args, path_to_repo).first
+
+ if names.respond_to?(:split)
+ names = names.split("\n").map(&:strip)
+
+ names.each do |name|
+ name.slice! '* '
+ end
+
+ names
+ else
+ []
+ end
+ end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 1f3a6520473..c489c1e96e1 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -2,14 +2,15 @@
#
# Table name: 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
+# 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
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 80c1af8f337..a47fbca3260 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -2,23 +2,24 @@
#
# Table name: snippets
#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# expires_at :datetime
-# private :boolean default(TRUE), not null
-# type :string(255)
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# file_name :string(255)
+# expires_at :datetime
+# type :string(255)
+# visibility_level :integer default(0), not null
#
class Snippet < ActiveRecord::Base
include Linguist::BlobHelper
+ include Gitlab::VisibilityLevel
- default_value_for :private, true
+ default_value_for :visibility_level, Snippet::PRIVATE
belongs_to :author, class_name: "User"
@@ -30,10 +31,13 @@ class Snippet < ActiveRecord::Base
validates :title, presence: true, length: { within: 0..255 }
validates :file_name, presence: true, length: { within: 0..255 }
validates :content, presence: true
+ validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
# Scopes
- scope :are_public, -> { where(private: false) }
- scope :are_private, -> { where(private: true) }
+ scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
+ scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) }
+ scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
+ scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
scope :fresh, -> { order("created_at DESC") }
scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
@@ -66,6 +70,10 @@ class Snippet < ActiveRecord::Base
expires_at && expires_at < Time.current
end
+ def visibility_level_field
+ visibility_level
+ end
+
class << self
def search(query)
where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%")
@@ -76,7 +84,7 @@ class Snippet < ActiveRecord::Base
end
def accessible_to(user)
- where('private = ? OR author_id = ?', false, user)
+ where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user)
end
end
end
diff --git a/app/models/tree.rb b/app/models/tree.rb
index 07c9a825e24..4f5d81f0a5e 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -15,7 +15,7 @@ class Tree
# by markup renderer.
if available_readmes.length > 1
supported_readmes = available_readmes.select do |readme|
- gitlab_markdown?(readme.name) || markup?(readme.name)
+ previewable?(readme.name)
end
# Take the first supported readme, or the first available readme, if we
diff --git a/app/models/user.rb b/app/models/user.rb
index ed3eba4cdf0..d400edc0df5 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -178,8 +178,7 @@ class User < ActiveRecord::Base
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
- scope :ldap, -> { where(provider: 'ldap') }
-
+ scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
#
@@ -196,6 +195,16 @@ class User < ActiveRecord::Base
end
end
+ def sort(method)
+ case method.to_s
+ when 'recent_sign_in' then reorder('users.last_sign_in_at DESC')
+ when 'oldest_sign_in' then reorder('users.last_sign_in_at ASC')
+ when 'recently_created' then reorder('users.created_at DESC')
+ when 'late_created' then reorder('users.created_at ASC')
+ else reorder("users.name ASC")
+ end
+ end
+
def find_for_commit(email, name)
# Prefer email match over name match
User.where(email: email).first ||
@@ -203,7 +212,7 @@ class User < ActiveRecord::Base
User.where(name: name).first
end
- def filter filter_name
+ def filter(filter_name)
case filter_name
when "admins"; self.admins
when "blocked"; self.blocked
@@ -213,10 +222,15 @@ class User < ActiveRecord::Base
end
end
- def search query
+ def search(query)
where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
end
+ def by_login(login)
+ where('lower(username) = :value OR lower(email) = :value',
+ value: login.to_s.downcase).first
+ 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
end
@@ -321,18 +335,14 @@ class User < ActiveRecord::Base
end
def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
+ Ability.abilities
end
def can_select_namespace?
several_namespaces? || admin
end
- def can? action, subject
+ def can?(action, subject)
abilities.allowed?(self, action, subject)
end
@@ -353,7 +363,7 @@ class User < ActiveRecord::Base
(personal_projects.count.to_f / projects_limit) * 100
end
- def recent_push project_id = nil
+ def recent_push(project_id = nil)
# Get push events not earlier than 2 hours ago
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
events = events.where(project_id: project_id) if project_id
@@ -382,11 +392,11 @@ class User < ActiveRecord::Base
project.team_member_by_id(self.id)
end
- def already_forked? project
+ def already_forked?(project)
!!fork_of(project)
end
- def fork_of project
+ def fork_of(project)
links = ForkedProjectLink.where(forked_from_project_id: project, forked_to_project_id: personal_projects)
if links.any?
@@ -397,7 +407,7 @@ class User < ActiveRecord::Base
end
def ldap_user?
- extern_uid && provider == 'ldap'
+ extern_uid && provider.start_with?('ldap')
end
def accessible_deploy_keys
@@ -488,6 +498,14 @@ class User < ActiveRecord::Base
end
end
+ def hook_attrs
+ {
+ name: name,
+ username: username,
+ avatar_url: avatar_url
+ }
+ end
+
def ensure_namespace_correct
# Ensure user has namespace
self.create_namespace!(path: self.username, name: self.username) unless self.namespace
@@ -512,7 +530,7 @@ class User < ActiveRecord::Base
NotificationService.new
end
- def log_info message
+ def log_info(message)
Gitlab::AppLogger.info message
end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 051612633cd..0d46eeaa18f 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -6,11 +6,7 @@ class BaseService
end
def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
+ Ability.abilities
end
def can?(object, action, subject)
@@ -25,7 +21,7 @@ class BaseService
EventCreateService.new
end
- def log_info message
+ def log_info(message)
Gitlab::AppLogger.info message
end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index db6f0831f8b..bd245100955 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -10,12 +10,6 @@ module Files
private
- def success
- out = super()
- out[:error] = ''
- out
- end
-
def repository
project.repository
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 6a13778d67c..3f5222c93f1 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -22,27 +22,27 @@ class GitPushService
project.update_repository_size
if push_to_branch?(ref)
- if push_to_existing_branch?(ref, oldrev)
- # Collect data for this git push
- @push_commits = project.repository.commits_between(oldrev, newrev)
- project.update_merge_requests(oldrev, newrev, ref, @user)
- process_commit_messages(ref)
+ if push_remove_branch?(ref, newrev)
+ @push_commits = []
elsif push_to_new_branch?(ref, oldrev)
# Re-find the pushed commits.
if is_default_branch?(ref)
# Initial push to the default branch. Take the full history of that branch as "newly pushed".
@push_commits = project.repository.commits(newrev)
+ # Default branch is protected by default
+ project.protected_branches.create({ name: project.default_branch })
else
# Use the pushed commits that aren't reachable by the default branch
# as a heuristic. This may include more commits than are actually pushed, but
# that shouldn't matter because we check for existing cross-references later.
@push_commits = project.repository.commits_between(project.default_branch, newrev)
end
-
process_commit_messages(ref)
-
- elsif push_remove_branch_branch?(ref, newrev)
- @push_commits = []
+ elsif push_to_existing_branch?(ref, oldrev)
+ # Collect data for this git push
+ @push_commits = project.repository.commits_between(oldrev, newrev)
+ project.update_merge_requests(oldrev, newrev, ref, @user)
+ process_commit_messages(ref)
end
@push_data = post_receive_data(oldrev, newrev, ref)
@@ -75,7 +75,7 @@ class GitPushService
# Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
# close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
- def process_commit_messages ref
+ def process_commit_messages(ref)
is_default_branch = is_default_branch?(ref)
@push_commits.each do |commit|
@@ -150,49 +150,40 @@ class GitPushService
# will be passed as post receive hook data.
#
push_commits_limited.each do |commit|
- data[:commits] << {
- id: commit.id,
- message: commit.safe_message,
- timestamp: commit.committed_date.xmlschema,
- url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{commit.id}",
- author: {
- name: commit.author_name,
- email: commit.author_email
- }
- }
+ data[:commits] << commit.hook_attrs(project)
end
data
end
- def push_to_existing_branch? ref, oldrev
+ def push_to_existing_branch?(ref, oldrev)
ref_parts = ref.split('/')
# Return if this is not a push to a branch (e.g. new commits)
- ref_parts[1] =~ /heads/ && oldrev != "0000000000000000000000000000000000000000"
+ ref_parts[1] =~ /heads/ && oldrev != Gitlab::Git::BLANK_SHA
end
- def push_to_new_branch? ref, oldrev
+ def push_to_new_branch?(ref, oldrev)
ref_parts = ref.split('/')
- ref_parts[1] =~ /heads/ && oldrev == "0000000000000000000000000000000000000000"
+ ref_parts[1] =~ /heads/ && oldrev == Gitlab::Git::BLANK_SHA
end
- def push_remove_branch? ref, newrev
+ def push_remove_branch?(ref, newrev)
ref_parts = ref.split('/')
- ref_parts[1] =~ /heads/ && newrev == "0000000000000000000000000000000000000000"
+ ref_parts[1] =~ /heads/ && newrev == Gitlab::Git::BLANK_SHA
end
- def push_to_branch? ref
+ def push_to_branch?(ref)
ref =~ /refs\/heads/
end
- def is_default_branch? ref
+ def is_default_branch?(ref)
ref == "refs/heads/#{project.default_branch}"
end
- def commit_user commit
+ def commit_user(commit)
User.find_for_commit(commit.author_email, commit.author_name) || user
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
new file mode 100644
index 00000000000..e3371ec3c1b
--- /dev/null
+++ b/app/services/issuable_base_service.rb
@@ -0,0 +1,13 @@
+class IssuableBaseService < BaseService
+ private
+
+ def create_assignee_note(issuable)
+ Note.create_assignee_change_note(
+ issuable, issuable.project, current_user, issuable.assignee)
+ end
+
+ def create_milestone_note(issuable)
+ Note.create_milestone_change_note(
+ issuable, issuable.project, current_user, issuable.milestone)
+ end
+end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 71b9ffc3489..755c0ef45a8 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -1,21 +1,13 @@
module Issues
- class BaseService < ::BaseService
+ class BaseService < ::IssuableBaseService
private
- def create_assignee_note(issue)
- Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
- end
-
def execute_hooks(issue, action = 'open')
- issue_data = issue.to_hook_data
+ issue_data = issue.to_hook_data(current_user)
issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue.project.execute_hooks(issue_data, :issue_hooks)
end
-
- def create_milestone_note(issue)
- Note.create_milestone_change_note(issue, issue.project, current_user, issue.milestone)
- end
end
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index a0e57144435..0ee9635ed99 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -8,9 +8,14 @@ module Issues
Issues::ReopenService.new(project, current_user, {}).execute(issue)
when 'close'
Issues::CloseService.new(project, current_user, {}).execute(issue)
+ when 'task_check'
+ issue.update_nth_task(params[:task_num].to_i, true)
+ when 'task_uncheck'
+ issue.update_nth_task(params[:task_num].to_i, false)
end
- if params.present? && issue.update_attributes(params.except(:state_event))
+ if params.present? && issue.update_attributes(params.except(:state_event,
+ :task_num))
issue.reset_events_cache
if issue.previous_changes.include?('milestone_id')
diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb
index 9bc50d3d16c..700a21ca011 100644
--- a/app/services/merge_requests/base_merge_service.rb
+++ b/app/services/merge_requests/base_merge_service.rb
@@ -13,7 +13,8 @@ module MergeRequests
def execute_project_hooks(merge_request)
if merge_request.project
- merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
+ hook_data = merge_request.to_hook_data(current_user)
+ merge_request.project.execute_hooks(hook_data, :merge_request_hooks)
end
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 2907f3587da..7f3421b8e4b 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -1,11 +1,5 @@
module MergeRequests
- class BaseService < ::BaseService
-
- private
-
- def create_assignee_note(merge_request)
- Note.create_assignee_change_note(merge_request, merge_request.project, current_user, merge_request.assignee)
- end
+ class BaseService < ::IssuableBaseService
def create_note(merge_request)
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
@@ -13,12 +7,9 @@ module MergeRequests
def execute_hooks(merge_request)
if merge_request.project
- merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
+ hook_data = merge_request.to_hook_data(current_user)
+ merge_request.project.execute_hooks(hook_data, :merge_request_hooks)
end
end
-
- def create_milestone_note(merge_request)
- Note.create_milestone_change_note(merge_request, merge_request.project, current_user, merge_request.milestone)
- end
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
new file mode 100644
index 00000000000..74448998ddd
--- /dev/null
+++ b/app/services/merge_requests/refresh_service.rb
@@ -0,0 +1,67 @@
+module MergeRequests
+ class RefreshService < MergeRequests::BaseService
+ def execute(oldrev, newrev, ref)
+ return true unless ref =~ /heads/
+
+ @branch_name = ref.gsub("refs/heads/", "")
+ @fork_merge_requests = @project.fork_merge_requests.opened
+ @commits = @project.repository.commits_between(oldrev, newrev)
+
+ close_merge_requests
+ reload_merge_requests
+ comment_mr_with_commits
+
+ true
+ end
+
+ private
+
+ # Collect open merge requests that target same branch we push into
+ # and close if push to master include last commit from merge request
+ # We need this to close(as merged) merge requests that were merged into
+ # target branch manually
+ def close_merge_requests
+ commit_ids = @commits.map(&:id)
+ merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a
+ merge_requests = merge_requests.select(&:last_commit)
+
+ merge_requests = merge_requests.select do |merge_request|
+ commit_ids.include?(merge_request.last_commit.id)
+ end
+
+
+ merge_requests.uniq.select(&:source_project).each do |merge_request|
+ MergeRequests::MergeService.new.execute(merge_request, @current_user, nil)
+ end
+ end
+
+ # Refresh merge request diff if we push to source or target branch of merge request
+ # Note: we should update merge requests from forks too
+ def reload_merge_requests
+ merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a
+ merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a
+ merge_requests = filter_merge_requests(merge_requests)
+
+ merge_requests.each do |merge_request|
+ merge_request.reload_code
+ merge_request.mark_as_unchecked
+ end
+ end
+
+ # Add comment about pushing new commits to merge requests
+ def comment_mr_with_commits
+ merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
+ merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
+ merge_requests = filter_merge_requests(merge_requests)
+
+ merge_requests.each do |merge_request|
+ Note.create_new_commits_note(merge_request, merge_request.project,
+ @current_user, @commits)
+ end
+ end
+
+ def filter_merge_requests(merge_requests)
+ merge_requests.uniq.select(&:source_project)
+ end
+ end
+end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 6e416a0080c..fc26619cd17 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -17,9 +17,15 @@ module MergeRequests
MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request)
when 'close'
MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request)
+ when 'task_check'
+ merge_request.update_nth_task(params[:task_num].to_i, true)
+ when 'task_uncheck'
+ merge_request.update_nth_task(params[:task_num].to_i, false)
end
- if params.present? && merge_request.update_attributes(params.except(:state_event))
+ if params.present? && merge_request.update_attributes(
+ params.except(:state_event, :task_num)
+ )
merge_request.reset_events_cache
if merge_request.previous_changes.include?('milestone_id')
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index fe39f83b400..c9a1574b84e 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -119,11 +119,12 @@ class NotificationService
# ignore gitlab service messages
return true if note.note =~ /\A_Status changed to closed_/
- return true if note.note =~ /\A_mentioned in / && note.system == true
+ return true if note.cross_reference? && note.system == true
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
target = note.noteable
+
if target.respond_to?(:participants)
recipients = target.participants
else
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index a6b68749a71..44e494525b3 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -18,7 +18,7 @@ class SystemHooksService
def build_event_data(model, event)
data = {
event_name: build_event_name(model, event),
- created_at: model.created_at
+ created_at: model.created_at.xmlschema
}
case model
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index b122b6c8658..29a55b36ca5 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -26,6 +26,10 @@ class AttachmentUploader < CarrierWave::Uploader::Base
Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}"
end
+ def url
+ Gitlab.config.gitlab.relative_url_root + super unless super.nil?
+ end
+
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index c79d1280dd9..8db2b2a709c 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -8,7 +8,7 @@
.panel-body
- if @sidekiq_processes.empty?
%h4.cred
- %i.icon-warning-sign
+ %i.fa.fa-exclamation-triangle
There are no running sidekiq processes. Please restart GitLab
- else
%table.table
@@ -25,18 +25,18 @@
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- data = process.strip.split(' ')
%tr
- %td= Settings.gitlab.user
+ %td= gitlab_config.user
- 5.times do
%td= data.shift
%td= data.join(' ')
.clearfix
%p
- %i.icon-exclamation-sign
+ %i.fa.fa-exclamation-circle
If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
%p
- %i.icon-exclamation-sign
- If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{Settings.gitlab.user} -f sidekiq) and restart GitLab.
+ %i.fa.fa-exclamation-circle
+ If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab.
diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
index c58ca2c9a33..7b483ee6556 100644
--- a/app/views/admin/broadcast_messages/index.html.haml
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -3,7 +3,7 @@
%p.light
Broadcast messages are displayed for every user and can be used to notify users about scheduled maintenance, recent upgrades and more.
.broadcast-message-preview
- %i.icon-bullhorn
+ %i.fa.fa-bullhorn
%span Your message here
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal'} do |f|
@@ -53,7 +53,7 @@
#{broadcast_message.ends_at.to_s(:short)}
&nbsp;
= link_to [:admin, broadcast_message], method: :delete, remote: true, class: 'remove-row btn btn-tiny' do
- %i.icon-remove.cred
+ %i.fa.fa-times.cred
.message= broadcast_message.message
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 31990ee5932..f4d7e25fd74 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -2,39 +2,20 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
- .form-group.group_name_holder
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: "Example Group", class: "form-control"
- .form-group.group-description-holder
- = f.label :description, "Details", class: 'control-label'
- .col-sm-10
- = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
+ = render 'shared/group_form', f: f
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10
- %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
- %i.icon-paper-clip
- %span Choose File ...
- &nbsp;
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-group-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ = render 'shared/choose_group_avatar_button', f: f
- if @group.new_record?
.form-group
.col-sm-2
.col-sm-10
.bs-callout.bs-callout-info
- %ul
- %li A group is a collection of several projects
- %li Groups are private by default
- %li Members of a group may only view projects they have permission to access
- %li Group project URLs are prefixed with the group namespace
- %li Existing projects may be moved into a group
+ = render 'shared/group_tips'
.form-actions
= f.submit 'Create group', class: "btn btn-create"
= link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 9a0d5967927..1d7fef43184 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -10,7 +10,7 @@
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
.form-group
= text_field_tag :name, params[:name], class: "form-control input-mn-300"
- = submit_tag "Search", class: "btn submit btn-primary"
+ = button_tag "Search", class: "btn submit btn-primary"
%hr
@@ -24,7 +24,7 @@
%h4
= link_to [:admin, group] do
- %i.icon-folder-close
+ %i.fa.fa-folder
= group.name
&rarr;
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index d59d2a23179..8057de38805 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -2,7 +2,7 @@
Group: #{@group.name}
= link_to edit_admin_group_path(@group), class: "btn pull-right" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
%hr
.row
@@ -64,7 +64,7 @@
%div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
- = submit_tag 'Add users into group', class: "btn btn-create"
+ = button_tag 'Add users into group', class: "btn btn-create"
.panel.panel-default
.panel-heading
%h3.panel-title
@@ -74,13 +74,13 @@
%ul.well-list.group-users-list
- @members.each do |member|
- user = member.user
- %li{class: dom_class(user)}
+ %li{class: dom_class(member), id: dom_id(user)}
.list-item-name
%strong
= link_to user.name, admin_user_path(user)
%span.pull-right.light
= member.human_access
- = link_to group_group_members_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
- %i.icon-minus.icon-white
+ = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ %i.fa.fa-minus.fa-inverse
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index cce8aeb02c7..384c6ee9af5 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -1,68 +1,25 @@
+- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
+ Gitlab::ProductionLogger, Gitlab::SidekiqLogger]
%ul.nav.nav-tabs.log-tabs
- %li.active
- = link_to "githost.log", "#githost", 'data-toggle' => 'tab'
- %li
- = link_to "application.log", "#application", 'data-toggle' => 'tab'
- %li
- = link_to "production.log", "#production", 'data-toggle' => 'tab'
- %li
- = link_to "sidekiq.log", "#sidekiq", 'data-toggle' => 'tab'
-
+ - loggers.each do |klass|
+ %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
+ = link_to klass::file_name, "##{klass::file_name_noext}",
+ 'data-toggle' => 'tab'
%p.light To prevent performance issues admin logs output the last 2000 lines
.tab-content
- .tab-pane.active#githost
- .file-holder#README
- .file-title
- %i.icon-file
- githost.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.icon-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::GitLogger.read_latest.each do |line|
- %li
- %p= line
- .tab-pane#application
- .file-holder#README
- .file-title
- %i.icon-file
- application.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.icon-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::AppLogger.read_latest.each do |line|
- %li
- %p= line
- .tab-pane#production
- .file-holder#README
- .file-title
- %i.icon-file
- production.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.icon-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::Logger.read_latest_for('production.log').each do |line|
- %li
- %p= line
- .tab-pane#sidekiq
- .file-holder#README
- .file-title
- %i.icon-file
- sidekiq.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.icon-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::Logger.read_latest_for('sidekiq.log').each do |line|
- %li
- %p= line
+ - loggers.each do |klass|
+ .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
+ id: klass::file_name_noext }
+ .file-holder#README
+ .file-title
+ %i.fa.fa-file
+ = klass::file_name
+ .pull-right
+ = link_to '#', class: 'log-bottom' do
+ %i.fa.fa-arrow-down
+ Scroll down
+ .file-content.logs
+ %ol
+ - klass.read_latest.each do |line|
+ %li
+ %p= line
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 5ca6090f8d3..2cd6b12be7f 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -35,7 +35,7 @@
= label
%hr
= hidden_field_tag :sort, params[:sort]
- = submit_tag "Search", class: "btn submit btn-primary"
+ = button_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_projects_path, class: "btn btn-cancel"
.col-md-9
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 8413f0cb7f9..6d536199851 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
Project: #{@project.name_with_namespace}
= link_to edit_project_path(@project), class: "btn pull-right" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
%hr
.row
@@ -98,7 +98,7 @@
group members (#{@group.group_members.count})
.pull-right
= link_to admin_group_path(@group), class: 'btn btn-small' do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
%ul.well-list
- @group_members.each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false
@@ -112,7 +112,7 @@
(#{@project.users.count})
.pull-right
= link_to project_team_index_path(@project), class: "btn btn-tiny" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Manage Access
%ul.well-list.team_members
- @project_members.each do |project_member|
@@ -127,6 +127,6 @@
- else
%span.light= project_member.human_access
= link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-small btn-remove" do
- %i.icon-remove
+ %i.fa.fa-times
.panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index b8a18224443..92c619738a2 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -22,8 +22,8 @@
= form_tag admin_users_path, method: :get, class: 'form-inline' do
.form-group
= search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control'
- = button_tag type: 'submit', class: 'btn btn-primary' do
- %i.icon-search
+ = button_tag class: 'btn btn-primary' do
+ %i.fa.fa-search
%hr
= link_to 'Reset', admin_users_path, class: "btn btn-cancel"
@@ -32,15 +32,35 @@
.panel-heading
Users (#{@users.total_count})
.panel-head-actions
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %span.light sort:
+ - if @sort.present?
+ = @sort.humanize
+ - else
+ Name
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to admin_users_path(sort: nil) do
+ Name
+ = link_to admin_users_path(sort: 'recent_sign_in') do
+ Recent sign in
+ = link_to admin_users_path(sort: 'oldest_sign_in') do
+ Oldest sign in
+ = link_to admin_users_path(sort: 'recently_created') do
+ Recently created
+ = link_to admin_users_path(sort: 'late_created') do
+ Late created
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
%ul.well-list
- @users.each do |user|
%li
.list-item-name
- if user.blocked?
- %i.icon-lock.cred
+ %i.fa.fa-lock.cred
- else
- %i.icon-user.cgreen
+ %i.fa.fa-user.cgreen
= link_to user.name, [:admin, user]
- if user.admin?
%strong.cred (Admin)
@@ -48,7 +68,7 @@
%span.cred It's you!
.pull-right
%span.light
- %i.icon-envelope
+ %i.fa.fa-envelope
= mail_to user.email, user.email, class: 'light'
&nbsp;
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-small"
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index a172d12edb1..211d77d5185 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -8,7 +8,7 @@
.pull-right
= link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
%hr
%ul.nav.nav-tabs
@@ -45,7 +45,7 @@
%span.light Secondary email:
%strong= email.email
= link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-tiny btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do
- %i.icon-remove
+ %i.fa.fa-times
%li
%span.light Can create groups:
@@ -86,6 +86,11 @@
- else
never
+ %li
+ %span.light Sign-in count:
+ %strong
+ = @user.sign_in_count
+
- if @user.ldap_user?
%li
%span.light LDAP uid:
@@ -171,8 +176,8 @@
.pull-right
%span.light= user_group.human_access
- unless user_group.owner?
- = link_to group_users_group_path(group, user_group), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
- %i.icon-remove.icon-white
+ = link_to group_group_member_path(group, user_group), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ %i.fa.fa-times.fa-inverse
- else
.nothing-here-block This user has no groups.
@@ -211,4 +216,4 @@
- if tm.respond_to? :project
= link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do
- %i.icon-remove
+ %i.fa.fa-times
diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml
index 9bcc77b8d8e..5460cf56f22 100644
--- a/app/views/dashboard/_groups.html.haml
+++ b/app/views/dashboard/_groups.html.haml
@@ -3,7 +3,7 @@
= search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- if current_user.can_create_group?
= link_to new_group_path, class: "btn btn-new pull-right" do
- %i.icon-plus
+ %i.fa.fa-plus
New group
%ul.well-list.dash-list
- groups.each do |group|
@@ -13,7 +13,7 @@
%span.group-name.filter-title
= truncate(group.name, length: 35)
%span.arrow
- %i.icon-angle-right
+ %i.fa.fa-angle-right
- if groups.blank?
%li
.nothing-here-block You have no groups yet.
diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml
index e326bee53ab..89ed5102754 100644
--- a/app/views/dashboard/_project.html.haml
+++ b/app/views/dashboard/_project.html.haml
@@ -9,4 +9,4 @@
%span.project-name.filter-title
= project.name
%span.arrow
- %i.icon-angle-right
+ %i.fa.fa-angle-right
diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml
index 0cc253a8dd1..3598425777f 100644
--- a/app/views/dashboard/_projects.html.haml
+++ b/app/views/dashboard/_projects.html.haml
@@ -3,7 +3,7 @@
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- if current_user.can_create_project?
= link_to new_project_path, class: "btn btn-new pull-right" do
- %i.icon-plus
+ %i.fa.fa-plus
New project
%ul.well-list.dash-list
@@ -21,4 +21,4 @@
.pull-right
= link_to projects_dashboard_path do
Show all
- %i.icon-angle-right
+ %i.fa.fa-angle-right
diff --git a/app/views/dashboard/_projects_filter.html.haml b/app/views/dashboard/_projects_filter.html.haml
index e4fa2d59e8a..b65e882e693 100644
--- a/app/views/dashboard/_projects_filter.html.haml
+++ b/app/views/dashboard/_projects_filter.html.haml
@@ -37,7 +37,7 @@
- @groups.each do |group|
%li{ class: (group.name == params[:group]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(group: group.name) do
- %i.icon-folder-close-alt
+ %i.fa.fa-folder-o
= group.name
%small.pull-right
= group.projects.count
@@ -51,5 +51,5 @@
- @tags.each do |tag|
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do
- %i.icon-tag
+ %i.fa.fa-tag
= tag.name
diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml
index fed4b2776ae..add9eb7fa29 100644
--- a/app/views/dashboard/_sidebar.html.haml
+++ b/app/views/dashboard/_sidebar.html.haml
@@ -18,7 +18,7 @@
%span.rss-icon
= link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
%strong
- %i.icon-rss
+ %i.fa.fa-rss
News Feed
%hr
diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml
index ff85e32bc4e..5d133cd8285 100644
--- a/app/views/dashboard/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/_zero_authorized_projects.html.haml
@@ -3,7 +3,7 @@
%hr
%div
.dashboard-intro-icon
- %i.icon-bookmark-empty
+ %i.fa.fa-bookmark-o
%div
%p.slead
You don't have access to any projects right now.
@@ -23,7 +23,7 @@
%hr
%div
.dashboard-intro-icon
- %i.icon-group
+ %i.fa.fa-users
%div
%p.slead
You can create a group for several dependent projects.
@@ -37,7 +37,7 @@
%hr
%div
.dashboard-intro-icon
- %i.icon-globe
+ %i.fa.fa-globe
%div
%p.slead
There are
@@ -46,5 +46,5 @@
%br
Public projects are an easy way to allow everyone to have read-only access.
.link_holder
- = link_to explore_projects_path, class: "btn btn-new" do
+ = link_to trending_explore_projects_path, class: "btn btn-new" do
Browse public projects »
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index d3ff291eaa8..7c1f1ddbb80 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -7,7 +7,7 @@
.row
.fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.icon-list.icon-2x
+ %i.fa.fa-list.fa-2x
.col-md-3.responsive-side
= render 'shared/filter', entity: 'issue'
.col-md-9
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 7a9ea9f6f90..c96584c7b6b 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -7,7 +7,7 @@
%hr
.row
.fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.icon-list.icon-2x
+ %i.fa.fa-list.fa-2x
.col-md-3.responsive-side
= render 'shared/filter', entity: 'merge_request'
.col-md-9
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
index 2714ebc53de..f124c688be1 100644
--- a/app/views/dashboard/projects.html.haml
+++ b/app/views/dashboard/projects.html.haml
@@ -40,23 +40,23 @@
- if current_user.can_leave_project?(project)
.pull-right
= link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do
- %i.icon-signout
+ %i.fa.fa-sign-out
Leave
- if project.forked_from_project
%small.pull-right
- %i.icon-code-fork
+ %i.fa.fa-code-fork
Forked from:
= link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project)
.project-info
.pull-right
- if project.archived?
%span.label
- %i.icon-archive
+ %i.fa.fa-archive
Archived
- project.tags.each do |tag|
%span.label.label-info
- %i.icon-tag
+ %i.fa.fa-tag
= tag.name
- if project.description.present?
%p= truncate project.description, length: 100
diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml
index 6a08b1aa640..10951af6a09 100644
--- a/app/views/dashboard/show.html.haml
+++ b/app/views/dashboard/show.html.haml
@@ -6,7 +6,7 @@
= render 'sidebar'
.fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.icon-list.icon-2x
+ %i.fa.fa-list.fa-2x
- else
= render "zero_authorized_projects"
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 1326cc0aac9..f6cbf9b82ba 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -6,8 +6,8 @@
.devise-errors
= devise_error_messages!
= f.hidden_field :reset_password_token
- %div
- = f.password_field :password, class: "form-control top", placeholder: "New password", required: true
+ .form-group#password-strength
+ = f.password_field :password, class: "form-control top", id: "user_password_recover", placeholder: "New password", required: true
%div
= f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true
.clearfix.append-bottom-10
diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml
index d6a952f3dc5..123de881f59 100644
--- a/app/views/devise/registrations/new.html.haml
+++ b/app/views/devise/registrations/new.html.haml
@@ -11,8 +11,8 @@
= f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
%div
= f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
- %div
- = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true
+ .form-group#password-strength
+ = f.password_field :password, class: "form-control middle", id: "user_password_sign_up", placeholder: "Password", required: true
%div
= f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true
%div
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 6c5a878e904..bf8a593c254 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,5 +1,5 @@
-= form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do
+= form_tag(user_omniauth_callback_path(provider), id: 'new_ldap_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
%br/
- = submit_tag "LDAP Sign in", class: "btn-save btn"
+ = button_tag "LDAP Sign in", class: "btn-save btn"
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index b70b0d66172..ca7e9570b43 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -2,22 +2,24 @@
.login-heading
%h3 Sign in
.login-body
- - if ldap_enabled? && gitlab_config.signin_enabled
+ - if ldap_enabled?
%ul.nav.nav-tabs
- %li.active
- = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab'
- %li
- = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
+ - @ldap_servers.each_with_index do |server, i|
+ %li{class: (:active if i.zero?)}
+ = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
+ - if gitlab_config.signin_enabled
+ %li
+ = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
.tab-content
- %div#tab-ldap.tab-pane.active
- = render partial: 'devise/sessions/new_ldap'
- %div#tab-signin.tab-pane
- = render partial: 'devise/sessions/new_base'
+ - @ldap_servers.each_with_index do |server, i|
+ %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
+ = render 'devise/sessions/new_ldap', provider: server['provider_name']
+ - if gitlab_config.signin_enabled
+ %div#tab-signin.tab-pane
+ = render 'devise/sessions/new_base'
- - elsif ldap_enabled?
- = render partial: 'devise/sessions/new_ldap'
- elsif gitlab_config.signin_enabled
- = render partial: 'devise/sessions/new_base'
+ = render 'devise/sessions/new_base'
- else
%div
No authentication methods configured.
@@ -36,7 +38,6 @@
%span.light Did not receive confirmation email?
= link_to "Send again", new_confirmation_path(resource_name)
-
- if extra_config.has_key?('sign_in_text')
%hr
= markdown(extra_config.sign_in_text)
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index 0e03e116e7d..f0c34def145 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,5 +1,5 @@
%li.commit
.commit-row-title
- = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: ''
+ = link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit_short_id", alt: ''
&nbsp;
= gfm event_commit_title(commit[:message]), project
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index 17228c430ca..2b63519edac 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -2,7 +2,7 @@
- event.commits.first(15).each do |commit|
%p
%strong= commit[:author][:name]
- = link_to "(##{commit[:id][0...8]})", project_commit_path(event.project, id: commit[:id])
+ = link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id])
%i
at
= commit[:timestamp].to_time.to_s(:short)
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index ad2afbce14c..6ec8e54fba5 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -9,7 +9,7 @@
.event-body
.event-note
.md
- %i.icon-comment-alt.event-note-icon
+ %i.fa.fa-comment-o.event-note-icon
= event_note(event.target.note)
- note = event.target
- if note.attachment.url
@@ -18,5 +18,5 @@
= image_tag note.attachment.secure_url, class: 'note-image-attach'
- else
= link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
- %i.icon-paper-clip
+ %i.fa.fa-paperclip
= note.attachment_identifier
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 1bca64c7d50..b912b5e092f 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -22,4 +22,4 @@
- if event.commits_count > 2
%span ... and #{event.commits_count - 2} more commits.
= link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do
- %strong Compare &rarr; #{event.commit_from[0..7]}...#{event.commit_to[0..7]}
+ %strong Compare &rarr; #{truncate_sha(event.commit_from)}...#{truncate_sha(event.commit_to)}
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 80ddd5c1bde..709d062df83 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -4,7 +4,7 @@
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search"
.form-group
- = submit_tag 'Search', class: "btn btn-primary wide"
+ = button_tag 'Search', class: "btn btn-primary wide"
.pull-right
.dropdown.inline
@@ -36,7 +36,7 @@
.clearfix
%h4
= link_to group_path(id: group.path) do
- %i.icon-group
+ %i.fa.fa-users
= group.name
.clearfix
%p
diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml
index fd5aacbfdb4..ffbddbae4d6 100644
--- a/app/views/explore/projects/_project.html.haml
+++ b/app/views/explore/projects/_project.html.haml
@@ -6,6 +6,7 @@
- if current_page?(starred_explore_projects_path)
%strong.pull-right
+ %i.fa.fa-star
= pluralize project.star_count, 'star'
.project-info
@@ -21,5 +22,5 @@
&middot;
= link_to pluralize(project.repository.tag_names.count, 'tag'), project_tags_path(project)
- else
- %i.icon-warning-sign
+ %i.fa.fa-exclamation-triangle
Empty repository
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index c8bf78385e8..f797c4e3830 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -4,7 +4,7 @@
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
.form-group
- = submit_tag 'Search', class: "btn btn-primary wide"
+ = button_tag 'Search', class: "btn btn-primary wide"
.pull-right
.dropdown.inline
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index 9c793d4050c..420f0693756 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -1,6 +1,6 @@
.explore-trending-block
%p.lead
- %i.icon-comments-alt
+ %i.fa.fa-star
See most starred projects
%hr
.public-projects
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 18bb1ac0ba4..9cad9238933 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -1,6 +1,6 @@
.explore-trending-block
%p.lead
- %i.icon-comments-alt
+ %i.fa.fa-comments-o
See most discussed projects for last month
%hr
.public-projects
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 2ebff21b819..2c65b3049e3 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -4,7 +4,7 @@
- if can? current_user, :create_projects, @group
.panel-head-actions
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do
- %i.icon-plus
+ %i.fa.fa-plus
New project
%ul.well-list
- if projects.blank?
@@ -18,4 +18,4 @@
%span.project-name
= project.name
%span.arrow
- %i.icon-angle-right
+ %i.fa.fa-angle-right
diff --git a/app/views/groups/_settings_nav.html.haml b/app/views/groups/_settings_nav.html.haml
index 32eae31a47d..ec1fb4a2c00 100644
--- a/app/views/groups/_settings_nav.html.haml
+++ b/app/views/groups/_settings_nav.html.haml
@@ -1,10 +1,10 @@
%ul.nav.nav-pills.nav-stacked.nav-stacked-menu
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group) do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Group
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group) do
- %i.icon-folder-close
+ %i.fa.fa-folder
Projects
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 97f22df044f..eb24fd65d9e 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -11,16 +11,7 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
- .form-group
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left"
-
- .form-group.group-description-holder
- = f.label :description, "Details", class: 'control-label'
- .col-sm-10
- = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
+ = render 'shared/group_form', f: f
.form-group
.col-sm-2
@@ -31,13 +22,7 @@
You can change your group avatar here
- else
You can upload a group avatar here
- %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
- %i.icon-paper-clip
- %span Choose File ...
- &nbsp;
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-group-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ = render 'shared/choose_group_avatar_button', f: f
- if @group.avatar?
%hr
= link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index 099e50a384e..d05016d9c3f 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -15,14 +15,14 @@
- if show_controls
- if can?(current_user, :modify, member)
= link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
- if can?(current_user, :destroy, member)
- if current_user == member.user
= link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
- %i.icon-minus.icon-white
+ %i.fa.fa-minus.fa-inverse
- else
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
- %i.icon-minus.icon-white
+ %i.fa.fa-minus.fa-inverse
.edit-member.hide.js-toggle-content
= form_for [@group, member], remote: true do |f|
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 0152ae86833..1932ba2f644 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -11,7 +11,7 @@
.row
.fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.icon-list.icon-2x
+ %i.fa.fa-list.fa-2x
.col-md-3.responsive-side
= render 'shared/filter', entity: 'issue'
.col-md-9
diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml
index ebf407d4ef1..d2ebcdab7e1 100644
--- a/app/views/groups/members.html.haml
+++ b/app/views/groups/members.html.haml
@@ -13,13 +13,13 @@
= form_tag members_group_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 input-mn-300' }
- = submit_tag 'Search', class: 'btn'
+ = button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:manage_group, @group)
.pull-right
= link_to '#', class: 'btn btn-new js-toggle-button' do
Add members
- %i.icon-chevron-down
+ %i.fa.fa-chevron-down
.js-toggle-content.hide.new-group-member-holder
= render "new_group_member"
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 71d346d0469..86d5acdaa32 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -10,7 +10,7 @@
%hr
.row
.fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.icon-list.icon-2x
+ %i.fa.fa-list.fa-2x
.col-md-3.responsive-side
= render 'shared/filter', entity: 'merge_request'
.col-md-9
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 54e901173f0..2727525f070 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -11,7 +11,7 @@
.row
.fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.icon-list.icon-2x
+ %i.fa.fa-list.fa-2x
.col-md-3.responsive-side
= render 'groups/filter', entity: 'milestone'
.col-md-9
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index cdc087f949a..6e17cdaef6f 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -2,37 +2,18 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
- .form-group
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true
- .form-group.group-description-holder
- = f.label :description, "Details", class: 'control-label'
- .col-sm-10
- = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2
+ = render 'shared/group_form', f: f, autofocus: true
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10
- %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
- %i.icon-paper-clip
- %span Choose File ...
- &nbsp;
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-group-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ = render 'shared/choose_group_avatar_button', f: f
.form-group
.col-sm-2
.col-sm-10
- %ul
- %li A group is a collection of several projects
- %li Groups are private by default
- %li Members of a group may only view projects they have permission to access
- %li Group project URLs are prefixed with the group namespace
- %li Existing projects may be moved into a group
+ = render 'shared/group_tips'
.form-actions
= f.submit 'Create group', class: "btn btn-create", tabindex: 3
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index a5e752d776f..65a66355c56 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -9,7 +9,7 @@
- if can? current_user, :manage_group, @group
.panel-head-actions
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do
- %i.icon-plus
+ %i.fa.fa-plus
New Project
%ul.well-list
- @projects.each do |project|
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 6db393c882c..d876e87852c 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -24,13 +24,13 @@
= @group.name
- if @group.description.present?
%p
- = auto_link @group.description, link: :urls
+ = escaped_autolink(@group.description)
= render "projects", projects: @projects
- if current_user
.prepend-top-20
= link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do
%strong
- %i.icon-rss
+ %i.fa.fa-rss
News Feed
%hr
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 467f003b333..7b21ca30d8c 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -3,9 +3,9 @@
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
- %h4
+ %h4
Keyboard Shortcuts
- %small
+ %small
= link_to '(Show all)', '#', class: 'js-more-help-button'
.modal-body.shortcuts-cheatsheet
.col-lg-4
@@ -15,11 +15,11 @@
%th
%th Global Shortcuts
%tr
- %td.shortcut
+ %td.shortcut
.key s
%td Focus Search
%tr
- %td.shortcut
+ %td.shortcut
.key ?
%td Show this dialog
%tbody
@@ -29,15 +29,15 @@
%tr
%td.shortcut
.key
- %i.icon-arrow-up
+ %i.fa.fa-arrow-up
%td Move selection up
%tr
%td.shortcut
.key
- %i.icon-arrow-down
+ %i.fa.fa-arrow-down
%td Move selection down
%tr
- %td.shortcut
+ %td.shortcut
.key enter
%td Open Selection
@@ -48,25 +48,25 @@
%th
%th Global Dashboard
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key a
%td
Go to the activity feed
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key p
%td
Go to projects
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key i
%td
Go to issues
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key m
%td
@@ -76,43 +76,43 @@
%th
%th Project
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key p
%td
Go to the project's activity feed
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key f
%td
Go to files
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key c
%td
Go to commits
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key n
%td
Go to network graph
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key g
%td
Go to graphs
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key i
%td
Go to issues
%tr
- %td.shortcut
+ %td.shortcut
.key g
.key m
%td
@@ -132,28 +132,28 @@
%tr
%td.shortcut
.key
- %i.icon-arrow-left
+ %i.fa.fa-arrow-left
\/
.key h
%td Scroll left
%tr
%td.shortcut
.key
- %i.icon-arrow-right
+ %i.fa.fa-arrow-right
\/
.key l
%td Scroll right
%tr
%td.shortcut
.key
- %i.icon-arrow-up
+ %i.fa.fa-arrow-up
\/
.key k
%td Scroll up
%tr
%td.shortcut
.key
- %i.icon-arrow-down
+ %i.fa.fa-arrow-down
\/
.key j
%td Scroll down
@@ -161,7 +161,7 @@
%td.shortcut
.key
shift
- %i.icon-arrow-up
+ %i.fa.fa-arrow-up
\/
.key
shift k
@@ -170,7 +170,7 @@
%td.shortcut
.key
shift
- %i.icon-arrow-down
+ %i.fa.fa-arrow-down
\/
.key
shift j
@@ -180,11 +180,11 @@
%th
%th Issues
%tr
- %td.shortcut
+ %td.shortcut
.key a
%td Change assignee
%tr
- %td.shortcut
+ %td.shortcut
.key m
%td Change milestone
%tbody{ class: 'hidden-shortcut merge_reuests', style: 'display:none' }
@@ -192,11 +192,11 @@
%th
%th Merge Requests
%tr
- %td.shortcut
+ %td.shortcut
.key a
%td Change assignee
%tr
- %td.shortcut
+ %td.shortcut
.key m
%td Change milestone
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index 903e093e5fc..7b8193abfdf 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -14,7 +14,7 @@
%br
Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises.
%br
- Read more about GitLab at #{link_to "www.gitlab.com", "https://www.gitlab.com/", target: "_blank"}.
+ Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}.
%hr
@@ -34,7 +34,7 @@
%ul.well-list
%li
See our website for
- = link_to "getting help", "https://www.gitlab.com/getting-help/"
+ = link_to 'getting help', promo_url + '/getting-help/'
%li
Use the
= link_to 'search bar', '#', onclick: 'Shortcuts.focusSearch(event)'
diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml
index 5794e3de338..e7d477c225e 100644
--- a/app/views/layouts/_broadcast.html.haml
+++ b/app/views/layouts/_broadcast.html.haml
@@ -1,4 +1,4 @@
- if broadcast_message.present?
.broadcast-message{ style: broadcast_styling(broadcast_message) }
- %i.icon-bullhorn
+ %i.fa.fa-bullhorn
= broadcast_message.message
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 0c27f679dee..fa6aecb6661 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -18,8 +18,7 @@
= javascript_include_tag "application"
= csrf_meta_tags
= include_gon
- :erb
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ %meta{name: 'viewport', content: 'width=device-width, initial-scale=1.0'}
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
index 7c727aca785..5dcaee2fa02 100644
--- a/app/views/layouts/_head_panel.html.haml
+++ b/app/views/layouts/_head_panel.html.haml
@@ -10,7 +10,7 @@
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
%span.sr-only Toggle navigation
- %i.icon-reorder
+ %i.fa.fa-bars
.navbar-collapse.collapse
%ul.nav.navbar-nav
@@ -18,31 +18,31 @@
= render "layouts/search"
%li.visible-sm.visible-xs
= link_to search_path, title: "Search", class: 'has_bottom_tooltip', 'data-original-title' => 'Search area' do
- %i.icon-search
+ %i.fa.fa-search
%li
= link_to help_path, title: 'Help', class: 'has_bottom_tooltip',
'data-original-title' => 'Help' do
- %i.icon-question-sign
+ %i.fa.fa-question-circle
%li
= link_to explore_root_path, title: "Explore", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
- %i.icon-globe
+ %i.fa.fa-globe
%li
= link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'My snippets' do
- %i.icon-paste
+ %i.fa.fa-clipboard
- if current_user.is_admin?
%li
= link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do
- %i.icon-cogs
+ %i.fa.fa-cogs
- if current_user.can_create_project?
%li
= link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do
- %i.icon-plus
+ %i.fa.fa-plus
%li
= link_to profile_path, title: "Profile settings", class: 'has_bottom_tooltip', 'data-original-title' => 'Profile settings"' do
- %i.icon-user
+ %i.fa.fa-user
%li
= link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do
- %i.icon-signout
+ %i.fa.fa-sign-out
%li.hidden-xs
= link_to current_user, class: "profile-pic", id: 'profile-pic' do
= image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
index 945c66e2adf..9bfc14d16c1 100644
--- a/app/views/layouts/_public_head_panel.html.haml
+++ b/app/views/layouts/_public_head_panel.html.haml
@@ -10,7 +10,7 @@
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
%span.sr-only Toggle navigation
- %i.icon-reorder
+ %i.fa.fa-bars
.pull-right.hidden-xs
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new'
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 5ab82122ad7..2460a6a014d 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -8,7 +8,7 @@
- if @snippet || @snippets
= hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref
- = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test'
+ = button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
:javascript
diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml
index deb0822d8f2..63b0cbbd205 100644
--- a/app/views/notify/new_ssh_key_email.html.haml
+++ b/app/views/notify/new_ssh_key_email.html.haml
@@ -6,5 +6,5 @@
title:
%code= @key.title
%p
- If this key was added in error, you can remove it here:
+ If this key was added in error, you can remove it under
= link_to "SSH Keys", profile_keys_url
diff --git a/app/views/notify/new_ssh_key_email.text.erb b/app/views/notify/new_ssh_key_email.text.erb
index 5f0080c2b76..05b551c89a0 100644
--- a/app/views/notify/new_ssh_key_email.text.erb
+++ b/app/views/notify/new_ssh_key_email.text.erb
@@ -2,6 +2,6 @@ Hi <%= @user.name %>!
A new public key was added to your account:
-title.................. <%= @key.title %>
+Title: <%= @key.title %>
-If this key was added in error, you can remove it here: <%= profile_keys_url %>
+If this key was added in error, you can remove it at <%= profile_keys_url %>
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 0358810afdc..3cf50bf0826 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -1,11 +1,11 @@
-%h3 #{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
+%h3 #{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, project_url(@project)}
%h4 Commits:
%ul
- @commits.each do |commit|
%li
- %strong #{commit.short_id}
+ %strong #{link_to commit.short_id, project_commit_url(@project, commit)}
%span by #{commit.author_name}
%pre #{commit.safe_message}
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index 4d7c972a16a..6f5f9eda2c5 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -1,9 +1,9 @@
-#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
+#{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, project_url(@project)}
\
Commits:
- @commits.each do |commit|
- #{commit.short_id} by #{commit.author_name}
+ #{link_to commit.short_id, project_commit_url(@project, commit)} by #{commit.author_name}
#{commit.safe_message}
\- - - - -
\
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index c60e1ca9297..a21dcff41c0 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -52,7 +52,7 @@
&nbsp;
.loading-gif.hide
%p
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Saving new username
%p.light
= user_url(@user)
diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml
index 2efe72b1bb3..e9ffca8faf4 100644
--- a/app/views/profiles/groups/index.html.haml
+++ b/app/views/profiles/groups/index.html.haml
@@ -3,7 +3,7 @@
- if current_user.can_create_group?
%span.pull-right
= link_to new_group_path, class: "btn btn-new" do
- %i.icon-plus
+ %i.fa.fa-plus
New Group
%p.light
Group members have access to all a group's projects
@@ -19,12 +19,12 @@
.pull-right
- if can?(current_user, :manage_group, group)
= link_to edit_group_path(group), class: "btn-small btn btn-grouped" do
- %i.icon-cogs
+ %i.fa.fa-cogs
Settings
- if can?(current_user, :destroy, user_group)
= link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn btn-grouped", title: 'Remove user from group' do
- %i.icon-signout
+ %i.fa.fa-sign-out
Leave
= link_to group, class: 'group-name' do
diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
index 46b7fc72869..c02b47b0ad5 100644
--- a/app/views/profiles/keys/new.html.haml
+++ b/app/views/profiles/keys/new.html.haml
@@ -13,4 +13,4 @@
if( key_mail && key_mail.length > 0 && title.val() == '' ){
$('#key_title').val( key_mail );
}
- }); \ No newline at end of file
+ });
diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml
index 940c553d1ba..2c85d2a9b2b 100644
--- a/app/views/profiles/notifications/_settings.html.haml
+++ b/app/views/profiles/notifications/_settings.html.haml
@@ -1,5 +1,5 @@
%li
- %span.notification-icon-holder
+ %span.notification.fa.fa-holder
- if notification.global?
= notification_icon(@notification)
- else
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index f84de4430cc..a044fad8fa3 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -31,12 +31,12 @@
.clearfix
%hr
- %p
- You can also specify notification level per group or per project
- %br
- By default all projects and groups uses notification level set above
.row.all-notifications
.col-md-6
+ %p
+ You can also specify notification level per group or per project.
+ %br
+ By default all projects and groups uses notification level set above.
%h4 Groups:
%ul.bordered-list
- @group_members.each do |users_group|
@@ -44,6 +44,10 @@
= render 'settings', type: 'group', membership: users_group, notification: notification
.col-md-6
+ %p
+ To specify notification level per project of a group you belong to,
+ %br
+ you need to be a member of the project itself, not only its group.
%h4 Projects:
%ul.bordered-list
- @project_members.each do |project_member|
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 2a7d317aa3e..425200ff523 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -24,7 +24,7 @@
.form-group
= f.label :password, 'New password', class: 'control-label'
.col-sm-10
- = f.password_field :password, required: true, class: 'form-control'
+ = f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile'
.form-group
= f.label :password_confirmation, class: 'control-label'
.col-sm-10
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index aef7348fd20..42d2d0db29c 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -16,7 +16,7 @@
.col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
.form-group
= f.label :password, class: 'control-label'
- .col-sm-10= f.password_field :password, required: true, class: 'form-control'
+ .col-sm-10= f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile'
.form-group
= f.label :password_confirmation, class: 'control-label'
.col-sm-10
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index ed19dccf53e..d6b52f86154 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -78,7 +78,7 @@
or change it at #{link_to "gravatar.com", "http://gravatar.com"}
%hr
%a.choose-btn.btn.btn-small.js-choose-user-avatar-button
- %i.icon-paper-clip
+ %i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
new file mode 100644
index 00000000000..fd8320adb8d
--- /dev/null
+++ b/app/views/projects/_commit_button.html.haml
@@ -0,0 +1,9 @@
+.form-actions
+ .commit-button-annotation
+ = button_tag 'Commit Changes',
+ class: 'btn commit-btn js-commit-button btn-create'
+ .message
+ to branch
+ %strong= ref
+ = link_to 'Cancel', cancel_path,
+ class: 'btn btn-cancel', data: {confirm: leave_edit_message}
diff --git a/app/views/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml
index e283bd2bf1d..6ff46970336 100644
--- a/app/views/projects/_dropdown.html.haml
+++ b/app/views/projects/_dropdown.html.haml
@@ -1,7 +1,7 @@
- if current_user
.dropdown.pull-right
%a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
- %i.icon-reorder
+ %i.fa.fa-bars
%ul.dropdown-menu
- if @project.issues_enabled && can?(current_user, :write_issue, @project)
%li
@@ -23,11 +23,11 @@
%li.divider
%li
= link_to new_project_branch_path(@project) do
- %i.icon-code-fork
+ %i.fa.fa-code-fork
Git branch
%li
= link_to new_project_tag_path(@project) do
- %i.icon-tag
+ %i.fa.fa-tag
Git tag
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 2c1ac06fc90..672a91e0eef 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -3,7 +3,7 @@
.project-home-row
.project-home-desc
- if @project.description.present?
- = auto_link ERB::Util.html_escape(@project.description), link: :urls
+ = escaped_autolink(@project.description)
- if can?(current_user, :admin_project, @project)
&ndash;
= link_to 'Edit', edit_project_path
@@ -17,7 +17,7 @@
.fork-buttons
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- if current_user.already_forked?(@project)
- = link_to project_path(current_user.fork_of(@project)), title: 'Got to my fork' do
+ = link_to project_path(current_user.fork_of(@project)), title: 'Go to my fork' do
= link_to_toggle_fork
- else
= link_to fork_project_path(@project), title: "Fork project", method: "POST" do
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index 3f288814b51..6cdfab933b4 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -29,7 +29,7 @@
.form-group
.issue-assignee
= f.label :assignee_id, class: 'control-label' do
- %i.icon-user
+ %i.fa.fa-user
Assign to
.col-sm-10
= project_users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
@@ -40,7 +40,36 @@
.form-group
.issue-milestone
= f.label :milestone_id, class: 'control-label' do
- %i.icon-time
+ %i.fa.fa-clock-o
Milestone
- .col-sm-10= f.select(:milestone_id, milestone_options(issuable),
- { include_blank: 'Select milestone' }, { class: 'select2' })
+ .col-sm-10
+ - if milestone_options(issuable).present?
+ = f.select(:milestone_id, milestone_options(issuable),
+ { include_blank: 'Select milestone' }, { class: 'select2' })
+ - else
+ %span.light No open milestones available.
+ &nbsp;
+ = link_to 'Create new milestone', new_project_milestone_path(issuable.project)
+.form-group
+ = f.label :label_ids, class: 'control-label' do
+ %i.icon-tag
+ Labels
+ .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'
+ - else
+ %span.light No labels yet.
+ &nbsp;
+ = link_to 'Create new label', new_project_label_path(issuable.project)
+
+.form-actions
+ - if issuable.new_record?
+ = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
+ - else
+ = f.submit 'Save changes', class: 'btn btn-save'
+ - if issuable.new_record?
+ - cancel_project = issuable.source_project
+ - else
+ - cancel_project = issuable.project
+ = link_to 'Cancel', [cancel_project, issuable], class: 'btn btn-cancel'
diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml
index 9b73f06a5be..2008f8c558d 100644
--- a/app/views/projects/_settings_nav.html.haml
+++ b/app/views/projects/_settings_nav.html.haml
@@ -1,25 +1,25 @@
%ul.nav.nav-pills.nav-stacked.nav-stacked-menu.append-bottom-20.project-settings-nav
= nav_link(path: 'projects#edit') do
= link_to edit_project_path(@project), class: "stat-tab tab " do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Project
= nav_link(controller: [:team_members, :teams]) do
= link_to project_team_index_path(@project), class: "team-tab tab" do
- %i.icon-group
+ %i.fa.fa-users
Members
= nav_link(controller: :deploy_keys) do
= link_to project_deploy_keys_path(@project) do
- %i.icon-key
+ %i.fa.fa-key
Deploy Keys
= nav_link(controller: :hooks) do
= link_to project_hooks_path(@project) do
- %i.icon-link
+ %i.fa.fa-link
Web Hooks
= nav_link(controller: :services) do
= link_to project_services_path(@project) do
- %i.icon-cogs
+ %i.fa.fa-cogs
Services
= nav_link(controller: :protected_branches) do
= link_to project_protected_branches_path(@project) do
- %i.icon-lock
+ %i.fa.fa-lock
Protected branches
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 464e7ca40e1..bdf02c6285d 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -3,7 +3,7 @@
#tree-holder.tree-holder
.file-holder
.file-title
- %i.icon-file
+ %i.fa.fa-file
%span.file_name
= @path
%small= number_to_human_size @blob.size
@@ -15,7 +15,7 @@
%tr
%td.blame-commit
%span.commit
- = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
+ = link_to commit.short_id, project_commit_path(@project, commit), class: "commit_short_id"
&nbsp;
= commit_author_link(commit, avatar: true, size: 16)
&nbsp;
@@ -30,5 +30,5 @@
%code{ class: highlightjs_class(@blob.name) }
:erb
<% lines.each do |line| %>
- <%= line %>
+ <%= line %>
<% end %>
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index 8587dc4bc6d..812d88a8730 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -2,21 +2,27 @@
-# only show edit link for text files
- if @blob.text?
- if allowed_tree_edit?
- = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small"
+ = link_to 'Edit', project_edit_tree_path(@project, @id),
+ class: 'btn btn-small'
- else
- %span.btn.btn-small.disabled edit
- = link_to "raw", project_raw_path(@project, @id), class: "btn btn-small", target: "_blank"
+ %span.btn.btn-small.disabled Edit
+ = link_to 'Raw', project_raw_path(@project, @id),
+ class: 'btn btn-small', target: '_blank'
-# only show normal/blame view links for text files
- if @blob.text?
- if current_page? project_blame_path(@project, @id)
- = link_to "normal view", project_blob_path(@project, @id), class: "btn btn-small"
+ = link_to 'Normal View', project_blob_path(@project, @id),
+ class: 'btn btn-small'
- else
- = link_to "blame", project_blame_path(@project, @id), class: "btn btn-small" unless @blob.empty?
- = link_to "history", project_commits_path(@project, @id), class: "btn btn-small"
+ = link_to 'Blame', project_blame_path(@project, @id),
+ class: 'btn btn-small' unless @blob.empty?
+ = link_to 'History', project_commits_path(@project, @id),
+ class: 'btn btn-small'
- if @ref != @commit.sha
- = link_to 'permalink', project_blob_path(@project,
+ = link_to 'Permalink', project_blob_path(@project,
tree_join(@commit.sha, @path)), class: 'btn btn-small'
- if allowed_tree_edit?
- = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do
- remove
+ = button_tag class: 'remove-blob btn btn-small btn-remove',
+ 'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do
+ Remove
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index be785dacedb..492dff437b5 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -1,6 +1,6 @@
%ul.breadcrumb.repo-breadcrumb
%li
- %i.icon-angle-right
+ %i.fa.fa-angle-right
= link_to project_tree_path(@project, @ref) do
= @project.path
- tree_breadcrumbs(@tree, 6) do |title, path|
@@ -22,7 +22,7 @@
%div#tree-content-holder.tree-content-holder
%article.file-holder
.file-title.clearfix
- %i.icon-file
+ %i.fa.fa-file
%span.file_name
= blob.name
%small= number_to_human_size blob.size
diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml
index cdbfee7cc68..c24eeea4931 100644
--- a/app/views/projects/blob/_download.html.haml
+++ b/app/views/projects/blob/_download.html.haml
@@ -2,6 +2,6 @@
.center
= link_to project_raw_path(@project, @id) do
%h1.light
- %i.icon-download-alt
+ %i.fa.fa-download
%h4
Download (#{number_to_human_size blob.size})
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index 93ffd4463b1..c5568315cb1 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -10,16 +10,12 @@
.modal-body
= form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal' do
- .form-group.commit_message-group
- = label_tag 'commit_message', class: "control-label" do
- Commit message
- .col-sm-10
- = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message',
- params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3, class: 'form-control')}
+ = render 'shared/commit_message_container', params: params,
+ placeholder: 'Removed this file because...'
.form-group
.col-sm-2
.col-sm-10
- = submit_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
+ = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 08a537e0541..8e58f3c247a 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -7,19 +7,19 @@
%span.label.label-info default
- if @project.protected_branch? branch.name
%span.label.label-success
- %i.icon-lock
+ %i.fa.fa-lock
protected
.pull-right
- if can?(current_user, :download_code, @project)
= render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-small'
- if branch.name != @repository.root_ref
= link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-small', method: :post, title: "Compare" do
- %i.icon-copy
+ %i.fa.fa-files-o
Compare
- if can_remove_branch?(@project, branch.name)
= link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
- %i.icon-trash
+ %i.fa.fa-trash-o
- if commit
%ul.list-unstyled
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 8bc4a8f7e98..9f2b1b59292 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -4,7 +4,7 @@
.pull-right
- if can? current_user, :push_code, @project
= link_to new_project_branch_path(@project), class: 'btn btn-create' do
- %i.icon-add-sign
+ %i.fa.fa-add-sign
New branch
&nbsp;
.dropdown.inline
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 3f202f7ea6b..a6623240da1 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -3,7 +3,7 @@
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
= @error
%h3.page-title
- %i.icon-code-fork
+ %i.fa.fa-code-fork
New branch
= form_tag project_branches_path, method: :post, class: "form-horizontal" do
.form-group
@@ -15,7 +15,7 @@
.col-sm-10
= text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
.form-actions
- = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3
+ = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
:javascript
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 2bc9048b2ad..e149f017f84 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -2,11 +2,11 @@
%div
- if @notes_count > 0
%span.btn.disabled.btn-grouped
- %i.icon-comment
+ %i.fa.fa-comment
= @notes_count
.pull-left.btn-group
%a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
- %i.icon-download-alt
+ %i.fa.fa-download
Download as
%span.caret
%ul.dropdown-menu
@@ -35,7 +35,7 @@
.commit-info-row
%span.cgray= pluralize(@commit.parents.count, "parent")
- @commit.parents.each do |parent|
- = link_to parent.id[0...10], project_commit_path(@project, parent)
+ = link_to parent.short_id, project_commit_path(@project, parent)
- if @branches.any?
.commit-info-row
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 8e73663939f..1eb17f760dc 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -1,6 +1,6 @@
%li.commit.js-toggle-container
.commit-row-title
- = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
+ = link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id"
&nbsp;
%span.str-truncated
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
@@ -18,7 +18,7 @@
- if note_count > 0
%span.label.label-gray
- %i.icon-comment= note_count
+ %i.fa.fa-comment= note_count
- if commit.description?
.commit-row-description.js-toggle-content
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index 77f795f23f2..d57659065a8 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -2,7 +2,7 @@
.row.commits-row
.col-md-2
%h4
- %i.icon-calendar
+ %i.fa.fa-calendar
%span= day.stamp("28 Aug, 2010")
%p= pluralize(commits.count, 'commit')
.col-md-10
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 2dcd84af010..0c9d906481b 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -13,7 +13,3 @@
= link_to project_tags_path(@project) do
Tags
%span.badge.js-totaltags-count= @repository.tags.length
-
- = nav_link(controller: :repositories, action: :stats) do
- = link_to stats_project_repository_path(@project) do
- Stats
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
index b36369b4285..574599aa2d2 100644
--- a/app/views/projects/commits/_inline_commit.html.haml
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -1,6 +1,6 @@
%li.commit.inline-commit
.commit-row-title
- = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
+ = link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id"
&nbsp;
%span.str-truncated
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 482a845558f..5717c24c274 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -6,7 +6,7 @@
- if current_user && current_user.private_token
.commits-feed-holder.hidden-xs.hidden-sm
= link_to project_commits_path(@project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'btn' do
- %i.icon-rss
+ %i.fa.fa-rss
Commits feed
%ul.breadcrumb.repo-breadcrumb
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index da6157cf1b6..cb0a3747f7d 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -12,7 +12,7 @@
%span.input-group-addon to
= text_field_tag :to, params[:to], class: "form-control"
&nbsp;
- = submit_tag "Compare", class: "btn btn-create commits-compare-btn"
+ = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if compare_to_mr_button?
= link_to compare_mr_path, class: 'prepend-left-10 btn' do
%strong Make a merge request
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index 2b4f36fb4b8..a0345dbd9c3 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -2,19 +2,19 @@
.pull-right
- if @available_keys.include?(deploy_key)
= link_to enable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
- %i.icon-plus
+ %i.fa.fa-plus
Enable
- else
- if deploy_key.projects.count > 1
= link_to disable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
- %i.icon-off
+ %i.fa.fa-power-off
Disable
- else
= link_to 'Remove', project_deploy_key_path(@project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
= link_to project_deploy_key_path(deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first, deploy_key) do
- %i.icon-key
+ %i.fa.fa-key
%strong= deploy_key.title
%p.light.prepend-top-10
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index f50aeba337a..6f475e0b395 100644
--- a/app/views/projects/deploy_keys/index.html.haml
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -2,7 +2,7 @@
Deploy keys allow read-only access to the repository
= link_to new_project_deploy_key_path(@project), class: "btn btn-new pull-right", title: "New Deploy Key" do
- %i.icon-plus
+ %i.fa.fa-plus
New Deploy Key
%p.light
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index be9389172b7..bf7770ceedf 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -10,20 +10,24 @@
- if @commit.parent_ids.present?
= view_file_btn(@commit.parent_id, diff_file, project)
- else
- %span= diff_file.new_path
+ - if diff_file.renamed_file
+ %span= "#{diff_file.old_path} renamed to #{diff_file.new_path}"
+ - else
+ %span= diff_file.new_path
- if diff_file.mode_changed?
%span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
.diff-btn-group
- - unless params[:view] == 'parallel'
- %label
- = check_box_tag nil, 1, false, class: "js-toggle-diff-line-wrap"
- Wrap text
- &nbsp;
- = link_to "#", class: "js-toggle-diff-comments btn btn-small" do
- %i.icon-chevron-down
- Diff comments
- &nbsp;
+ - if blob.text?
+ - unless params[:view] == 'parallel'
+ %label
+ = check_box_tag nil, 1, false, class: 'js-toggle-diff-line-wrap'
+ Wrap text
+ &nbsp;
+ = link_to '#', class: 'js-toggle-diff-comments btn btn-small' do
+ %i.fa.fa-chevron-down
+ Diff comments
+ &nbsp;
- if @merge_request && @merge_request.source_project
= link_to project_edit_tree_path(@merge_request.source_project, tree_join(@merge_request.source_branch, diff_file.new_path), from_merge_request_id: @merge_request.id), { class: 'btn btn-small' } do
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 8ef7cc6e086..20e51d18da5 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -11,7 +11,7 @@
&nbsp;
= link_to '#', class: 'btn btn-small js-toggle-button' do
Show diff stats
- %i.icon-chevron-down
+ %i.fa.fa-chevron-down
.file-stats.js-toggle-content.hide
%ul.bordered-list
- diffs.each_with_index do |diff, i|
@@ -19,23 +19,23 @@
- if diff.deleted_file
%span.deleted-file
%a{href: "#diff-#{i}"}
- %i.icon-minus
+ %i.fa.fa-minus
= diff.old_path
- elsif diff.renamed_file
%span.renamed-file
%a{href: "#diff-#{i}"}
- %i.icon-minus
+ %i.fa.fa-minus
= diff.old_path
\->
= diff.new_path
- elsif diff.new_file
%span.new-file
%a{href: "#diff-#{i}"}
- %i.icon-plus
+ %i.fa.fa-plus
= diff.new_path
- else
%span.edit-file
%a{href: "#diff-#{i}"}
- %i.icon-adjust
+ %i.fa.fa-adjust
= diff.new_path
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index d9acb685517..b85cf7d8d37 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -13,7 +13,7 @@
= f.label :name, class: 'control-label' do
Project name
.col-sm-10
- = f.text_field :name, placeholder: "Example Project", class: "form-control"
+ = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
.form-group
@@ -86,105 +86,105 @@
- .danger-settings.js-toggle-container
- .centered-light-block
- %h3
- %i.icon-warning-sign
- Dangerous settings
-
- %p Project settings below may result in data loss!
- = link_to '#', class: 'btn js-toggle-button' do
- Show them to me
- %i.icon-chevron-down
-
- .js-toggle-content.hide
- - if can? current_user, :archive_project, @project
- .panel.panel-default.panel.panel-warning
+ .danger-settings
+ - if can? current_user, :archive_project, @project
+ - if @project.archived?
+ .panel.panel-success
.panel-heading
- - if @project.archived?
- Unarchive project
- - else
- Archive project
+ Unarchive project
.panel-body
- - if @project.archived?
- %p
- Unarchiving the project will mark its repository as active.
- %br
- 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_project_path(@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-remove"
- - else
- %p
- Archiving the project will mark its repository as read-only.
- %br
- 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_project_path(@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"
+ %p
+ Unarchiving the project will mark its repository as active.
+ %br
+ 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_project_path(@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
- .nothing-here-block Only the project owner can archive a project
-
- .panel.panel-default.panel.panel-warning
- .panel-heading Rename repository
+ .panel.panel-warning
+ .panel-heading
+ Archive project
+ .panel-body
+ %p
+ Archiving the project will mark its repository as read-only.
+ %br
+ 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_project_path(@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
+
+ .panel.panel-default.panel.panel-warning
+ .panel-heading Rename repository
+ .errors-holder
+ .panel-body
+ = form_for(@project, html: { class: 'form-horizontal' }) do |f|
+ .form-group.project_name_holder
+ = f.label :name, class: 'control-label' do
+ Project name
+ .col-sm-9
+ .form-group
+ = f.text_field :name, placeholder: "Example Project", class: "form-control"
+ .form-group
+ = f.label :path, class: 'control-label' do
+ %span Path
+ .col-sm-9
+ .form-group
+ .input-group
+ = 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"
+
+ - if can?(current_user, :change_namespace, @project)
+ .panel.panel-default.panel.panel-danger
+ .panel-heading Transfer project
.errors-holder
.panel-body
- = form_for(@project, html: { class: 'form-horizontal' }) do |f|
+ = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
.form-group
- = f.label :path, class: 'control-label' do
- %span Path
- .col-sm-9
+ = f.label :namespace_id, class: 'control-label' do
+ %span Namespace
+ .col-sm-10
.form-group
- .input-group
- = f.text_field :path, class: 'form-control'
- %span.input-group-addon .git
+ = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' }
%ul
- %li Be careful. Renaming a project's repository can have unintended side effects.
+ %li Be careful. Changing the project's namespace can have unintended side effects.
+ %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 'Rename', class: "btn btn-warning"
+ = f.submit 'Transfer', 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
- - if can?(current_user, :change_namespace, @project)
- .panel.panel-default.panel.panel-danger
- .panel-heading Transfer project
- .errors-holder
- .panel-body
- = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
- .form-group
- = f.label :namespace_id, class: 'control-label' do
- %span Namespace
- .col-sm-10
- .form-group
- = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' }
- %ul
- %li Be careful. Changing the project's namespace can have unintended side effects.
- %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"
- - else
- .nothing-here-block Only the project owner can transfer a project
-
- - if can?(current_user, :remove_project, @project)
- .panel.panel-default.panel.panel-danger
- .panel-heading Remove project
- .panel-body
+ - if can?(current_user, :remove_project, @project)
+ .panel.panel-default.panel.panel-danger
+ .panel-heading Remove project
+ .panel-body
+ = form_tag(project_path(@project), method: :delete, html: { class: 'form-horizontal'}) do
%p
Removing the project will delete its repository and all related resources including issues, merge requests etc.
%br
%strong Removed projects cannot be restored!
- = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove"
- - else
- .nothing-here-block Only project owner can remove a project
+ = link_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
+ - else
+ .nothing-here-block Only project owner can remove a project
.save-project-loader.hide
.center
%h2
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Saving project.
%p Please wait a moment, this page will automatically refresh when ready.
+
+
+= render 'shared/confirm_modal', phrase: @project.path
diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml
index 62798b51d82..5ccde05063e 100644
--- a/app/views/projects/edit_tree/show.html.haml
+++ b/app/views/projects/edit_tree/show.html.haml
@@ -8,7 +8,7 @@
= form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do
.file-holder.file
.file-title
- %i.icon-file
+ %i.fa.fa-file
%span.file_name
%span.monospace.light #{@ref}:
= @path
@@ -20,24 +20,14 @@
.js-edit-mode-pane#preview.hide
.center
%h2
- %i.icon-spinner.icon-spin
-
- .form-group.commit_message-group
- = label_tag 'commit_message', class: "control-label" do
- Commit message
- .col-sm-10
- = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message', '',
- placeholder: "Update #{@blob.name}", required: true, rows: 3, class: 'form-control')}
- .form-actions
- = hidden_field_tag 'last_commit', @last_commit
- = hidden_field_tag 'content', '', id: "file-content"
- = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
- .commit-button-annotation
- = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary'
- .message
- to branch
- %strong= @ref
- = link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message}
+ %i.fa.fa-spinner.fa-spin
+ = render 'shared/commit_message_container', params: params,
+ placeholder: "Update #{@blob.name}"
+ = hidden_field_tag 'last_commit', @last_commit
+ = hidden_field_tag 'content', '', id: "file-content"
+ = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
+ = render 'projects/commit_button', ref: @ref,
+ cancel_path: @after_edit_path
:javascript
ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace")
diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml
index bdd75de447a..d8f5c7b98d6 100644
--- a/app/views/projects/fork.html.haml
+++ b/app/views/projects/fork.html.haml
@@ -1,6 +1,6 @@
.alert.alert-danger.alert-block
%h4
- %i.icon-code-fork
+ %i.fa.fa-code-fork
Fork Error!
%p
You tried to fork
@@ -15,5 +15,5 @@
%p
= link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do
- %i.icon-code-fork
+ %i.fa.fa-code-fork
Try to Fork again
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
new file mode 100644
index 00000000000..9f37a760e61
--- /dev/null
+++ b/app/views/projects/graphs/_head.html.haml
@@ -0,0 +1,5 @@
+%ul.nav.nav-tabs
+ = nav_link(action: :show) do
+ = link_to 'Contributors', project_graph_path
+ = nav_link(action: :commits) do
+ = link_to 'Commits', commits_project_graph_path
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
new file mode 100644
index 00000000000..a189a487135
--- /dev/null
+++ b/app/views/projects/graphs/commits.html.haml
@@ -0,0 +1,85 @@
+= render 'head'
+
+%p.lead
+ Commits statistic for
+ %strong #{@repository.root_ref}
+ #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
+
+.row
+ .col-md-6
+ %ul
+ %li
+ %p.lead
+ %strong #{@commits_graph.commits.size}
+ commits during
+ %strong #{@commits_graph.duration}
+ days
+ %li
+ %p.lead
+ Average
+ %strong #{@commits_graph.commit_per_day}
+ commits per day
+ %li
+ %p.lead
+ Contributed by
+ %strong #{@commits_graph.authors}
+ authors
+ .col-md-6
+ %div
+ %p.slead
+ Commits per day of month
+ %canvas#month-chart{width: 800, height: 400}
+.row
+ .col-md-6
+ %div
+ %p.slead
+ Commits per day hour (UTC)
+ %canvas#hour-chart{width: 800, height: 400}
+ .col-md-6
+ %div
+ %p.slead
+ Commits per weekday
+ %canvas#weekday-chart{width: 800, height: 400}
+
+:coffeescript
+ data = {
+ labels : #{@commits_per_time.keys.to_json},
+ datasets : [{
+ fillColor : "rgba(220,220,220,0.5)",
+ strokeColor : "rgba(220,220,220,1)",
+ pointColor : "rgba(220,220,220,1)",
+ pointStrokeColor : "#EEE",
+ data : #{@commits_per_time.values.to_json}
+ }]
+ }
+
+ ctx = $("#hour-chart").get(0).getContext("2d");
+ new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true});
+
+ data = {
+ labels : #{@commits_per_week_days.keys.to_json},
+ datasets : [{
+ fillColor : "rgba(220,220,220,0.5)",
+ strokeColor : "rgba(220,220,220,1)",
+ pointColor : "rgba(220,220,220,1)",
+ pointStrokeColor : "#EEE",
+ data : #{@commits_per_week_days.values.to_json}
+ }]
+ }
+
+ ctx = $("#weekday-chart").get(0).getContext("2d");
+ new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true});
+
+ data = {
+ labels : #{@commits_per_month.keys.to_json},
+ datasets : [{
+ fillColor : "rgba(220,220,220,0.5)",
+ strokeColor : "rgba(220,220,220,1)",
+ pointColor : "rgba(220,220,220,1)",
+ pointStrokeColor : "#EEE",
+ data : #{@commits_per_month.values.to_json}
+ }]
+ }
+
+ ctx = $("#month-chart").get(0).getContext("2d");
+ new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true});
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 8e4548f26d0..e3d5094ddc5 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,17 +1,13 @@
+= render 'head'
.loading-graph
.center
%h3.page-title
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Building repository graph.
- %p Please wait a moment, this page will automatically refresh when ready.
+ %p.slead Please wait a moment, this page will automatically refresh when ready.
-.stat-graph
+.stat-graph.hide
.header.clearfix
- .pull-right
- %select
- %option{:value => "commits"} Commits
- %option{:value => "additions"} Additions
- %option{:value => "deletions"} Deletions
%h3#date_header.page-title
%p.light
Commits to #{@project.default_branch}, excluding merge commits. Limited by 6,000 commits
@@ -21,15 +17,21 @@
#contributors.clearfix
%ol.contributors-list.clearfix
-:javascript
- $(".stat-graph").hide();
- $.ajax({
+
+:coffeescript
+ $.ajax
type: "GET",
url: location.href,
- complete: function() {
+ success: (data) ->
+ graph = new ContributorsStatGraph()
+ graph.init(data)
+
+ $("#brush_change").change ->
+ graph.change_date_header()
+ graph.redraw_authors()
+
$(".stat-graph").fadeIn();
$(".loading-graph").hide();
- },
- dataType: "script"
- });
+ dataType: "json"
+
diff --git a/app/views/projects/graphs/show.js.haml b/app/views/projects/graphs/show.js.haml
deleted file mode 100644
index dcf6cacdceb..00000000000
--- a/app/views/projects/graphs/show.js.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-- if @success
- :plain
- controller = new ContributorsStatGraph
- controller.init(#{@log})
-
- $("select").change( function () {
- var field = $(this).val()
- controller.set_current_field(field)
- controller.redraw_master()
- controller.redraw_authors()
- })
-
- $("#brush_change").change( function () {
- controller.change_date_header()
- controller.redraw_authors()
- })
-- else
- :plain
- $('.stat-graph').replaceWith('<div class="alert alert-danger">Failed to load graph</div>')
diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml
index 2b907748486..4513c89e784 100644
--- a/app/views/projects/import.html.haml
+++ b/app/views/projects/import.html.haml
@@ -2,7 +2,7 @@
.save-project-loader
.center
%h2
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Import in progress.
%p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
%p Please wait while we import the repository for you. Refresh at will.
@@ -19,12 +19,13 @@
= form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
- %span Import existing repo
+ %span Import existing git repo
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
.bs-callout.bs-callout-info
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
+ For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
.form-actions
= f.submit 'Retry import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index a4a6b0005c7..64a28d8da49 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -8,23 +8,8 @@
.alert.alert-info
= "Please review the <strong>#{link_to "guidelines for contribution", contribution_guide_url}</strong> to this repository.".html_safe
- = form_for [@project, @issue], html: { class: 'form-horizontal issue-form' } do |f|
+ = form_for [@project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
= render 'projects/issuable_form', f: f, issuable: @issue
- .form-group
- = f.label :label_ids, class: 'control-label' do
- %i.icon-tag
- Labels
- .col-sm-10
- = f.collection_select :label_ids, @project.labels.all, :id, :name, { selected: @issue.label_ids }, multiple: true, class: 'select2'
-
- .form-actions
- - if @issue.new_record?
- = f.submit 'Submit new issue', class: "btn btn-create"
- -else
- = f.submit 'Save changes', class: "btn-save btn"
-
- - cancel_path = @issue.new_record? ? project_issues_path(@project) : project_issue_path(@project, @issue)
- = link_to "Cancel", cancel_path, class: 'btn btn-cancel'
:javascript
$('.assign-to-me-link').on('click', function(e){
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 82cde14e05d..1d2f3ed8118 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -10,18 +10,18 @@
- if current_controller?(:milestones)
%li.pull-right
%button.btn.btn-default.sidebar-expand-button
- %i.icon.icon-list
+ %i.icon.fa.fa-list
- if current_controller?(:issues)
- if current_user
%li
= link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
- %i.icon-rss
+ %i.fa.fa-rss
%li.pull-right
.pull-right
%button.btn.btn-default.sidebar-expand-button
- %i.icon.icon-list
+ %i.icon.fa.fa-list
= form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
.append-right-10.hidden-xs.hidden-sm
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
@@ -32,5 +32,5 @@
= hidden_field_tag :label_id, params['label_id']
- if can? current_user, :write_issue, @project
= link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
- %i.icon-plus
+ %i.fa.fa-plus
New Issue
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 1dfcd726068..7525812696f 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -14,18 +14,20 @@
.issue-info
- if issue.assignee
assigned to #{link_to_member(@project, issue.assignee)}
- - else
- unassigned
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
- if issue.notes.any?
%span
- %i.icon-comments
+ %i.fa.fa-comments
= issue.notes.count
- if issue.milestone
%span
- %i.icon-time
+ %i.fa.fa-clock-o
= issue.milestone.title
+ - if issue.tasks?
+ %span.task-status
+ = issue.task_status
+
.pull-right
%small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')}
@@ -41,7 +43,7 @@
- else
= link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue btn-close", remote: true
= link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
index 8c3f0823386..648f459dc9e 100644
--- a/app/views/projects/issues/_issue_context.html.haml
+++ b/app/views/projects/issues/_issue_context.html.haml
@@ -19,6 +19,7 @@
= hidden_field_tag :issue_context
= f.submit class: 'btn'
- elsif issue.milestone
- = link_to issue.milestone.title, project_milestone_path
+ = link_to project_milestone_path(@project, @issue.milestone) do
+ = @issue.milestone.title
- else
None
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 1d0dcd7f074..0bff8bdbead 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -4,7 +4,7 @@
.issues-filters
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.icon-user
+ %i.fa.fa-user
%span.light assignee:
- if @assignee.present?
%strong= @assignee.name
@@ -27,7 +27,7 @@
.dropdown.inline.prepend-left-10
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.icon-time
+ %i.fa.fa-clock-o
%span.light milestone:
- if @milestone.present?
%strong= @milestone.title
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 5de77b8bf32..4ec362b3063 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,7 +1,7 @@
= render "head"
.row
.fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
- %i.icon-list.icon-2x
+ %i.fa.fa-list.fa-2x
.col-md-3.responsive-side
= render 'shared/project_filter', project_entities_path: project_issues_path(@project),
labels: true, redirect: 'issues', entity: 'issue'
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 41532fea741..aad58e48f6c 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -4,7 +4,7 @@
%span.pull-right.issue-btn-group
- if can?(current_user, :write_issue, @project)
= link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do
- %i.icon-plus
+ %i.fa.fa-plus
New Issue
- if can?(current_user, :modify_issue, @issue)
- if @issue.closed?
@@ -13,7 +13,7 @@
= link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
= link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
.clearfix
@@ -39,7 +39,7 @@
Open
.creator
- Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)}
+ Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
%h4.title
= gfm escape_once(@issue.title)
@@ -48,7 +48,7 @@
.description
.wiki
= preserve do
- = markdown @issue.description
+ = markdown(@issue.description, parse_tasks: true)
.context
%cite.cgray
= render partial: 'issue_context', locals: { issue: @issue }
@@ -62,7 +62,8 @@
= link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue"
.participants
- %cite.cgray #{@issue.participants.count} participants
+ %cite.cgray
+ = pluralize(@issue.participants.count, 'participant')
- @issue.participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 154a01f59ad..d52e64666a0 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,24 +1,6 @@
-= form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal" } do |f|
+= form_for [@project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f|
.merge-request-form-info
= render 'projects/issuable_form', f: f, issuable: @merge_request
- .form-group
- = f.label :label_ids, class: 'control-label' do
- %i.icon-tag
- Labels
- .col-sm-10
- = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2'
-
- .form-actions
- - if @merge_request.new_record?
- = f.submit 'Submit merge request', class: "btn btn-create"
- -else
- = f.submit 'Save changes', class: "btn btn-save"
- - if @merge_request.new_record?
- = link_to project_merge_requests_path(@source_project), class: "btn btn-cancel" do
- Cancel
- - else
- = link_to project_merge_request_path(@target_project, @merge_request), class: "btn btn-cancel" do
- Cancel
:javascript
disableButtonIfEmptyField("#merge_request_title", ".btn-save");
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 2649fb55c30..1ee2e1bdae8 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -4,7 +4,7 @@
= link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title"
- if merge_request.merged?
%small.pull-right
- %i.icon-ok
+ %i.fa.fa-check
MERGED
- else
%span.pull-right
@@ -12,7 +12,7 @@
%span.light
#{merge_request.source_project_namespace}:
= truncate merge_request.source_branch, length: 25
- %i.icon-angle-right.light
+ %i.fa.fa-angle-right.light
= merge_request.target_branch
.merge-request-info
- if merge_request.author
@@ -21,13 +21,15 @@
= render 'votes/votes_inline', votable: merge_request
- if merge_request.notes.any?
%span
- %i.icon-comments
+ %i.fa.fa-comments
= merge_request.mr_and_commit_notes.count
- if merge_request.milestone_id?
%span
- %i.icon-time
+ %i.fa.fa-clock-o
= merge_request.milestone.title
-
+ - if merge_request.tasks?
+ %span.task-status
+ = merge_request.task_status
.pull-right
%small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 84bf81441b1..d4666eacd7e 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -9,7 +9,7 @@
%span.pull-right
= link_to 'Change branches', new_project_merge_request_path(@project)
-= form_for [@project, @merge_request], html: { class: "merge-request-form" } do |f|
+= form_for [@project, @merge_request], html: { class: "merge-request-form gfm-form" } do |f|
.panel.panel-default
.panel-body
@@ -30,7 +30,7 @@
.form-group
.issue-assignee
= f.label :assignee_id do
- %i.icon-user
+ %i.fa.fa-user
Assign to
%div
= project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
@@ -39,12 +39,12 @@
.form-group
.issue-milestone
= f.label :milestone_id do
- %i.icon-time
+ %i.fa.fa-clock-o
Milestone
%div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
.form-group
= f.label :label_ids do
- %i.icon-tag
+ %i.fa.fa-tag
Labels
%div
= f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2'
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 4323c8f65a4..7b28dd5e7da 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -10,18 +10,18 @@
%ul.nav.nav-pills.merge-request-tabs
%li.notes-tab{data: {action: 'notes'}}
= link_to project_merge_request_path(@project, @merge_request) do
- %i.icon-comment
+ %i.fa.fa-comment
Discussion
%span.badge= @merge_request.mr_and_commit_notes.count
%li.diffs-tab{data: {action: 'diffs'}}
= link_to diffs_project_merge_request_path(@project, @merge_request) do
- %i.icon-list-alt
+ %i.fa.fa-list-alt
Changes
%span.badge= @merge_request.diffs.size
- content_for :note_actions do
- if can?(current_user, :modify_merge_request, @merge_request)
- - unless @merge_request.closed? || @merge_request.merged?
+ - if @merge_request.open?
= link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
- if @merge_request.closed?
= link_to 'Reopen', project_merge_request_path(@project, @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"
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 0954fa8fcea..be638d7cac1 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,13 +1,13 @@
- if can? current_user, :write_merge_request, @project
= link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do
- %i.icon-plus
+ %i.fa.fa-plus
New Merge Request
%h3.page-title
Merge Requests
%hr
.row
.fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.icon-list.icon-2x
+ %i.fa.fa-list.fa-2x
.col-md-3.responsive-side
= render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
labels: true, redirect: 'merge_requests', entity: 'merge_request'
@@ -15,7 +15,7 @@
.mr-filters.append-bottom-10
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.icon-user
+ %i.fa.fa-user
%span.light assignee:
- if @assignee.present?
%strong= @assignee.name
@@ -38,7 +38,7 @@
.dropdown.inline.prepend-left-10
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.icon-time
+ %i.fa.fa-clock-o
%span.light milestone:
- if @milestone.present?
%strong= @milestone.title
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index ede709ea1df..a6587403871 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,7 +1,7 @@
- if @commits.present?
.panel.panel-default
.panel-heading
- %i.icon-list
+ %i.fa.fa-list
Commits (#{@commits.count})
.commits.mr-commits
- if @commits.count > 8
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 9540453ce3e..63db4b30968 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
@@ -10,11 +10,11 @@
- target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
%p
%strong Step 1.
- Checkout the branch we are going to merge and pull in the code
+ Fetch the code and create a new branch pointing to it
%pre.dark
:preserve
- git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch}
- git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
+ git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
+ git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD
%p
%strong Step 2.
Merge the branch and push the changes to GitLab
diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index d7d5f970c95..4939ae03994 100644
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -16,20 +16,6 @@
%h4
You can accept this request automatically.
.accept-merge-holder.clearfix
- .js-toggle-container
- %p
- You can
- %strong= link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message"
- before accepting merge request
- .js-toggle-content.hide
- .form-group
- = label_tag :merge_commit_message, "Commit message", class: 'control-label'
- .col-sm-10
- = render 'shared/commit_message_container', {textarea: text_area_tag(:merge_commit_message,
- @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true)}
- %p.hint
- The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines.
-
.accept-group
.pull-left
= f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
@@ -38,6 +24,14 @@
= label_tag :should_remove_source_branch, class: "checkbox" do
= check_box_tag :should_remove_source_branch
Remove source-branch
+ .js-toggle-container
+ %label
+ %i.fa.fa-edit
+ = link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message"
+ .js-toggle-content.hide
+ = render 'shared/commit_message_container', params: params,
+ text: @merge_request.merge_commit_message,
+ rows: 14, hint: true
%hr
.light
@@ -62,7 +56,7 @@
.automerge_widget.unchecked
%p
%strong
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Checking for ability to automatically merge…
.automerge_widget.already_cannot_be_merged.hide
@@ -71,6 +65,6 @@
.merge-in-progress.hide
%p
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
&nbsp;
Merge is in progress. Please wait. Page will be automatically reloaded. &nbsp;
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 f1aaba2010d..7e5a4eda508 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -18,7 +18,7 @@
.description
.wiki
= preserve do
- = markdown @merge_request.description
+ = markdown(@merge_request.description, parse_tasks: true)
.context
%cite.cgray
diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml
index b77eeac6123..941b15d3b32 100644
--- a/app/views/projects/merge_requests/show/_mr_ci.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml
@@ -1,29 +1,28 @@
- if @commits.any?
.ci_widget.ci-success{style: "display:none"}
- %i.icon-ok
+ %i.fa.fa-check
%span CI build passed
for #{@merge_request.last_commit_short_sha}.
= link_to "Build page", ci_build_details_path(@merge_request)
.ci_widget.ci-failed{style: "display:none"}
- %i.icon-remove
+ %i.fa.fa-times
%span CI build failed
for #{@merge_request.last_commit_short_sha}.
= link_to "Build page", ci_build_details_path(@merge_request)
- [:running, :pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"}
- %i.icon-time
+ %i.fa.fa-clock-o
%span CI build #{status}
for #{@merge_request.last_commit_short_sha}.
= link_to "Build page", ci_build_details_path(@merge_request)
.ci_widget
- %strong
- %i.icon-spinner
- Checking for CI status for #{@merge_request.last_commit_short_sha}
+ %i.fa.fa-spinner
+ Checking for CI status for #{@merge_request.last_commit_short_sha}
.ci_widget.ci-error{style: "display:none"}
- %i.icon-remove
+ %i.fa.fa-times
%span Cannot connect to the CI server. Please check your settings and try again.
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 563a5244993..6fe765248e4 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -6,7 +6,7 @@
- if @merge_request.open?
.btn-group.pull-left
%a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
- %i.icon-download-alt
+ %i.fa.fa-download
Download as
%span.caret
%ul.dropdown-menu
@@ -16,7 +16,7 @@
= link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
= link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped", id:"edit_merge_request" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
- if @merge_request.closed?
= link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
@@ -31,7 +31,12 @@
%span.prepend-left-20
%span From
- if @merge_request.for_fork?
- %strong.label-branch #{@merge_request.source_project_namespace}:#{@merge_request.source_branch}
+ %strong.label-branch<
+ - if @merge_request.source_project
+ = link_to @merge_request.source_project_namespace, project_path(@merge_request.source_project)
+ - else
+ \ #{@merge_request.source_project_namespace}
+ \:#{@merge_request.source_branch}
%span into
%strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
- else
diff --git a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
index 491360af1bb..4fe5935bcf3 100644
--- a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
+++ b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
@@ -5,13 +5,13 @@
.remove_source_branch_widget
%p Changes merged into #{@merge_request.target_branch}. You can remove source branch now
= link_to project_branch_path(@merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-small remove_source_branch" do
- %i.icon-remove
+ %i.fa.fa-times
Remove Source Branch
.remove_source_branch_widget.failed.hide
Failed to remove source branch '#{@merge_request.source_branch}'
.remove_source_branch_in_progress.hide
- %i.icon-refresh.icon-spin
+ %i.fa.fa-refresh.fa-spin
&nbsp;
Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded. &nbsp;
diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml
index a5f8af89d1a..87dad6140be 100644
--- a/app/views/projects/merge_requests/show/_state_widget.html.haml
+++ b/app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -1,8 +1,8 @@
-.panel.mr-state-widget.panel-default
+.mr-state-widget
- if @merge_request.source_project.ci_service && @commits.any?
- .panel-heading
+ .mr-widget-heading
= render "projects/merge_requests/show/mr_ci"
- .panel-body
+ .mr-widget-body
- if @merge_request.open?
- if @merge_request.source_branch_exists? && @merge_request.target_branch_exists?
= render "projects/merge_requests/show/mr_accept"
@@ -21,6 +21,12 @@
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
= render "projects/merge_requests/show/remove_source_branch"
+ - if @merge_request.locked?
+ %h4
+ Merge in progress...
+ %p
+ GitLab tries to merge it right now. During this time merge request is locked and can not be closed.
+
- unless @commits.any?
%h4 Nothing to merge
%p
@@ -31,10 +37,10 @@
%br
Try to use different branches or push new code.
- - if !@closes_issues.empty? && @merge_request.open?
- .panel-footer
+ - if @closes_issues.present? && @merge_request.open?
+ .mr-widget-footer
%span
- %i.icon-ok
+ %i.fa.fa-check
Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
= succeed '.' do
!= gfm(issues_sentence(@closes_issues))
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index df79125eae6..5fb01a11cc5 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -5,7 +5,7 @@
%hr
-= form_for [@project, @milestone], html: {class: "new_milestone form-horizontal"} do |f|
+= form_for [@project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form'} do |f|
-if @milestone.errors.any?
.alert.alert-danger
%ul
@@ -21,7 +21,7 @@
.form-group
= f.label :description, "Description", class: "control-label"
.col-sm-10
- = f.text_area :description, maxlength: 65535, class: "form-control markdown-area", rows: 10
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
.hint
.pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
.pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 4018d132a55..1002b9513ff 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -2,7 +2,7 @@
.pull-right
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link btn-grouped" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
= link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-close"
%h4
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index f0e48a51777..03367b7cdbf 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -4,12 +4,12 @@
Milestones
- if can? current_user, :admin_milestone, @project
= link_to new_project_milestone_path(@project), class: "pull-right btn btn-new", title: "New Milestone" do
- %i.icon-plus
+ %i.fa.fa-plus
New Milestone
.row
.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
- %i.icon-list.icon-2x
+ %i.fa.fa-list.fa-2x
.col-md-3.responsive-side
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if (params[:f] == "active" || !params[:f]))}
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 1a495aa1c40..8263f7530a2 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -4,7 +4,7 @@
.pull-right
- if can?(current_user, :admin_milestone, @project)
= link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
- if @milestone.active?
= link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
@@ -70,7 +70,7 @@
.pull-right
= link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small btn-grouped", title: "New Issue" do
- %i.icon-plus
+ %i.fa.fa-plus
New Issue
= link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link btn-grouped"
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index f8206936e6e..4a21b84fb85 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -3,8 +3,8 @@
.controls
= form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f|
= text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha'
- = button_tag type: 'submit', class: 'btn btn-success btn-search-sha' do
- %i.icon-search
+ = button_tag class: 'btn btn-success btn-search-sha' do
+ %i.fa.fa-search
.inline.prepend-left-20
.checkbox.light
= label_tag :filter_ref do
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 949b72356d7..f5cd0f21e01 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -23,7 +23,7 @@
.col-sm-2
.col-sm-10
= link_to "#", class: 'js-toggle-button' do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
%span Customize repository name?
.js-toggle-content.hide
.form-group
@@ -39,18 +39,19 @@
.col-sm-2
.col-sm-10
= link_to "#", class: 'js-toggle-button' do
- %i.icon-upload-alt
+ %i.fa.fa-upload
%span Import existing repository?
.js-toggle-content.hide
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
- %span Import existing repo
+ %span Import existing git repo
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
.bs-callout.bs-callout-info
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
+ For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
%hr
.form-group
@@ -74,6 +75,6 @@
.save-project-loader.hide
.center
%h2
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Creating project &amp; repository.
%p Please wait a moment, this page will automatically refresh when ready.
diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml
index 4ce8d372a38..f09d3659774 100644
--- a/app/views/projects/new_tree/show.html.haml
+++ b/app/views/projects/new_tree/show.html.haml
@@ -19,28 +19,17 @@
Encoding
.col-sm-10
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
-
- .form-group.commit_message-group
- = label_tag 'commit_message', class: "control-label" do
- Commit message
- .col-sm-10
- = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message',
- params[:commit_message], placeholder: "Added new file", required: true, rows: 3, class: 'form-control')}
-
.file-holder
.file-title
- %i.icon-file
+ %i.fa.fa-file
.file-content.code
%pre#editor= params[:content]
- .form-actions
- = hidden_field_tag 'content', '', id: "file-content"
- .commit-button-annotation
- = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create'
- .message
- to branch
- %strong= @ref
- = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message}
+ = render 'shared/commit_message_container', params: params,
+ placeholder: 'Add new file'
+ = hidden_field_tag 'content', '', id: 'file-content'
+ = render 'projects/commit_button', ref: @ref,
+ cancel_path: project_tree_path(@project, @id)
:javascript
ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index a01056b7166..c731baf0a65 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -4,7 +4,7 @@
%tr.notes_holder
%td.notes_line{ colspan: 2 }
%span.discussion-notes-count
- %i.icon-comment
+ %i.fa.fa-comment
= notes.count
%td.notes_content
%ul.notes{ rel: note.discussion_id }
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index 506d1fff008..789f3e19fd2 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -5,7 +5,7 @@
- if note1
%td.notes_line
%span.btn.disabled
- %i.icon-comment
+ %i.fa.fa-comment
= notes1.count
%td.notes_content.parallel
%ul.notes{ rel: note1.discussion_id }
@@ -20,7 +20,7 @@
- if note2
%td.notes_line
%span.btn.disabled
- %i.icon-comment
+ %i.fa.fa-comment
= notes2.count
%td.notes_content.parallel
%ul.notes{ rel: note2.discussion_id }
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 65d7f081545..c68b3817e79 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" }, authenticity_token: true do |f|
+= form_for [@project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f|
= note_target_fields
= f.hidden_field :commit_id
= f.hidden_field :line_code
@@ -31,7 +31,7 @@
.note-form-option
%a.choose-btn.btn.js-choose-note-attachment-button
- %i.icon-paper-clip
+ %i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-attachment-filename File name...
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 394fa88e045..a25c5e207fb 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -6,16 +6,16 @@
.note-header
.note-actions
= link_to "##{dom_id(note)}", name: dom_id(note) do
- %i.icon-link
+ %i.fa.fa-link
Link here
&nbsp;
- if can?(current_user, :admin_note, note) && note.editable?
= link_to "#", title: "Edit comment", class: "js-note-edit" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
&nbsp;
= link_to project_note_path(@project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do
- %i.icon-trash.cred
+ %i.fa.fa-trash-o.cred
Remove
= link_to_member(@project, note.author, avatar: false)
%span.note-last-update
@@ -23,11 +23,11 @@
- if note.upvote?
%span.vote.upvote.label.label-success
- %i.icon-thumbs-up
+ %i.fa.fa-thumbs-up
\+1
- if note.downvote?
%span.vote.downvote.label.label-danger
- %i.icon-thumbs-down
+ %i.fa.fa-thumbs-down
\-1
@@ -45,7 +45,7 @@
.note-form-option
%a.choose-btn.btn.js-choose-note-attachment-button
- %i.icon-paper-clip
+ %i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-attachment-filename File name...
@@ -59,11 +59,11 @@
- if note.attachment.image?
= link_to note.attachment.secure_url, target: '_blank' do
= image_tag note.attachment.secure_url, class: 'note-image-attach'
- .attachment.pull-right
+ .attachment
= link_to note.attachment.secure_url, target: "_blank" do
- %i.icon-paper-clip
+ %i.fa.fa-paperclip
= note.attachment_identifier
= link_to delete_attachment_project_note_path(@project, note),
title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do
- %i.icon-trash.cred
+ %i.fa.fa-trash-o.cred
.clear
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index eb416c5b5f0..52c06ec172d 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -3,7 +3,7 @@
.discussion-header
.discussion-actions
= link_to "#", class: "js-toggle-button" do
- %i.icon-chevron-up
+ %i.fa.fa-chevron-up
Show/hide discussion
%div
= link_to_member(@project, note.author, avatar: false)
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index a928029a5e5..94f16a5f02e 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -3,7 +3,7 @@
.discussion-header
.discussion-actions
= link_to "#", class: "js-toggle-button" do
- %i.icon-chevron-up
+ %i.fa.fa-chevron-up
Show/hide discussion
%div
= link_to_member(@project, note.author, avatar: false)
diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml
index da71220af17..b4d1cce7980 100644
--- a/app/views/projects/notes/discussions/_diff.html.haml
+++ b/app/views/projects/notes/discussions/_diff.html.haml
@@ -21,7 +21,7 @@
- else
%td.old_line= raw(line.type == "new" ? "&nbsp;" : line.old_pos)
%td.new_line= raw(line.type == "old" ? "&nbsp;" : line.new_pos)
- %td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw "#{line.text} &nbsp;"
+ %td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text)
- if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
index 4ae914c107b..52a1d342f55 100644
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ b/app/views/projects/notes/discussions/_outdated.html.haml
@@ -3,7 +3,7 @@
.discussion-header
.discussion-actions
= link_to "#", class: "js-toggle-button" do
- %i.icon-chevron-down
+ %i.fa.fa-chevron-down
Show/hide discussion
%div
= link_to_member(@project, note.author, avatar: false)
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 3980a6c0863..227a2f9a061 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -1,13 +1,13 @@
%h3.page-title Protected branches
-%p.light This ability keeps stable branches secure and forces developers to use code reviews
+%p.light Keep stable branches secure and force developers to use Merge Requests
%hr
.bs-callout.bs-callout-info
%p Protected branches are designed to
%ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
- %li prevents anyone from force pushing to the branch
- %li prevents anyone from deleting the branch
+ %li prevent anyone from force pushing to the branch
+ %li prevent anyone from deleting the branch
%p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
- if can? current_user, :admin_project, @project
@@ -35,7 +35,7 @@
- if @project.root_ref?(branch.name)
%span.label.label-info default
%span.label.label-success
- %i.icon-lock
+ %i.fa.fa-lock
.pull-right
- if can? current_user, :admin_project, @project
= link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index 0bf59a20384..ce69adeb48c 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -1,10 +1,10 @@
- ref = ref || nil
- btn_class = btn_class || ''
-- split_button = split_button || false
+- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
= link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
- %i.icon-download-alt
+ %i.fa.fa-download
%span Download zip
%a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
@@ -13,25 +13,25 @@
%ul.dropdown-menu{ role: 'menu' }
%li
= link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), rel: 'nofollow' do
- %i.icon-download-alt
+ %i.fa.fa-download
%span Download zip
%li
= link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
- %i.icon-download-alt
+ %i.fa.fa-download
%span Download tar.gz
%li
= link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
- %i.icon-download-alt
+ %i.fa.fa-download
%span Download tar.bz2
%li
= link_to archive_project_repository_path(@project, ref: ref, format: 'tar'), rel: 'nofollow' do
- %i.icon-download-alt
+ %i.fa.fa-download
%span Download tar
- else
%span.btn-group{class: btn_class}
= link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
- %i.icon-download-alt
+ %i.fa.fa-download
%span zip
= link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
- %i.icon-download-alt
+ %i.fa.fa-download
%span tar.gz
diff --git a/app/views/projects/repositories/stats.html.haml b/app/views/projects/repositories/stats.html.haml
deleted file mode 100644
index 70db27d6444..00000000000
--- a/app/views/projects/repositories/stats.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-= render "projects/commits/head"
-.row
- .col-md-6
- %div#activity-chart.chart
- %hr
- %p
- %b Total commits:
- %span= @repository.commit_count
- %p
- %b Total files in #{@repository.root_ref}:
- %span= @stats.files_count
- %p
- %b Authors:
- %span= @stats.authors_count
-
-
- .col-md-6
- %h4 Top 50 Committers:
- %ol.styled
- - @stats.authors[0...50].each do |author|
- %li
- = image_tag avatar_icon(author.email, 16), class: 'avatar s16', alt: ''
- = author.name
- %small.light= author.email
- .pull-right
- = author.commits
-
-
-:javascript
- var labels = [#{@graph.labels.to_json}];
- var commits = [#{@graph.commits.join(', ')}];
- var title = "Commit activity for last #{@graph.weeks} weeks";
- Chart.init(labels, commits, title);
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index a5db7969a68..1151f22c7e8 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -28,8 +28,11 @@
- @service.fields.each do |field|
- name = field[:name]
+ - value = @service.send(name) unless field[:type] == 'password'
- type = field[:type]
- placeholder = field[:placeholder]
+ - choices = field[:choices]
+ - default_choice = field[:default_choice]
.form-group
= f.label name, class: "control-label"
@@ -40,6 +43,10 @@
= 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" }
+ - elsif type == 'password'
+ = f.password_field name, class: 'form-control'
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 7902f51f72f..9b06ebe95a4 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,9 +1,14 @@
= render "home_panel"
+- readme = @repository.readme
%ul.nav.nav-tabs
%li.active
- = link_to project_path(@project) do
+ = link_to '#tab-activity', 'data-toggle' => 'tab' do
Activity
+ - if readme
+ %li
+ = link_to '#tab-readme', 'data-toggle' => 'tab' do
+ Readme
.project-home-links
- unless @project.empty_repo?
= link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
@@ -11,57 +16,68 @@
= link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), project_tags_path(@project)
%span.light.prepend-left-20= repository_size
-.row
- %section.col-md-9
- = render "events/event_last_push", event: @last_push
- = render 'shared/event_filter'
- .content_list
- = spinner
- %aside.col-md-3.project-side.hidden-sm.hidden-xs
- .clearfix
- - if @project.archived?
- .alert.alert-warning
- %h4
- %i.icon-warning-sign
- Archived project!
- %p Repository is read-only
+.tab-content
+ .tab-pane.active#tab-activity
+ .row
+ %section.col-md-9
+ = render "events/event_last_push", event: @last_push
+ = render 'shared/event_filter'
+ .content_list
+ = spinner
+ %aside.col-md-3.project-side.hidden-sm.hidden-xs
+ .clearfix
+ - if @project.archived?
+ .alert.alert-warning
+ %h4
+ %i.fa.fa-exclamation-triangle
+ Archived project!
+ %p Repository is read-only
- - if @project.forked_from_project
- .alert.alert-success
- %i.icon-code-fork.project-fork-icon
- Forked from:
- %br
- = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
+ - if @project.forked_from_project
+ .alert.alert-success
+ %i.fa.fa-code-fork.project-fork-icon
+ Forked from:
+ %br
+ = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
- - unless @project.empty_repo?
- = link_to project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do
- Compare code
+ - unless @project.empty_repo?
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do
+ Compare code
- - if @repository.readme
- - readme = @repository.readme
- = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)), class: 'btn btn-block' do
- = readme.name
+ - if @repository.version
+ - version = @repository.version
+ = link_to project_blob_path(@project, tree_join(@repository.root_ref, version.name)), class: 'btn btn-block' do
+ Version:
+ %span.count
+ = @repository.blob_by_oid(version.id).data
- - if @repository.version
- - version = @repository.version
- = link_to project_blob_path(@project, tree_join(@repository.root_ref, version.name)), class: 'btn btn-block' do
- Version:
- %span.count
- = @repository.blob_by_oid(version.id).data
+ .prepend-top-10
+ %p
+ %span.light Created on
+ #{@project.created_at.stamp('Aug 22, 2013')}
+ %p
+ %span.light Owned by
+ - if @project.group
+ #{link_to @project.group.name, @project.group} group
+ - else
+ #{link_to @project.owner_name, @project.owner}
- .prepend-top-10
- %p
- %span.light Created on
- #{@project.created_at.stamp('Aug 22, 2013')}
- %p
- %span.light Owned by
- - if @project.group
- #{link_to @project.group.name, @project.group} group
- - else
- #{link_to @project.owner_name, @project.owner}
+ - @project.ci_services.each do |ci_service|
+ - if ci_service.active? && ci_service.respond_to?(:builds_path)
+ - if ci_service.respond_to?(:status_img_path)
+ = link_to ci_service.builds_path do
+ = image_tag ci_service.status_img_path, alt: "build status"
+ - else
+ %span.light CI provided by
+ = link_to ci_service.title, ci_service.builds_path
+ - if readme
+ .tab-pane#tab-readme
+ %article.readme-holder#README
+ = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do
+ %h4.readme-file-title
+ %i.fa.fa-file
+ = readme.name
+ .wiki
+ = render_readme(readme)
- - if @project.gitlab_ci?
- %hr
- = link_to @project.gitlab_ci_service.builds_path do
- = image_tag @project.gitlab_ci_service.status_img_path, alt: "build status"
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index e4fdbd868c3..ada0d30c496 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -22,7 +22,7 @@
.file-holder
.file-title
- %i.icon-file
+ %i.fa.fa-file
%span.file_name
= @snippet.file_name
.options
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 9ed737b181f..f93c1b4211f 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -2,14 +2,17 @@
%li
%h4
= link_to project_commits_path(@project, tag.name), class: "" do
- %i.icon-tag
+ %i.fa.fa-tag
= tag.name
+ - if tag.message.present?
+ &nbsp;
+ = tag.message
.pull-right
- if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small'
- if can?(current_user, :admin_project, @project)
= link_to project_tag_path(@project, tag.name), class: 'btn btn-small btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do
- %i.icon-trash
+ %i.fa.fa-trash-o
- if commit
%ul.list-unstyled
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 6cbf99239ee..ac74e3b6d36 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -5,7 +5,7 @@
- if can? current_user, :push_code, @project
.pull-right
= link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do
- %i.icon-add-sign
+ %i.fa.fa-add-sign
New tag
%p.light
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 45ee61caf68..ad7ff8d3db8 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -3,7 +3,7 @@
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
= @error
%h3.page-title
- %i.icon-code-fork
+ %i.fa.fa-code-fork
New tag
= form_tag project_tags_path, method: :post, class: "form-horizontal" do
.form-group
@@ -21,7 +21,7 @@
= text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
.light (Optional) Entering a message will create an annotated tag.
.form-actions
- = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3
+ = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
:javascript
diff --git a/app/views/projects/team_members/_group_members.html.haml b/app/views/projects/team_members/_group_members.html.haml
index 77ffab89f37..df3c914fdea 100644
--- a/app/views/projects/team_members/_group_members.html.haml
+++ b/app/views/projects/team_members/_group_members.html.haml
@@ -5,7 +5,7 @@
group members (#{group_users_count})
.pull-right
= link_to members_group_path(@group), class: 'btn btn-small' do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
%ul.well-list
- @group.group_members.order('access_level DESC').limit(20).each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false
diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml
index 79b78665417..7a9c0939ba0 100644
--- a/app/views/projects/team_members/_team_member.html.haml
+++ b/app/views/projects/team_members/_team_member.html.haml
@@ -5,10 +5,10 @@
- unless @project.personal? && user == current_user
.pull-left
= form_for(member, as: :project_member, url: project_team_member_path(@project, member.user)) do |f|
- = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "medium project-access-select span2 trigger-submit"
+ = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "trigger-submit"
&nbsp;
= link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
- %i.icon-minus.icon-white
+ %i.fa.fa-minus.fa-inverse
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
%p
%strong= user.name
diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/team_members/import.html.haml
index 510b579fe2f..d1f46c61b2e 100644
--- a/app/views/projects/team_members/import.html.haml
+++ b/app/views/projects/team_members/import.html.haml
@@ -9,6 +9,6 @@
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
- = submit_tag 'Import project members', class: "btn btn-create"
+ = button_tag 'Import project members', class: "btn btn-create"
= link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index ec2701af0eb..f082d711865 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,7 +1,7 @@
%article.readme-holder#README
= link_to '#README' do
%h4.readme-file-title
- %i.icon-file
+ %i.fa.fa-file
= readme.name
.wiki
= render_readme(readme)
diff --git a/app/views/projects/tree/_spinner.html.haml b/app/views/projects/tree/_spinner.html.haml
index 5a9e77b63df..b47ad0f41e4 100644
--- a/app/views/projects/tree/_spinner.html.haml
+++ b/app/views/projects/tree/_spinner.html.haml
@@ -1,3 +1,3 @@
%span.log_loading.hide
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Loading commit data...
diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml
index 474604df654..46e9be4af83 100644
--- a/app/views/projects/tree/_submodule_item.html.haml
+++ b/app/views/projects/tree/_submodule_item.html.haml
@@ -1,14 +1,14 @@
- tree, commit = submodule_links(submodule_item)
%tr{ class: "tree-item" }
%td.tree-item-file-name
- %i.icon-archive
+ %i.fa.fa-archive
%span
= link_to truncate(submodule_item.name, length: 40), tree
@
%span.monospace
- if commit.nil?
- #{submodule_item.id[0..10]}
+ #{truncate_sha(submodule_item.id)}
- else
- = link_to "#{submodule_item.id[0..10]}", commit
+ = link_to "#{truncate_sha(submodule_item.id)}", commit
%td
%td.hidden-xs
diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml
index 84c3682d7ab..1159fcadffd 100644
--- a/app/views/projects/tree/_tree.html.haml
+++ b/app/views/projects/tree/_tree.html.haml
@@ -12,7 +12,7 @@
%li
= link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do
%small
- %i.icon-plus
+ %i.fa.fa-plus
%div#tree-content-holder.tree-content-holder
%table#tree-slider{class: "table_#{@hex_path} tree-table" }
@@ -24,13 +24,13 @@
.pull-left Last Commit
.last-commit.hidden-sm.pull-left
&nbsp;
- %i.icon-angle-right
+ %i.fa.fa-angle-right
&nbsp;
%small.light
= link_to @commit.short_id, project_commit_path(@project, @commit)
&ndash;
= truncate(@commit.title, length: 50)
- = link_to "history", project_commits_path(@project, @id), class: "pull-right"
+ = link_to 'History', project_commits_path(@project, @id), class: 'pull-right'
- if @path.present?
%tr.tree-item
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 0a24e36ae84..f37c086716d 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal' } do |f|
+= form_for [@project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f|
-if @page.errors.any?
#error_explanation
.alert.alert-danger
@@ -22,7 +22,7 @@
.form-group
= f.label :content, class: 'control-label'
.col-sm-10
- = f.text_area :content, class: 'form-control js-gfm-input markdown-area', rows: 18
+ = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
.col-sm-12.hint
.pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
.pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 68d70873231..30410bc95e0 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -4,5 +4,5 @@
Page History
- if can?(current_user, :write_wiki, @project)
= link_to edit_project_wiki_path(@project, @page), class: "btn btn-grouped" do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index 0a7e51e974c..90539fde583 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -7,13 +7,13 @@
= nav_link(path: 'wikis#git_access') do
= link_to git_access_project_wikis_path(@project) do
- %i.icon-download-alt
+ %i.fa.fa-download
Git Access
- if can?(current_user, :write_wiki, @project)
.pull-right
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- %i.icon-plus
+ %i.fa.fa-plus
New Page
= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index d3a66c48c9b..ef4b8f74714 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -17,7 +17,7 @@
%tr
%td
= link_to project_wiki_path(@project, @page, version_id: commit.id) do
- = commit.id[0..10]
+ = truncate_sha(commit.id)
%td
= commit.author.name
%td
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 049aff0bc9b..eca69ce50b1 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -1,6 +1,6 @@
.dropdown.inline
%a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-tags
+ %i.fa.fa-tags
%span.light Group:
- if @group.present?
%strong= @group.name
@@ -18,7 +18,7 @@
.dropdown.inline.prepend-left-10.project-filter
%a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-tags
+ %i.fa.fa-tags
%span.light Project:
- if @project.present?
%strong= @project.name_with_namespace
diff --git a/app/views/search/_project_filter.html.haml b/app/views/search/_project_filter.html.haml
index 57a45c9acb6..c201b3d6c47 100644
--- a/app/views/search/_project_filter.html.haml
+++ b/app/views/search/_project_filter.html.haml
@@ -1,25 +1,25 @@
%ul.nav.nav-pills.nav-stacked.search-filter
%li{class: ("active" if @scope == 'blobs')}
= link_to search_filter_path(scope: 'blobs') do
- %i.icon-code
+ %i.fa.fa-code
Code
.pull-right
= @search_results.blobs_count
%li{class: ("active" if @scope == 'issues')}
= link_to search_filter_path(scope: 'issues') do
- %i.icon-exclamation-sign
+ %i.fa.fa-exclamation-circle
Issues
.pull-right
= @search_results.issues_count
%li{class: ("active" if @scope == 'merge_requests')}
= link_to search_filter_path(scope: 'merge_requests') do
- %i.icon-code-fork
+ %i.fa.fa-code-fork
Merge requests
.pull-right
= @search_results.merge_requests_count
%li{class: ("active" if @scope == 'notes')}
= link_to search_filter_path(scope: 'notes') do
- %i.icon-comments
+ %i.fa.fa-comments
Comments
.pull-right
= @search_results.notes_count
diff --git a/app/views/search/_snippet_filter.html.haml b/app/views/search/_snippet_filter.html.haml
index 0d1984a0d78..95d23fa9f47 100644
--- a/app/views/search/_snippet_filter.html.haml
+++ b/app/views/search/_snippet_filter.html.haml
@@ -1,13 +1,13 @@
%ul.nav.nav-pills.nav-stacked.search-filter
%li{class: ("active" if @scope == 'snippet_blobs')}
= link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
- %i.icon-code
+ %i.fa.fa-code
Snippet Contents
.pull-right
= @search_results.snippet_blobs_count
%li{class: ("active" if @scope == 'snippet_titles')}
= link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
- %i.icon-book
+ %i.fa.fa-book
Titles and Filenames
.pull-right
= @search_results.snippet_titles_count
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index f9d217e8408..b46b4832e19 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -2,7 +2,7 @@
.file-holder
.file-title
= link_to project_blob_path(@project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do
- %i.icon-file
+ %i.fa.fa-file
%strong
= blob.filename
.file-content.code.term
diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml
index 3615f6ae52a..01fb8cd9b8e 100644
--- a/app/views/search/results/_empty.html.haml
+++ b/app/views/search/results/_empty.html.haml
@@ -1,4 +1,4 @@
.search_box
.search_glyph
- %span.icon-search
+ %span.fa.fa-search
%h4 #{message}
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 6a446538574..a44a4542df5 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -1,7 +1,7 @@
- project = note.project
.search-result-row
%h5.note-search-caption.str-truncated
- %i.icon-comment
+ %i.fa.fa-comment
= link_to_member(project, note.author, avatar: false)
commented on
@@ -10,7 +10,7 @@
= project.name_with_namespace
&middot;
= link_to project_commit_path(project, note.commit_id, anchor: dom_id(note)) do
- Commit #{note.commit_id[0..8]}
+ Commit #{truncate_sha(note.commit_id)}
- else
= link_to project do
= project.name_with_namespace
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index a3d909d44dc..6fc2cdf6362 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -11,7 +11,7 @@
= link_to snippet_path do
.file-holder
.file-title
- %i.icon-file
+ %i.fa.fa-file
%strong= snippet_blob[:snippet_object].file_name
%span.options
.btn-group.tree-btn-group.pull-right
@@ -46,7 +46,7 @@
- offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1
- i = index + offset
= link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}" do
- %i.icon-link
+ %i.fa.fa-link
= i
- unless snippet == snippet_blob[:snippet_chunks].last
%a
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 84abb9293b2..f7e5ee5e20e 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -4,7 +4,7 @@
= truncate(snippet_title.title, length: 60)
- if snippet_title.private?
%span.label.label-gray
- %i.icon-lock
+ %i.fa.fa-lock
private
%span.cgray.monospace.tiny.pull-right.term
= snippet_title.file_name
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index 75414d73b0c..e361074b6a0 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -2,7 +2,7 @@
.file-holder
.file-title
= link_to project_wiki_path(@project, wiki_blob.filename) do
- %i.icon-file
+ %i.fa.fa-file
%strong
= wiki_blob.filename
.file-content.code.term
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index bae57917a4c..5b4816e4c40 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -6,7 +6,7 @@
.col-sm-6
= search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search"
.col-sm-4
- = submit_tag 'Search', class: "btn btn-create"
+ = button_tag 'Search', class: "btn btn-create"
.form-group
.col-sm-2
- unless params[:snippets].eql? 'true'
diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml
new file mode 100644
index 00000000000..f32c2d388a7
--- /dev/null
+++ b/app/views/shared/_choose_group_avatar_button.html.haml
@@ -0,0 +1,7 @@
+%a.choose-btn.btn.btn-small.js-choose-group-avatar-button
+ %i.fa.fa-paperclip
+ %span Choose File ...
+&nbsp;
+%span.file_name.js-avatar-filename File name...
+= f.file_field :avatar, class: 'js-group-avatar-input hidden'
+.light The maximum file size allowed is 100KB.
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index 4365947e701..5071ff640f1 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -1,3 +1,14 @@
-.commit-message-container
- .max-width-marker
- = textarea
+.form-group.commit_message-group
+ = label_tag 'commit_message', class: 'control-label' do
+ Commit message
+ .col-sm-10
+ .commit-message-container
+ .max-width-marker
+ = text_area_tag 'commit_message',
+ (params[:commit_message] || local_assigns[:text]),
+ class: 'form-control', placeholder: local_assigns[:placeholder],
+ required: true, rows: (local_assigns[:rows] || 3)
+ - if local_assigns[:hint]
+ %p.hint
+ Try to keep the first line under 52 characters
+ and the others under 72.
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
new file mode 100644
index 00000000000..30ba361c860
--- /dev/null
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -0,0 +1,22 @@
+#modal-confirm-danger.modal.hide{tabindex: -1}
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h4 Confirmation required
+
+ .modal-body
+ %p.cred.lead.js-confirm-text
+
+ %p
+ This action can lead to data loss.
+ To prevent accidental actions we ask you to confirm your intention.
+ %br
+ Please type
+ %code.js-confirm-danger-match #{phrase}
+ to proceed or close this modal to cancel
+
+ .form-group
+ = text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
+ .form-group
+ = submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
diff --git a/app/views/shared/_file_hljs.html.haml b/app/views/shared/_file_hljs.html.haml
index cbc01bd60e0..444c948b026 100644
--- a/app/views/shared/_file_hljs.html.haml
+++ b/app/views/shared/_file_hljs.html.haml
@@ -5,7 +5,7 @@
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
= link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do
- %i.icon-link
+ %i.fa.fa-link
= i
.highlight
%pre
diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml
index 9e65ce11ade..d366dd97a71 100644
--- a/app/views/shared/_filter.html.haml
+++ b/app/views/shared/_filter.html.haml
@@ -45,6 +45,6 @@
%fieldset
- if params[:state].present? || params[:project_id].present?
= link_to filter_path(entity, state: nil, project_id: nil), class: 'pull-right cgray' do
- %i.icon-remove
+ %i.fa.fa-times
%strong Clear filter
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
new file mode 100644
index 00000000000..93294e42505
--- /dev/null
+++ b/app/views/shared/_group_form.html.haml
@@ -0,0 +1,12 @@
+.form-group
+ = f.label :name, class: 'control-label' do
+ Group name
+ .col-sm-10
+ = f.text_field :name, placeholder: 'Example Group', class: 'form-control',
+ autofocus: local_assigns[:autofocus] || false
+
+.form-group.group-description-holder
+ = f.label :description, 'Details', 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/_group_tips.html.haml b/app/views/shared/_group_tips.html.haml
new file mode 100644
index 00000000000..e5cf783beb7
--- /dev/null
+++ b/app/views/shared/_group_tips.html.haml
@@ -0,0 +1,6 @@
+%ul
+ %li A group is a collection of several projects
+ %li Groups are private by default
+ %li Members of a group may only view projects they have permission to access
+ %li Group project URLs are prefixed with the group namespace
+ %li Existing projects may be moved into a group
diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml
index 5e7d1c2c885..ea6a49e1501 100644
--- a/app/views/shared/_project_filter.html.haml
+++ b/app/views/shared/_project_filter.html.haml
@@ -38,7 +38,7 @@
Labels
%small.pull-right
= link_to project_labels_path(@project), class: 'light' do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
%ul.nav.nav-pills.nav-stacked.nav-small.labels-filter
- @project.labels.order_by_name.each do |label|
%li{class: label_filter_class(label.name)}
@@ -46,7 +46,7 @@
= render_colored_label(label)
- if selected_label?(label.name)
.pull-right
- %i.icon-remove
+ %i.fa.fa-times
- if @project.labels.empty?
.light-well
@@ -58,7 +58,7 @@
%fieldset
- if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any?
= link_to project_entities_path, class: 'cgray pull-right' do
- %i.icon-remove
+ %i.fa.fa-times
%strong Clear filter
diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml
index 5675e43b05f..3400c345c4c 100644
--- a/app/views/shared/_promo.html.haml
+++ b/app/views/shared/_promo.html.haml
@@ -1,5 +1,5 @@
.gitlab-promo
- = link_to "Homepage", "https://www.gitlab.com/"
- = link_to "Blog", "https://www.gitlab.com/blog/"
+ = link_to 'Homepage', promo_url
+ = link_to "Blog", promo_url + '/blog/'
= link_to "@gitlabhq", "https://twitter.com/gitlabhq"
= link_to "Requests", "http://feedback.gitlab.com/"
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 49ea8460e7d..f729f129e45 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -10,22 +10,8 @@
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
- - unless @snippet.respond_to?(:project)
- .form-group
- = f.label "Access", class: 'control-label'
- .col-sm-10
- = f.label :private_true, class: 'radio-label' do
- = f.radio_button :private, true
- %span
- %strong Private
- (only you can see this snippet)
- %br
- = f.label :private_false, class: 'radio-label' do
- = f.radio_button :private, false
- %span
- %strong Public
- (GitLab users can see this snippet)
-
+ = render "shared/snippets/visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true
+
.form-group
.file-editor
= f.label :file_name, "File", class: 'control-label'
diff --git a/app/views/shared/snippets/_visibility_level.html.haml b/app/views/shared/snippets/_visibility_level.html.haml
new file mode 100644
index 00000000000..9acff18e450
--- /dev/null
+++ b/app/views/shared/snippets/_visibility_level.html.haml
@@ -0,0 +1,27 @@
+.form-group.project-visibility-level-holder
+ = f.label :visibility_level, class: 'control-label' do
+ Visibility Level
+ = link_to "(?)", help_page_path("public_access", "public_access")
+ .col-sm-10
+ - if can_change_visibility_level
+ - Gitlab::VisibilityLevel.values.each do |level|
+ .radio
+ - restricted = restricted_visibility_levels.include?(level)
+ = f.radio_button :visibility_level, level, disabled: restricted
+ = label "#{dom_class(@snippet)}_visibility_level", level do
+ = visibility_level_icon(level)
+ .option-title
+ = visibility_level_label(level)
+ .option-descr
+ = snippet_visibility_level_description(level)
+ - unless restricted_visibility_levels.empty?
+ .col-sm-10
+ %span.info
+ Some visibility level settings have been restricted by the administrator.
+ - else
+ .col-sm-10
+ %span.info
+ = visibility_level_icon(visibility_level)
+ %strong
+ = visibility_level_label(visibility_level)
+ .light= visibility_level_description(visibility_level)
diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml
index e6f83167330..c584dd8dfb6 100644
--- a/app/views/snippets/_snippet.html.haml
+++ b/app/views/snippets/_snippet.html.haml
@@ -4,7 +4,7 @@
= truncate(snippet.title, length: 60)
- if snippet.private?
%span.label.label-gray
- %i.icon-lock
+ %i.fa.fa-lock
private
%span.cgray.monospace.tiny.pull-right
= snippet.file_name
diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml
index e3edd856983..b2b7ea4df0e 100644
--- a/app/views/snippets/current_user_index.html.haml
+++ b/app/views/snippets/current_user_index.html.haml
@@ -23,6 +23,11 @@
Private
%span.pull-right
= @user.snippets.are_private.count
+ = nav_tab :scope, 'are_internal' do
+ = link_to user_snippets_path(@user, scope: 'are_internal') do
+ Internal
+ %span.pull-right
+ = @user.snippets.are_internal.count
= nav_tab :scope, 'are_public' do
= link_to user_snippets_path(@user, scope: 'are_public') do
Public
diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml
index cea2517a8e1..0d71c41e2e7 100644
--- a/app/views/snippets/index.html.haml
+++ b/app/views/snippets/index.html.haml
@@ -2,10 +2,12 @@
Public snippets
.pull-right
- = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
- Add new snippet
- = link_to user_snippets_path(current_user), class: "btn btn-grouped" do
- My snippets
+
+ - if current_user
+ = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
+ Add new snippet
+ = link_to user_snippets_path(current_user), class: "btn btn-grouped" do
+ My snippets
%p.light
Public snippets created by you and other users are listed here
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 1d2e3d5ae4a..f5bc543de10 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -3,7 +3,7 @@
- if @snippet.private?
%span.label.label-success
- %i.icon-lock
+ %i.fa.fa-lock
private
.pull-right
@@ -30,7 +30,7 @@
.file-holder
.file-title
- %i.icon-file
+ %i.fa.fa-file
%span.file_name
= @snippet.file_name
.options
diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml
index 1cb53ec6a25..67f3a68aa22 100644
--- a/app/views/snippets/user_index.html.haml
+++ b/app/views/snippets/user_index.html.haml
@@ -4,8 +4,9 @@
%span
\/
Snippets
- = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
- Add new snippet
+ - if current_user
+ = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
+ Add new snippet
%hr
diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml
index 09b2985d498..ea008c2dede 100644
--- a/app/views/users/_groups.html.haml
+++ b/app/views/users/_groups.html.haml
@@ -1,3 +1,3 @@
- groups.each do |group|
= link_to group, class: 'profile-groups-avatars', :title => group.name do
- = image_tag group_icon(group.path)
+ - image_tag group_icon(group.path)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 60159a29b99..cb49c030af2 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -6,7 +6,7 @@
- if @user == current_user
.pull-right
= link_to profile_path, class: 'btn' do
- %i.icon-edit
+ %i.fa.fa-pencil-square-o
Edit Profile settings
%br
%span.user-show-username #{@user.username}
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 43ef54631a9..01586150cd2 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -19,4 +19,4 @@ class RepositoryImportWorker
project.import_fail
end
end
-end \ No newline at end of file
+end
diff --git a/config/application.rb b/config/application.rb
index 99dfafdb786..44a5d68d126 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -2,7 +2,7 @@ require File.expand_path('../boot', __FILE__)
require 'rails/all'
require 'devise'
-
+I18n.config.enforce_available_locales = false
Bundler.require(:default, Rails.env)
module Gitlab
@@ -13,7 +13,6 @@ module Gitlab
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib
- #{config.root}/app/finders
#{config.root}/app/models/hooks
#{config.root}/app/models/concerns
#{config.root}/app/models/project_services
@@ -23,10 +22,6 @@ module Gitlab
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
- # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
- # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
- # config.time_zone = 'Central Time (US & Canada)'
-
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 3092ebf3450..bb0ffae0b70 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -33,7 +33,14 @@ production: &base
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
# user: git
+ ## Date & Time settings
+ # Uncomment and customize if you want to change the default time zone of GitLab application.
+ # To see all available zones, run `bundle exec rake time:zones:all`
+ # time_zone: 'UTC'
+
## Email settings
+ # Uncomment and set to false if you need to disable email sending from GitLab (default: true)
+ # email_enabled: true
# Email address used in the "From" field in mails sent by GitLab
email_from: example@example.com
@@ -119,6 +126,7 @@ production: &base
# new_issue_url: "http://jira.sample/secure/CreateIssue.jspa"
## Gravatar
+ ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar:
enabled: true # Use user avatar image from Gravatar.com (default: true)
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
@@ -134,37 +142,61 @@ production: &base
# bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: false
- host: '_your_ldap_server'
- port: 636
- uid: 'sAMAccountName'
- method: 'ssl' # "tls" or "ssl" or "plain"
- bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
- password: '_the_password_of_the_bind_user'
- # If allow_username_or_email_login is enabled, GitLab will ignore everything
- # after the first '@' in the LDAP username submitted by the user on login.
- #
- # Example:
- # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
- # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
- #
- # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
- # disable this setting, because the userPrincipalName contains an '@'.
- allow_username_or_email_login: false
-
- # Base where we can search for users
- #
- # Ex. ou=People,dc=gitlab,dc=example
- #
- base: ''
-
- # Filter LDAP users
- #
- # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
- # Ex. (employeeType=developer)
- #
- # Note: GitLab does not support omniauth-ldap's custom filter syntax.
- #
- user_filter: ''
+ servers:
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+
+ host: '_your_ldap_server'
+ port: 636
+ uid: 'sAMAccountName'
+ method: 'ssl' # "tls" or "ssl" or "plain"
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
+
+ # This setting specifies if LDAP server is Active Directory LDAP server.
+ # For non AD servers it skips the AD specific queries.
+ # If your LDAP server is not AD, set this to false.
+ active_directory: true
+
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
+
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ user_filter: ''
+
+ # GitLab EE only: add more LDAP servers
+ # Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+ # so that GitLab can remember which LDAP server a user belongs to.
+ # uswest2:
+ # label:
+ # host:
+ # ....
## OmniAuth settings
@@ -212,6 +244,15 @@ production: &base
backup:
path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
# keep_time: 604800 # default: 0 (forever) (in seconds)
+ # upload:
+ # # Fog storage connection settings, see http://fog.io/storage/ .
+ # connection:
+ # provider: AWS
+ # region: eu-west-1
+ # aws_access_key_id: AKIAKIAKI
+ # aws_secret_access_key: 'secret123'
+ # # The remote 'directory' to store your backups. For S3, this would be the bucket name.
+ # remote_directory: 'my.s3.bucket'
## GitLab Shell settings
gitlab_shell:
@@ -284,6 +325,20 @@ test:
project_url: "http://redmine/projects/:issues_tracker_id"
issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
+ ldap:
+ enabled: false
+ servers:
+ main:
+ label: ldap
+ host: 127.0.0.1
+ port: 3890
+ uid: 'uid'
+ method: 'plain' # "tls" or "ssl" or "plain"
+ base: 'dc=example,dc=com'
+ user_filter: ''
+ group_base: 'ou=groups,dc=example,dc=com'
+ admin_group: ''
+ sync_ssh_keys: false
staging:
<<: *base
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 5b7e69fbc6a..27bb83784ba 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -56,8 +56,25 @@ end
# Default settings
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
-Settings.ldap['allow_username_or_email_login'] = false if Settings.ldap['allow_username_or_email_login'].nil?
+# backwards compatibility, we only have one host
+if Settings.ldap['enabled'] || Rails.env.test?
+ if Settings.ldap['host'].present?
+ server = Settings.ldap.except('sync_time')
+ server['provider_name'] = 'ldap'
+ Settings.ldap['servers'] = {
+ 'ldap' => server
+ }
+ end
+
+ Settings.ldap['servers'].each do |key, server|
+ server['label'] ||= 'LDAP'
+ server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil?
+ server['active_directory'] = true if server['active_directory'].nil?
+ server['provider_name'] ||= "ldap#{key}".downcase
+ server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
+ end
+end
Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
@@ -78,6 +95,7 @@ Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
+Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git'
@@ -86,6 +104,7 @@ Settings.gitlab['user_home'] ||= begin
rescue ArgumentError # no user configured
'/home/' + Settings.gitlab['user']
end
+Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= false
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
@@ -129,6 +148,11 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s
Settings['backup'] ||= Settingslogic.new({})
Settings.backup['keep_time'] ||= 0
Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root)
+Settings.backup['upload'] ||= Settingslogic.new({'remote_directory' => nil, 'connection' => nil})
+# Convert upload connection settings to use symbol keys, to make Fog happy
+if Settings.backup['upload']['connection']
+ Settings.backup['upload']['connection'] = Hash[Settings.backup['upload']['connection'].map { |k, v| [k.to_sym, v] }]
+end
#
# Git
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
new file mode 100644
index 00000000000..18759f0cfb0
--- /dev/null
+++ b/config/initializers/7_omniauth.rb
@@ -0,0 +1,12 @@
+if Gitlab::LDAP::Config.enabled?
+ module OmniAuth::Strategies
+ server = Gitlab.config.ldap.servers.values.first
+ klass = server['provider_class']
+ const_set(klass, Class.new(LDAP)) unless klass == 'LDAP'
+ end
+
+ OmniauthCallbacksController.class_eval do
+ server = Gitlab.config.ldap.servers.values.first
+ alias_method server['provider_name'], :ldap
+ end
+end \ No newline at end of file
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 34f4f386988..c6eb3e51036 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -204,22 +204,24 @@ Devise.setup do |config|
# manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
- if Gitlab.config.ldap.enabled
- if Gitlab.config.ldap.allow_username_or_email_login
- email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
- else
- email_stripping_proc = ->(name) {name}
+ if Gitlab::LDAP::Config.enabled?
+ Gitlab.config.ldap.servers.values.each do |server|
+ if server['allow_username_or_email_login']
+ email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
+ else
+ email_stripping_proc = ->(name) {name}
+ end
+
+ config.omniauth server['provider_name'],
+ host: server['host'],
+ base: server['base'],
+ uid: server['uid'],
+ port: server['port'],
+ method: server['method'],
+ bind_dn: server['bind_dn'],
+ password: server['password'],
+ name_proc: email_stripping_proc
end
-
- config.omniauth :ldap,
- host: Gitlab.config.ldap['host'],
- base: Gitlab.config.ldap['base'],
- uid: Gitlab.config.ldap['uid'],
- port: Gitlab.config.ldap['port'],
- method: Gitlab.config.ldap['method'],
- bind_dn: Gitlab.config.ldap['bind_dn'],
- password: Gitlab.config.ldap['password'],
- name_proc: email_stripping_proc
end
Gitlab.config.omniauth.providers.each do |provider|
diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb
new file mode 100644
index 00000000000..c76a6b8b19f
--- /dev/null
+++ b/config/initializers/disable_email_interceptor.rb
@@ -0,0 +1,2 @@
+# Interceptor in lib/disable_email_interceptor.rb
+ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled
diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb
new file mode 100644
index 00000000000..8d2b771e535
--- /dev/null
+++ b/config/initializers/gitlab_shell_secret_token.rb
@@ -0,0 +1,19 @@
+# Be sure to restart your server when you modify this file.
+
+require 'securerandom'
+
+# Your secret key for verifying the gitlab_shell.
+
+
+secret_file = Rails.root.join('.gitlab_shell_secret')
+gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret')
+
+unless File.exist? secret_file
+ # Generate a new token of 16 random hexadecimal characters and store it in secret_file.
+ token = SecureRandom.hex(16)
+ File.write(secret_file, token)
+end
+
+if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink)
+ FileUtils.symlink(secret_file, gitlab_shell_symlink)
+end \ No newline at end of file
diff --git a/config/initializers/time_zone.rb b/config/initializers/time_zone.rb
new file mode 100644
index 00000000000..ee246e67d66
--- /dev/null
+++ b/config/initializers/time_zone.rb
@@ -0,0 +1 @@
+Time.zone = Gitlab.config.gitlab.time_zone || Time.zone
diff --git a/config/routes.rb b/config/routes.rb
index 39ab9f4265a..2534153758b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -170,7 +170,7 @@ Gitlab::Application.routes.draw do
resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create]
- devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions }
+ devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions, confirmations: :confirmations }
devise_scope :user do
get "/users/auth/:provider/omniauth_error" => "omniauth_callbacks#omniauth_error", as: :omniauth_error
@@ -206,7 +206,11 @@ Gitlab::Application.routes.draw do
resources :compare, only: [:index, :create]
resources :blame, only: [:show], constraints: {id: /.+/}
resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
- resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
+ resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} do
+ member do
+ get :commits
+ end
+ end
match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
@@ -230,7 +234,6 @@ Gitlab::Application.routes.draw do
resource :repository, only: [:show] do
member do
- get "stats"
get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex }
end
end
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index 6833082d68b..ea22744fd90 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -15,6 +15,7 @@
# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
+# The minimum is 2
worker_processes 2
# Since Unicorn is never exposed to outside clients, it does not need to
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index 1927a39f388..004d4cd64a1 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -1,11 +1,13 @@
-User.seed do |s|
- s.id = 1
- s.name = "Administrator"
- s.email = "admin@example.com"
- s.username = 'root'
- s.password = "5iveL!fe"
- s.password_confirmation = "5iveL!fe"
- s.admin = true
- s.projects_limit = 100
- s.confirmed_at = DateTime.now
+Gitlab::Seeder.quiet do
+ User.seed do |s|
+ s.id = 1
+ s.name = 'Administrator'
+ s.email = 'admin@example.com'
+ s.username = 'root'
+ s.password = '5iveL!fe'
+ s.password_confirmation = '5iveL!fe'
+ s.admin = true
+ s.projects_limit = 100
+ s.confirmed_at = DateTime.now
+ end
end
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index fef9666c6cb..ae4c0550a4f 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -7,7 +7,7 @@ Sidekiq::Testing.inline! do
'https://gitlab.com/gitlab-org/gitlab-ce.git',
'https://gitlab.com/gitlab-org/gitlab-ci.git',
'https://gitlab.com/gitlab-org/gitlab-shell.git',
- 'https://gitlab.com/gitlab-org/testme.git',
+ 'https://gitlab.com/gitlab-org/gitlab-test.git',
'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.git',
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 03f8de12985..f9b2fd8b05f 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -20,4 +20,22 @@ Gitlab::Seeder.quiet do
print '.'
end
end
+
+ project = Project.find_with_namespace('gitlab-org/gitlab-test')
+
+ params = {
+ source_branch: 'feature',
+ target_branch: 'master',
+ title: 'Can be automatically merged'
+ }
+ MergeRequests::CreateService.new(project, User.admins.first, params).execute
+ print '.'
+
+ params = {
+ source_branch: 'feature_conflict',
+ target_branch: 'feature',
+ title: 'Cannot be automatically merged'
+ }
+ MergeRequests::CreateService.new(project, User.admins.first, params).execute
+ print '.'
end
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index ff91e8430a4..b3a6f39c7d5 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -1,9 +1,26 @@
Gitlab::Seeder.quiet do
- contents = [
- `curl https://gist.githubusercontent.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`,
- `curl https://gist.githubusercontent.com/randx/3754594/raw/11026a295e6ef3a151c635707a3e1e8e15fc4725/gitlab_setup.sh`,
- `curl https://gist.githubusercontent.com/randx/3065552/raw/29fbd09f4605a5ea22a5a9095e35fd1938dea4d6/gistfile1.sh`,
- ]
+ content =<<eos
+class Member < ActiveRecord::Base
+ include Notifiable
+ include Gitlab::Access
+
+ belongs_to :user
+ belongs_to :source, polymorphic: true
+
+ validates :user, presence: true
+ validates :source, presence: true
+ validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source" }
+ validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
+
+ scope :guests, -> { where(access_level: GUEST) }
+ scope :reporters, -> { where(access_level: REPORTER) }
+ scope :developers, -> { where(access_level: DEVELOPER) }
+ scope :masters, -> { where(access_level: MASTER) }
+ scope :owners, -> { where(access_level: OWNER) }
+
+ delegate :name, :username, :email, to: :user, prefix: true
+end
+eos
(1..50).each do |i|
user = User.all.sample
@@ -12,10 +29,11 @@ Gitlab::Seeder.quiet do
id: i,
author_id: user.id,
title: Faker::Lorem.sentence(3),
- file_name: Faker::Internet.domain_word + '.sh',
- private: [true, false].sample,
- content: contents.sample,
+ file_name: Faker::Internet.domain_word + '.rb',
+ visibility_level: Gitlab::VisibilityLevel.values.sample,
+ content: content,
}])
+
print('.')
end
end
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index 21c10f31923..0755ac714e1 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -1,8 +1,10 @@
-password = if ENV['GITLAB_ROOT_PASSWORD'].nil? || ENV['GITLAB_ROOT_PASSWORD'].empty?
- "5iveL!fe"
- else
- ENV['GITLAB_ROOT_PASSWORD']
- end
+if ENV['GITLAB_ROOT_PASSWORD'].blank?
+ password = '5iveL!fe'
+ expire_time = Time.now
+else
+ password = ENV['GITLAB_ROOT_PASSWORD']
+ expire_time = nil
+end
admin = User.create(
email: "admin@example.com",
@@ -10,7 +12,7 @@ admin = User.create(
username: 'root',
password: password,
password_confirmation: password,
- password_expires_at: Time.now,
+ password_expires_at: expire_time,
theme_id: Gitlab::Theme::MARS
)
@@ -21,7 +23,7 @@ admin.save!
admin.confirm!
if admin.valid?
-puts %q[
+puts %Q[
Administrator account created:
login.........root
diff --git a/db/migrate/20140907220153_serialize_service_properties.rb b/db/migrate/20140907220153_serialize_service_properties.rb
index b95f5b82e03..bd75ab1eacb 100644
--- a/db/migrate/20140907220153_serialize_service_properties.rb
+++ b/db/migrate/20140907220153_serialize_service_properties.rb
@@ -23,7 +23,7 @@ class SerializeServiceProperties < ActiveRecord::Migration
associations[service.type.to_sym].each do |attribute|
service.send("#{attribute}=", service.attributes[attribute.to_s])
end
- service.save!
+ service.save
end
remove_column :services, :project_url, :string
diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
new file mode 100644
index 00000000000..a8e07033a5d
--- /dev/null
+++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
@@ -0,0 +1,17 @@
+class MoveSlackServiceToWebhook < ActiveRecord::Migration
+ def change
+ SlackService.all.each do |slack_service|
+ if ["token", "subdomain"].all? { |property| slack_service.properties.key? property }
+ token = slack_service.properties['token']
+ subdomain = slack_service.properties['subdomain']
+ webhook = "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}"
+ slack_service.properties['webhook'] = webhook
+ slack_service.properties.delete('token')
+ slack_service.properties.delete('subdomain')
+ # Room is configured on the Slack side
+ slack_service.properties.delete('room')
+ slack_service.save
+ end
+ end
+ end
+end
diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
new file mode 100644
index 00000000000..7f125acb5d1
--- /dev/null
+++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
@@ -0,0 +1,21 @@
+class AddVisibilityLevelToSnippet < ActiveRecord::Migration
+ def up
+ add_column :snippets, :visibility_level, :integer, :default => 0, :null => false
+
+ Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+
+ add_index :snippets, :visibility_level
+
+ remove_column :snippets, :private
+ end
+
+ def down
+ add_column :snippets, :private, :boolean, :default => false, :null => false
+
+ Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false)
+ Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true)
+
+ remove_column :snippets, :visibility_level
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4e249caa022..8ddebc5132a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20140914173417) do
+ActiveRecord::Schema.define(version: 20141007100818) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -293,20 +293,21 @@ ActiveRecord::Schema.define(version: 20140914173417) do
create_table "snippets", force: true do |t|
t.string "title"
t.text "content"
- t.integer "author_id", null: false
+ t.integer "author_id", null: false
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "file_name"
t.datetime "expires_at"
- t.boolean "private", default: true, null: false
t.string "type"
+ t.integer "visibility_level", default: 0, null: false
end
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
+ add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
create_table "taggings", force: true do |t|
t.integer "tag_id"
diff --git a/doc/README.md b/doc/README.md
index a85f2b6256e..7343d5ae273 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -5,6 +5,7 @@
- [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
- [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
+- [Project Services](project_services/project_services.md) Explore how project services can integrate a project with external services, such as for CI.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
@@ -19,6 +20,8 @@
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
+- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
+- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
## Contributor documentation
diff --git a/doc/api/README.md b/doc/api/README.md
index f76a253083f..ffe250df3ff 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -21,13 +21,7 @@
## Clients
-- [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
-- [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com)
-- [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
-- [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
-- [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
-- [node-gitlab](https://github.com/moul/node-gitlab) - Node.js
-- [NGitLab](https://github.com/Scooletz/NGitLab) - .NET
+Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients).
## Introduction
@@ -158,7 +152,7 @@ When an attribute is missing, you will get something like:
HTTP/1.1 400 Bad Request
Content-Type: application/json
-
+
{
"message":"400 (Bad request) \"title\" not given"
}
@@ -167,7 +161,7 @@ When a validation error occurs, error messages will be different. They will hold
HTTP/1.1 400 Bad Request
Content-Type: application/json
-
+
{
"message": {
"bio": [
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 74386615545..319f0b47386 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -211,3 +211,11 @@ Parameters:
It return 200 if succeed, 404 if the branch to be deleted does not exist
or 400 for other reasons. In case of an error, an explaining message is provided.
+
+Success response:
+
+```json
+{
+ "branch_name": "my-removed-branch"
+}
+```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index a935b146d37..ceeb683a6bf 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -95,6 +95,8 @@ GET /projects/:id/issues?state=closed
GET /projects/:id/issues?labels=foo
GET /projects/:id/issues?labels=foo,bar
GET /projects/:id/issues?labels=foo,bar&state=opened
+GET /projects/:id/issues?milestone=1.0.0
+GET /projects/:id/issues?milestone=1.0.0&state=opened
```
Parameters:
@@ -102,6 +104,7 @@ Parameters:
- `id` (required) - The ID of a project
- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
- `labels` (optional) - Comma-separated list of label names
+- `milestone` (optional) - Milestone title
## Single issue
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 9f6f6741093..0055e2e476f 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -186,6 +186,7 @@ Parameters:
"target_id": 830,
"target_type": "Issue",
"author_id": 1,
+ "author_username": "john",
"data": null,
"target_title": "Public project search field"
},
@@ -196,6 +197,7 @@ Parameters:
"target_id": null,
"target_type": null,
"author_id": 1,
+ "author_username": "john",
"data": {
"before": "50d4420237a9de7be1304607147aec22e4a14af7",
"after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
@@ -231,6 +233,7 @@ Parameters:
"target_id": 840,
"target_type": "Issue",
"author_id": 1,
+ "author_username": "john",
"data": null,
"target_title": "Finish & merge Code search PR"
}
@@ -281,6 +284,18 @@ Parameters:
- `visibility_level` (optional)
- `import_url` (optional)
+### Fork project
+
+Forks a project into the user namespace of the authenticated user.
+
+```
+POST /projects/fork/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID of the project to be forked
+
### Remove project
Removes a project including all associated resources (issues, merge requests etc.)
@@ -435,6 +450,7 @@ Parameters:
- `push_events` - Trigger hook on push events
- `issues_events` - Trigger hook on issues events
- `merge_requests_events` - Trigger hook on merge_requests events
+- `tag_push_events` - Trigger hook on push_tag events
### Edit project hook
@@ -452,6 +468,7 @@ Parameters:
- `push_events` - Trigger hook on push events
- `issues_events` - Trigger hook on issues events
- `merge_requests_events` - Trigger hook on merge_requests events
+- `tag_push_events` - Trigger hook on push_tag events
### Delete project hook
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index a412f60c0d9..8acf85d21c8 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -56,6 +56,7 @@ Parameters:
[
{
"name": "v1.0.0",
+ "message": "Release 1.0.0",
"commit": {
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"parents": [],
@@ -67,10 +68,11 @@ Parameters:
"committed_date": "2012-05-28T04:42:42-07:00",
"committer_email": "jack@example.com"
},
- "protected": false
}
]
```
+The message will be `nil` when creating a lightweight tag otherwise
+it will contain the annotation.
It returns 200 if the operation succeed. In case of an error,
405 with an explaining error message is returned.
diff --git a/doc/api/services.md b/doc/api/services.md
new file mode 100644
index 00000000000..ab9f9c00c67
--- /dev/null
+++ b/doc/api/services.md
@@ -0,0 +1,46 @@
+# Services
+
+## GitLab CI
+
+### Edit GitLab CI service
+
+Set GitLab CI service for a project.
+
+```
+PUT /projects/:id/services/gitlab-ci
+```
+
+Parameters:
+
+- `token` (required) - CI project token
+- `project_url` (required) - CI project url
+
+### Delete GitLab CI service
+
+Delete GitLab CI service settings for a project.
+
+```
+DELETE /projects/:id/services/gitlab-ci
+```
+
+## Hipchat
+
+### Edit Hipchat service
+
+Set Hipchat service for project.
+
+```
+PUT /projects/:id/services/hipchat
+```
+Parameters:
+
+- `token` (required) - Hipchat token
+- `room` (required) - Hipchat room name
+
+### Delete Hipchat service
+
+Delete Hipchat service for a project.
+
+```
+DELETE /projects/:id/services/hipchat
+```
diff --git a/doc/api/users.md b/doc/api/users.md
index 3fdd3a75e88..20e0d68977e 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -78,7 +78,8 @@ GET /users
"is_admin": false,
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
]
```
@@ -140,7 +141,8 @@ Parameters:
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
```
@@ -240,7 +242,8 @@ GET /user
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
```
diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md
new file mode 100644
index 00000000000..ddc0c8eac2b
--- /dev/null
+++ b/doc/customization/issue_closing.md
@@ -0,0 +1,5 @@
+# Issue closing pattern
+
+By default you can close issues from commit messages by saying 'Closes #12' or 'Fixed #101'.
+
+If you want to customize the message please do so in [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/73b92f85bcd6c213b845cc997843a969cf0906cf/config/gitlab.yml.example#L73)
diff --git a/doc/customization/libravatar.md b/doc/customization/libravatar.md
new file mode 100644
index 00000000000..4dffd3027a9
--- /dev/null
+++ b/doc/customization/libravatar.md
@@ -0,0 +1,69 @@
+# Use Libravatar service with GitLab
+
+GitLab by default supports [Gravatar](gravatar.com) avatar service.
+Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is
+[heavily based on gravatar](http://wiki.libravatar.org/api/).
+
+This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server.
+
+# Configuration
+
+In [gitlab.yml gravatar section](https://gitlab.com/gitlab-org/gitlab-ce/blob/672bd3902d86b78d730cea809fce312ec49d39d7/config/gitlab.yml.example#L122) set
+the configuration options as follows:
+
+## For HTTP
+
+```yml
+ gravatar:
+ enabled: true
+ # gravatar urls: possible placeholders: %{hash} %{size} %{email}
+ plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+## For HTTPS
+
+```yml
+ gravatar:
+ enabled: true
+ # gravatar urls: possible placeholders: %{hash} %{size} %{email}
+ ssl_url: "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+## Self-hosted
+
+If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the url will be different in the configuration
+but the important part is to provide the same placeholders so GitLab can parse the url correctly.
+
+For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is
+
+`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon`
+
+
+## Omnibus-gitlab example
+
+In `/etc/gitlab/gitlab.rb`:
+
+#### For http
+
+```ruby
+gitlab_rails['gravatar_enabled'] = true
+gitlab_rails['gravatar_plain_url'] = "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+#### For https
+
+```ruby
+gitlab_rails['gravatar_enabled'] = true
+gitlab_rails['gravatar_ssl_url'] = "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+
+Run `sudo gitlab-ctl reconfigure` for changes to take effect.
+
+
+## Default URL for missing images
+
+[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service.
+
+In order to use a different set other than `identicon`, replace `&d=identicon` portion of the url with another supported set.
+For example, you can use `retro` set in which case url would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
index 6000d389796..6c141d1fb7a 100644
--- a/doc/customization/welcome_message.md
+++ b/doc/customization/welcome_message.md
@@ -1,3 +1,7 @@
+# Customize the complete sign-in page (GitLab Enterprise Edition only)
+
+Please see [Branded login page](http://doc.gitlab.com/ee/customization/branded_login_page.html)
+
# Add a welcome message to the sign-in page (GitLab Community Edition)
It is possible to add a markdown-formatted welcome message to your GitLab
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 4624d9f60b6..c4813d22eaa 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -2,7 +2,7 @@
## Software delivery
-There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://www.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development.
+There are two editions of GitLab: [Enterprise Edition](https://about.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://about.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development.
EE releases are available not long after CE releases. To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme.
diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md
index b3e84183a41..ee16aedafe7 100644
--- a/doc/development/ci_setup.md
+++ b/doc/development/ci_setup.md
@@ -4,28 +4,30 @@ This document describes what services we use for testing GitLab and GitLab CI.
We currently use three CI services to test GitLab:
-1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/2/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
+1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org
3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
| Software @ configuration being tested | GitLab CI (ci.gitlab.org) | GitLab CI (GitHost.io) | Semaphore |
-|---------------------------------------|---------------------------|------------------------|-----------|
-| GitLab CE @ MySQL | ✓ | ✓ | |
-| GitLab CE @ PostgreSQL | | | ✓ |
-| GitLab EE @ MySQL | ✓ | | |
-| GitLab CI @ MySQL | ✓ | | |
-| GitLab CI @ PostgreSQL | | | ✓ |
-| GitLab CI Runner | ✓ | | ✓ |
-| GitLab Shell | ✓ | | ✓ |
-| GitLab Shell | ✓ | | ✓ |
+|---------------------------------------|---------------------------|---------------------------------------------------------------------------|-----------|
+| GitLab CE @ MySQL | ✓ | ✓ [Core team can trigger builds](https://gitlab-ce.githost.io/projects/4) | |
+| GitLab CE @ PostgreSQL | | | ✓ [Core team can trigger builds](https://semaphoreapp.com/gitlabhq/gitlabhq/branches/master) |
+| GitLab EE @ MySQL | ✓ | | |
+| GitLab CI @ MySQL | ✓ | | |
+| GitLab CI @ PostgreSQL | | | ✓ |
+| GitLab CI Runner | ✓ | | ✓ |
+| GitLab Shell | ✓ | | ✓ |
+| GitLab Shell | ✓ | | ✓ |
+
+Core team has access to trigger builds if needed for GitLab CE.
We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/build_script_gitlab_ce.md) for testing with GitLab CI.
# Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
-Language: Ruby
-Ruby verion: 2.1.2
-database.yml: pg
+- Language: Ruby
+- Ruby verion: 2.1.2
+- database.yml: pg
Build commands
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
index 1f3908f4e27..23c8365c340 100644
--- a/doc/development/shell_commands.md
+++ b/doc/development/shell_commands.md
@@ -22,6 +22,12 @@ FileUtils.mkdir_p "tmp/special/directory"
contents = `cat #{filename}`
# Correct
contents = File.read(filename)
+
+# Sometimes a shell command is just the best solution. The example below has no
+# user input, and is hard to implement correctly in Ruby: delete all files and
+# directories older than 120 minutes under /some/path, but not /some/path
+# itself.
+Gitlab::Popen.popen(%W(find /some/path -not -path /some/path -mmin +120 -delete))
```
This coding style could have prevented CVE-2013-4490.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 4fffe6c3489..459a21ae821 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -1,18 +1,22 @@
# Installation
+## Consider the Omnibus package installation
+
+Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
+
## Select Version to Install
Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below).
![Select latest branch](https://i.imgur.com/Lrdxk1k.png)
-If the highest number stable branch is unclear please check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version.
+If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
## Important Notes
This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880).
-This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://www.gitlab.com/downloads/).
+This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/).
This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation).
@@ -70,8 +74,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.0.0.tar.gz | tar xz
- cd git-2.0.0/
+ curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz
+ cd git-2.1.2/
+ ./configure
make prefix=/usr/local all
# Install into /usr/local/bin
@@ -87,7 +92,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby
-The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system ruby.
+The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby.
Remove the old Ruby 1.8 if present
@@ -122,7 +127,8 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Login to PostgreSQL
sudo -u postgres psql -d template1
- # Create a user for GitLab.
+ # Create a user for GitLab
+ # Do not type the 'template1=#', this is part of the prompt
template1=# CREATE USER git CREATEDB;
# Create the GitLab production database & grant all privileges on database
@@ -133,6 +139,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Try connecting to the new database with the new user
sudo -u git -H psql -d gitlabhq_production
+
+ # Quit the database session
+ gitlabhq_production> \q
## 5. Redis
@@ -146,6 +155,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+ # Grant permission to the socket to all members of the redis group
+ echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
+
+ # Create the directory which contains the socket
+ mkdir /var/run/redis
+ chown redis:redis /var/run/redis
+ chmod 755 /var/run/redis
+ # Persist the directory which contains the socket, if applicable
+ if [ -d /etc/tmpfiles.d ]; then
+ echo 'd /var/run/redis 0755 redis redis 10d -' | sudo tee -a /etc/tmpfiles.d/redis.conf
+ fi
# Activate the changes to redis.conf
sudo service redis-server restart
@@ -161,9 +181,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-3-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-4-stable gitlab
-**Note:** You can change `7-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `7-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -179,7 +199,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Make sure GitLab can write to the log/ and tmp/ directories
sudo chown -R git log/
sudo chown -R git tmp/
- sudo chmod -R u+rwX log/
+ sudo chmod -R u+rwX,go-w log/
sudo chmod -R u+rwX tmp/
# Create directory for satellites
@@ -258,7 +278,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install[v2.0.0] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
+ sudo -u git -H bundle exec rake gitlab:shell:install[v2.0.1] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
# By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows:
@@ -343,7 +363,7 @@ Validate your `gitlab` or `gitlab-ssl` Nginx config file with the following comm
sudo nginx -t
-You should receive `syntax is okay` and `test is successful` messages. If you receive errors check your `gitlab` or `gitlab-ssl` Nginx config file for typos, etc. as indiciated in the error message given.
+You should receive `syntax is okay` and `test is successful` messages. If you receive errors check your `gitlab` or `gitlab-ssl` Nginx config file for typos, etc. as indicated in the error message given.
### Restart
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 49edf36f574..ed194253148 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -24,7 +24,7 @@ For the installations options please see [the installation page on the GitLab we
On the above unsupported distributions is still possible to install GitLab yourself.
Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information.
-### Non Unix operating systems such as Windows
+### Non-Unix operating systems such as Windows
GitLab is developed for Unix operating systems.
GitLab does **not** run on Windows and we have no plans of supporting it in the near future.
@@ -53,8 +53,8 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
- 512MB is the absolute minimum but we do not recommend this amount of memory.
You will either need to configure 512MB or 1.5GB of swap space.
With 512MB of swap space you must configure only one unicorn worker.
-With one unicorn worker only git over ssh access will work because the git over http access requires two running workers (one worker to receive the user request and one worker for the authorization check).
-If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow http access but it will still be slow.
+With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
+If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow.
- 1GB RAM + 1GB swap supports up to 100 users
- **2GB RAM** is the **recommended** memory size and supports up to 500 users
- 4GB RAM supports up to 2,000 users
@@ -67,7 +67,7 @@ Notice: The 25 workers of Sidekiq will show up as separate processes in your pro
### Storage
-The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
+The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
@@ -85,12 +85,12 @@ Redis stores all user sessions and the background task queue.
The storage requirements for Redis are minimal, about 25kB per user.
Sidekiq processes the background jobs with a multithreaded process.
This process starts with the entire Rails stack (200MB+) but it can grow over time due to memory leaks.
-On a very active server (10.000 active users) the Sidekiq process can use 1GB+ of memory.
+On a very active server (10,000 active users) the Sidekiq process can use 1GB+ of memory.
-## Supported webbrowsers
+## Supported web browsers
- Chrome (Latest stable version)
-- Firefox (Latest released version)
+- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
- IE 10+
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index ee472ac3e3b..56b0d826adb 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -6,6 +6,95 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G
GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
+## Configuring GitLab for LDAP integration
+
+To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
+In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server.
+
+Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration.
+The old LDAP integration syntax still works in GitLab 7.4.
+If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab.
+
+```ruby
+# For omnibus packages
+gitlab_rails['ldap_enabled'] = true
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
+main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+
+ host: '_your_ldap_server'
+ port: 636
+ uid: 'sAMAccountName'
+ method: 'ssl' # "tls" or "ssl" or "plain"
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
+
+ # This setting specifies if LDAP server is Active Directory LDAP server.
+ # For non AD servers it skips the AD specific queries.
+ # If your LDAP server is not AD, set this to false.
+ active_directory: true
+
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
+
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ user_filter: ''
+
+# GitLab EE only: add more LDAP servers
+# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+# so that GitLab can remember which LDAP server a user belongs to.
+# uswest2:
+# label:
+# host:
+# ....
+EOS
+```
+
+If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
+
+```
+production:
+ # snip...
+ ldap:
+ enabled: false
+ servers:
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+ # snip...
+```
+
## Enabling LDAP sign-in for existing GitLab users
When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
@@ -24,15 +113,22 @@ If you want to limit all GitLab access to a subset of the LDAP users on your LDA
The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515).
```ruby
-# For omnibus-gitlab
-gitlab_rails['ldap_user_filter'] = '(employeeType=developer)'
+# For omnibus packages; new LDAP server syntax
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+main:
+ # snip...
+ user_filter: '(employeeType=developer)'
+EOS
```
```yaml
-# For installations from source
+# For installations from source; new LDAP server syntax
production:
ldap:
- user_filter: '(employeeType=developer)'
+ servers:
+ main:
+ # snip...
+ user_filter: '(employeeType=developer)'
```
Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax:
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 95cb0c6fae2..f2e73f272ef 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -4,15 +4,23 @@
To enable Slack integration you must create an Incoming WebHooks integration on Slack;
-1. Sign in to [Slack](https://slack.com) (https://YOURSUBDOMAIN.slack.com/services)
-1. Click on the Integrations menu at the top of the page.
-1. Add a new Integration.
+1. [Sign in to Slack](https://slack.com/signin)
+
+1. Select **Configure Integrations** from the dropdown next to your team name.
+
+1. Select the **All Services** tab
+
+1. Click **Add** next to Incoming Webhooks
+
1. Pick Incoming WebHooks
-1. Choose the channel name you want to send notifications to, in the Settings section
-1. Add Integrations.
- - Optional step; You can change bot's name and avatar by clicking "change the name of your bot", and "change the icon" after that you have to click "Save settings".
-Now, Slack is ready to get external hooks. Before you leave this page don't forget to get the Token that you'll need on GitLab. You can find it by clicking Expand button, located in the "Instructions for creating Incoming WebHooks" section. It's a random alpha-numeric text 24 characters long.
+1. Choose the channel name you want to send notifications to
+
+1. Click **Add Incoming WebHooks Integration**Add Integrations.
+ - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
+
+1. Copy the **Webhook URL**, we'll need this later for GitLab.
+
## On GitLab
@@ -26,10 +34,8 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this
1. Fill in your Slack details
- - Mark as active it
- - Type your subdomain's prefix (If your subdomain is https://somedomain.slack.com you only have to type the somedomain)
- - Type in the token you got from Slack
- - Type in the channel name you want to use (eg. #announcements)
+ - Mark it as active
+ - Paste in the webhook url you got from Slack
Have fun :)
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 5627fd0659f..edb7a975503 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -10,6 +10,7 @@
* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
* [Emoji](#emoji)
* [Special GitLab references](#special-gitlab-references)
+* [Task lists](#task-lists)
**[Standard Markdown](#standard-markdown)**
@@ -177,6 +178,24 @@ GFM will recognize the following:
- 1234567 : for commits
- \[file\](path/to/file) : for file references
+GFM also recognizes references to commits, issues, and merge requests in other projects:
+
+- namespace/project#123 : for issues
+- namespace/project!123 : for merge requests
+- namespace/project@1234567 : for commits
+
+## Task Lists
+
+You can add task lists to merge request and issue descriptions to keep track of to-do items. To create a task, add an unordered list to the description in an issue or merge request, formatted like so:
+
+```no-highlight
+* [x] Completed task
+* [ ] Unfinished task
+ * [x] Nested task
+```
+
+Task lists can only be created in descriptions, not in titles or comments. Task item state can be managed by editing the description's Markdown or by clicking the rendered checkboxes.
+
# Standard Markdown
## Headers
@@ -491,6 +510,10 @@ Code above produces next output:
| cell 1 | cell 2 |
| cell 3 | cell 4 |
+**Note**
+
+The row of dashes between the table header and body must have at least three dashes in each column.
+
## References
- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
diff --git a/doc/project_services/bamboo.md b/doc/project_services/bamboo.md
new file mode 100644
index 00000000000..51668128c62
--- /dev/null
+++ b/doc/project_services/bamboo.md
@@ -0,0 +1,60 @@
+# Atlassian Bamboo CI Service
+
+GitLab provides integration with Atlassian Bamboo for continuous integration.
+When configured, pushes to a project will trigger a build in Bamboo automatically.
+Merge requests will also display CI status showing whether the build is pending,
+failed, or completed successfully. It also provides a link to the Bamboo build
+page for more information.
+
+Bamboo doesn't quite provide the same features as a traditional build system when
+it comes to accepting webhooks and commit data. There are a few things that
+need to be configured in a Bamboo build plan before GitLab can integrate.
+
+## Setup
+
+### Complete these steps in Bamboo:
+
+1. Navigate to a Bamboo build plan and choose 'Configure plan' from the 'Actions'
+dropdown.
+1. Select the 'Triggers' tab.
+1. Click 'Add trigger'.
+1. Enter a description such as 'GitLab trigger'
+1. Choose 'Repository triggers the build when changes are committed'
+1. Check one or more repositories checkboxes
+1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a
+whitelist of IP addresses that are allowed to trigger Bamboo builds.
+1. Save the trigger.
+1. In the left pane, select a build stage. If you have multiple build stages
+you want to select the last stage that contains the git checkout task.
+1. Select the 'Miscellaneous' tab.
+1. Under 'Pattern Match Labelling' put '${bamboo.repository.revision.number}'
+in the 'Labels' box.
+1. Save
+
+Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo
+service in GitLab
+
+### Complete these steps in GitLab:
+
+1. Navigate to the project you want to configure to trigger builds.
+1. Select 'Settings' in the top navigation.
+1. Select 'Services' in the left navigation.
+1. Click 'Atlassian Bamboo CI'
+1. Select the 'Active' checkbox.
+1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com'
+1. Enter the build key from your Bamboo build plan. Build keys are a short,
+all capital letter, identifier that is unique. It will be something like PR-BLD
+1. If necessary, enter username and password for a Bamboo user that has
+access to trigger the build plan. Leave these fields blank if you do not require
+authentication.
+1. Save or optionally click 'Test Settings'. Please note that 'Test Settings'
+will actually trigger a build in Bamboo.
+
+## Troubleshooting
+
+If builds are not triggered, these are a couple of things to keep in mind.
+
+1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger
+IP addresses'.
+1. Remember that GitLab only triggers builds on push events. A commit via the
+web interface will not trigger CI currently.
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
new file mode 100644
index 00000000000..20a69a211dd
--- /dev/null
+++ b/doc/project_services/project_services.md
@@ -0,0 +1,18 @@
+# Project Services
+
+__Project integrations with external services for continuous integration and more.__
+
+## Services
+
+- Assemblia
+- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continous integration.
+- Build box
+- Campfire
+- Emails on push
+- Flowdock
+- Gemnasium
+- GitLab CI
+- Hipchat
+- PivotalTracker
+- Pushover
+- Slack
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 4db08b1ba20..b4581e2a07a 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -10,10 +10,10 @@ The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
```
-# omnibus-gitlab
+# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create
-# installation from source or cookbook
+# if you've installed GitLab from source or using the cookbook
bundle exec rake gitlab:backup:create RAILS_ENV=production
```
@@ -46,10 +46,96 @@ Deleting tmp directories...[DONE]
Deleting old backups... [SKIPPING]
```
+## Upload backups to remote (cloud) storage
+
+Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates.
+It uses the [Fog library](http://fog.io/) to perform the upload.
+In the example below we use Amazon S3 for storage.
+But Fog also lets you use [other storage providers](http://fog.io/storage/).
+
+For omnibus packages:
+
+```ruby
+gitlab_rails['backup_upload_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-west-1',
+ 'aws_access_key_id' => 'AKIAKIAKI',
+ 'aws_secret_access_key' => 'secret123'
+}
+gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
+```
+
+For installations from source:
+
+```yaml
+ backup:
+ # snip
+ upload:
+ # Fog storage connection settings, see http://fog.io/storage/ .
+ connection:
+ provider: AWS
+ region: eu-west-1
+ aws_access_key_id: AKIAKIAKI
+ aws_secret_access_key: 'secret123'
+ # The remote 'directory' to store your backups. For S3, this would be the bucket name.
+ remote_directory: 'my.s3.bucket'
+```
+
+If you are uploading your backups to S3 you will probably want to create a new
+IAM user with restricted access rights. To give the upload user access only for
+uploading backups create the following IAM profile, replacing `my.s3.bucket`
+with the name of your bucket:
+
+```json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "Stmt1412062044000",
+ "Effect": "Allow",
+ "Action": [
+ "s3:AbortMultipartUpload",
+ "s3:GetBucketAcl",
+ "s3:GetBucketLocation",
+ "s3:GetObject",
+ "s3:GetObjectAcl",
+ "s3:ListBucketMultipartUploads",
+ "s3:PutObject",
+ "s3:PutObjectAcl"
+ ],
+ "Resource": [
+ "arn:aws:s3:::my.s3.bucket/*"
+ ]
+ },
+ {
+ "Sid": "Stmt1412062097000",
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetBucketLocation",
+ "s3:ListAllMyBuckets"
+ ],
+ "Resource": [
+ "*"
+ ]
+ },
+ {
+ "Sid": "Stmt1412062128000",
+ "Effect": "Allow",
+ "Action": [
+ "s3:ListBucket"
+ ],
+ "Resource": [
+ "arn:aws:s3:::my.s3.bucket"
+ ]
+ }
+ ]
+}
+```
+
## Storing configuration files
Please be informed that a backup does not store your configuration files.
-If you use Omnibus-GitLab please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
+If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
If you have a cookbook installation there should be a copy of your configuration in Chef.
If you have a manual installation please consider backing up your gitlab.yml file and any SSL keys and certificates.
@@ -58,7 +144,7 @@ If you have a manual installation please consider backing up your gitlab.yml fil
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
```
-# omnibus-gitlab
+# Omnibus package installation
sudo gitlab-rake gitlab:backup:restore
# installation from source or cookbook
@@ -104,8 +190,9 @@ Deleting tmp directories...[DONE]
## Configure cron to make daily backups
-For omnibus-gitlab, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup .
+For Omnibus package installations, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup .
+For installation from source or cookbook:
```
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index 39b1a52a44d..bb229e8acbb 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -1,28 +1,45 @@
-# Import
+# Import bare repositories into your GitLab instance
-### Import bare repositories into GitLab project instance
+## Notes
-Notes:
+- The owner of the project will be the first admin
+- The groups will be created as needed
+- The owner of the group will be the first admin
+- Existing projects will be skipped
-* project owner will be a first admin
-* groups will be created as needed
-* group owner will be the first admin
-* existing projects will be skipped
+## How to use
-How to use:
+### Create a new folder inside the git repositories path. This will be the name of the new group.
-1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path)
-2. run the command below
+- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed
+it in the `/etc/gitlab/gitlab.rb` file.
+- For manual installations, it is usually located at: `/home/git/repositories` or you can see where
+your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry.
+### Copy your bare repositories inside this newly created folder:
+
+```
+$ cp -r /old/git/foo.git/ /home/git/repositories/new_group/
+```
+
+### Run the command below depending on your type of installation:
+
+#### Omnibus Installation
+
+```
+$ sudo gitlab-rake gitlab:import:repos
```
-# omnibus-gitlab
-sudo gitlab-rake gitlab:import:repos
-# installation from source or cookbook
-bundle exec rake gitlab:import:repos RAILS_ENV=production
+#### Manual Installation
+
+Before running this command you need to change the directory to where your GitLab installation is located:
+
+```
+$ cd /home/git/gitlab
+$ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production
```
-Example output:
+#### Example output
```
Processing abcd.git
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index c46a3ed9c93..affe634587d 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -2,40 +2,42 @@
NOTE: This is a guide for GitLab developers.
-# **15th - Code Freeze & Release Manager**
+# **7 workdays before release - Code Freeze & Release Manager**
-### **1. Stop merging in code, except for important bugfixes**
+### **1. Stop merging in code, except for important bug fixes**
### **2. Release Manager**
A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated.
### **3. Create an overall issue**
-Name it "Release x.x.x" for easier searching.
+
+Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching.
+Replace the dates with actual dates based on the number of workdays before the release.
```
-15th:
+Xth:
* Update the changelog (#LINK)
* Triage the omnibus-gitlab milestone
-16th:
+Xth:
* Merge CE in to EE (#LINK)
* Close the omnibus-gitlab milestone
-17th:
+Xth:
* Create x.x.0.rc1 (#LINK)
* Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package)
-18th:
+Xth:
* Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
* Regression issue and tweet about rc1 (#LINK)
* Start blog post (#LINK)
-21th:
+Xth:
* Do QA and fix anything coming out of it (#LINK)
@@ -43,16 +45,13 @@ Name it "Release x.x.x" for easier searching.
* Release CE and EE (#LINK)
-23rd:
-
-* Prepare package for GitLab.com release (#LINK)
+Xth:
-24th:
+* * Deploy to GitLab.com (#LINK)
-* Deploy to GitLab.com (#LINK)
```
-### **4. Update Changelog**
+### **4. Update changelog**
Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing.
@@ -60,26 +59,26 @@ Any changes not yet added to the changelog are added by lead developer and in th
Ensure that there is enough time to incorporate the findings of the release candidate, etc.
-# **16th - Merge the CE into EE**
+# **6 workdays before release- Merge the CE into EE**
Do this via a merge request.
-# **17th - Create RC1**
+# **5 workdays before release - Create RC1**
The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub.
### **1. Update the installation guide**
1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay)
-1. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782)
-1. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794)
+1. Check the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782)
+1. Check the [Git version](/lib/tasks/gitlab/check.rake#L794)
1. There might be other changes. Ask around.
-### **2. Create an update guides**
+### **2. Create update guides**
-1. Create: CE update guide from previous version. Like `from-6-8-to-6.9`
+1. Create: CE update guide from previous version. Like `7.3-to-7.4.md`
1. Create: CE to EE update guide in EE repository for latest version.
-1. Update: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/6.0-to-6.x.md to latest version.
+1. Update: `6.x-or-7.x-to-7.x.md` to latest version.
It's best to copy paste the previous guide and make changes where necessary.
The typical steps are listed below with any points you should specifically look at.
@@ -98,9 +97,9 @@ List any major changes here, so the user is aware of them before starting to upg
#### 3. Do users need to update dependencies like `git`?
-- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release.
+- Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release.
-- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release.
+- Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release.
#### 4. Get latest code
@@ -112,19 +111,19 @@ List any major changes here, so the user is aware of them before starting to upg
Check if any of these changed since last release:
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab-ssl>
+- [lib/support/nginx/gitlab](/lib/support/nginx/gitlab)
+- [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl)
- <https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/gitlab.yml.example>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/unicorn.rb.example>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.mysql>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.postgresql>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/initializers/rack_attack.rb.example>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/resque.yml.example>
+- [config/gitlab.yml.example](/config/gitlab.yml.example)
+- [config/unicorn.rb.example](/config/unicorn.rb.example)
+- [config/database.yml.mysql](/config/database.yml.mysql)
+- [config/database.yml.postgresql](/config/database.yml.postgresql)
+- [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example)
+- [config/resque.yml.example](/config/resque.yml.example)
#### 8. Need to update init script?
-Check if the `init.d/gitlab` script changed since last release: <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/init.d/gitlab>
+Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab)
#### 9. Start application
@@ -144,38 +143,41 @@ Make sure the code quality indicators are green / good.
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
-### **4. Set VERSION**
+### **4. Run release tool**
-Change version in VERSION to `x.x.0.rc1`.
+**Make sure EE `master` has latest changes from CE `master`**
-### **5. Tag**
-
-Create an annotated tag that points to the version change commit:
+Get release tools
```
-git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1'
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
```
-### **6. Create stable branches**
+Release candidate creates stable branch from master.
+So we need to sync master branch between all CE remotes. Also do same for EE.
-For GitLab EE, append `-ee` to the branch.
+```
+bundle exec rake sync
+```
-`x-x-stable-ee`
+Create release candidate and stable branch:
```
-git checkout master
-git pull
-git checkout -b x-x-stable
-git push <remote> x-x-stable
+bundle exec rake release["x.x.0.rc1"]
```
Now developers can use master for merging new features.
So you should use stable branch for future code chages related to release.
-# **18th - Release RC1**
+# **4 workdays before release - Release RC1**
+
+### **1. Determine QA person
-### **1. Update GitLab.com**
+Notify person of QA day.
+
+### **2. Update GitLab.com**
Merge the RC1 EE code into GitLab.com.
Once the build is green, create a package.
@@ -183,18 +185,20 @@ If there are big database migrations consider testing them with the production d
Try to deploy in the morning.
It is important to do this as soon as possible, so we can catch any errors before we release the full version.
-### **2. Prepare the blog post**
+### **3. Prepare the blog post**
- Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
- Check the changelog of CE and EE for important changes.
- Create a WIP MR for the blog post
- Ask Dmitriy to add screenshots to the WIP MR.
-- Decide with team who will be the MVP user.
+- Decide with team who will be the MVP user.
+- Create WIP MR for adding MVP to MVP page on website
- Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
+- Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master)
- Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor)
- After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments
-### **3. Create a regressions issue**
+### **4. Create a regressions issue**
On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text:
@@ -211,7 +215,7 @@ Tweet about the RC release:
> GitLab x.x.0.rc1 is out. This release candidate is only suitable for testing. Please link regressions issues from LINK_TO_REGRESSION_ISSUE
-# **21st - Preparation**
+# **1 workdays before release - Preparation**
### **1. Pre QA merge**
@@ -234,91 +238,64 @@ create an issue about it in order to discuss the next steps after the release.
# **22nd - Release CE and EE**
-For GitLab EE, append `-ee` to the branches and tags.
-
-`x-x-stable-ee`
+**Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`**
-`v.x.x.0-ee`
-Note: Merge CE into EE if needed.
+### **1. Release code**
-### **1. Set VERSION to x.x.x and push**
-
-- Change the GITLAB_SHELL_VERSION file in `master` of the CE repository if the version changed.
-- Change the GITLAB_SHELL_VERSION file in `master` of the EE repository if the version changed.
-- Change the VERSION file in `master` branch of the CE repository and commit and push to origin.
-- Change the VERSION file in `master` branch of the EE repository and commit and push to origin.
-
-### **2. Update installation.md**
-
-Update [installation.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) to the newest version in master.
-
-### **3. Push latest changes from x-x-stable branch to dev.gitlab.org**
+Get release tools
```
-git checkout -b x-x-stable
-git push origin x-x-stable
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
```
-### **4. Build the Omnibus packages**
-
-Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md).
-This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase.
-
-### **5. Create annotated tag vx.x.x**
-
-In `x-x-stable` branch check for the SHA-1 of the commit with VERSION file changed. Tag that commit,
+Bump version, create release tag and push to remotes:
```
-git tag -a vx.x.0 -m 'Version x.x.0' xxxxx
+bundle exec rake release["x.x.0"]
```
-where `xxxxx` is SHA-1.
-### **6. Push the tag and x-x-stable branch to the remotes**
+### **2. Update installation.md**
-For GitLab CE, push to dev, GitLab.com and GitHub.
+Update [installation.md](/doc/install/installation.md) to the newest version in master.
-For GitLab EE, push to the subscribers repo.
-Make sure the branch is marked 'protected' on each of the remotes you pushed to.
+### **3. Build the Omnibus packages**
+
+Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md).
+This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase.
-```
-git push <remote> x-x-stable(-ee)
-git push <remote> vx.x.0
-```
-### **7. Publish packages for new release**
+### **4. Publish packages for new release**
Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository.
-### **8. Publish blog for new release**
+### **5. Publish blog for new release**
Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository.
-### **9. Tweet to blog**
+### **6. Tweet to blog**
Send out a tweet to share the good news with the world.
List the most important features and link to the blog post.
Proposed tweet for CE "GitLab X.X is released! It brings *** <link-to-blogpost>"
-### **10. Send out the newsletter**
-
-Send out an email to the 'GitLab Newsletter' mailing list on MailChimp.
-Replicate the former release newsletter and modify it accordingly.
-**Do not forget to edit `Subject line` and regenerate `Plain-Text Email` from HTML source**
-
-Include a link to the blog post and keep it short.
+# **1 workday after release - Update GitLab.com**
-Proposed email text:
-"We have released a new version of GitLab. See our blog post(<link>) for more information."
+Update GitLab.com from RC1 to the released package.
+# **25th - Release GitLab CI**
-# **23rd - Optional Patch Release**
-
-# **24th - Update GitLab.com**
+- Create the update guid `doc/x.x-to-x.x.md`.
+- Update CHANGELOG
+- Bump version
+- Create annotated tags `git tag -a vx.x.0 -m 'Version x.x.0' xxxxx`
+- Create stable branch `x-x-stable`
+- Create GitHub release post
+- Post to blog about release
+- Post to twitter
-Merge the stable release into GitLab.com. Once the build is green deploy the next morning.
-# **25th - Release GitLab CI**
diff --git a/doc/release/patch.md b/doc/release/patch.md
index bcc14568fc8..6ed56427e9a 100644
--- a/doc/release/patch.md
+++ b/doc/release/patch.md
@@ -10,6 +10,8 @@ Otherwise include it in the monthly release and note there was a regression fix
## Release Procedure
+### Preparation
+
1. Verify that the issue can be reproduced
1. Note in the 'GitLab X.X regressions' that you will create a patch
1. Create an issue on private GitLab development server
@@ -17,15 +19,36 @@ Otherwise include it in the monthly release and note there was a regression fix
1. Fix the issue on a feature branch, do this on the private GitLab development server
1. Consider creating and testing workarounds
1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch
+1. Make sure that the build has passed and all tests are passing
1. In a separate commit in the stable branch update the CHANGELOG
1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
-1. In a separate commit in the stable branch update the VERSION
-1. Create an annotated tag vX.X.X for CE and another patch release for EE `git tag -a vx.x.x -m 'Version x.x.x'`
-1. Make sure that the build has passed and all tests are passing
-1. Push the code and the tags to all the CE and EE repositories
+
+### Bump version
+
+Get release tools
+
+```
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
+```
+
+Bump version in stable branch, create release tag and push to remotes:
+
+```
+bundle exec rake release["x.x.x"]
+```
+
+Or if you need to release only EE:
+
+```
+CE=false be rake release['x.x.x']
+```
+
+### Release
+
1. Apply the patch to GitLab Cloud and the private GitLab development server
1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md)
1. Cherry-pick the changelog update back into master
-1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog
+1. Create and publish a blog post
+1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing and link to the blog post
1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only)
-1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the patch is EE only)
diff --git a/doc/release/security.md b/doc/release/security.md
index da442de6ee1..79d23c02ea4 100644
--- a/doc/release/security.md
+++ b/doc/release/security.md
@@ -8,7 +8,7 @@ Do a security release when there is a critical issue that needs to be addresses
## Security vulnerability disclosure
-Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
+Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Release Procedure
@@ -21,7 +21,7 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c
1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only)
1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq)
1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number
-1. Add the security researcher to the [Security Researcher Acknowledgments list](http://www.gitlab.com/vulnerability-acknowledgements/)
+1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/)
1. Thank the security researcher in an email for their cooperation
1. Update the blog post and the CHANGELOG when we receive the CVE number
diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md
index 897cd0b91fa..cde679598f7 100644
--- a/doc/update/4.2-to-5.0.md
+++ b/doc/update/4.2-to-5.0.md
@@ -195,6 +195,12 @@ sudo rm -R tmp
sudo -u git -H mkdir tmp
sudo chmod -R u+rwX tmp/
+# create directory for pids, make sure GitLab can write to it
+sudo -u git -H mkdir tmp/pids/
+sudo chmod -R u+rwX tmp/pids/
+
+# if you are already running a newer version of GitLab check that installation guide for other tmp folders you need to create
+
# reboot system
sudo reboot
diff --git a/doc/update/6.0-to-7.3.md b/doc/update/6.x-or-7.x-to-7.4.md
index ec4cac2f4fa..dd90ae3bf3d 100644
--- a/doc/update/6.0-to-7.3.md
+++ b/doc/update/6.x-or-7.x-to-7.4.md
@@ -1,6 +1,6 @@
-# From 6.0 to 7.3
+# From 6.x or 7.x to 7.4
-# GitLab 7.3 has not been released yet!
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.4.
## Global issue numbers
@@ -13,7 +13,11 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are
no longer allowed however so those will be removed from your tags during the
database migrations for GitLab 7.2.
-## 0. Backup
+## 0. Stop server
+
+ sudo service gitlab stop
+
+## 1. Backup
It's useful to make a backup just in case things go south:
(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
@@ -23,10 +27,6 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
-## 1. Stop server
-
- sudo service gitlab stop
-
## 2. Update Ruby
If you are still using Ruby 1.9.3 or below, you will need to update Ruby.
@@ -64,12 +64,13 @@ sudo gem install bundler --no-ri --no-rdoc
```bash
cd /home/git/gitlab
sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
-sudo -u git -H git checkout 7-3-stable
+sudo -u git -H git checkout 7-4-stable
```
OR
@@ -77,7 +78,7 @@ OR
For GitLab Enterprise Edition:
```bash
-sudo -u git -H git checkout 7-3-stable-ee
+sudo -u git -H git checkout 7-4-stable-ee
```
## 4. Install additional packages
@@ -98,6 +99,8 @@ sudo apt-get install pkg-config cmake
sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+ # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
+ sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
# Activate the changes to redis.conf
sudo service redis-server restart
# Add git to the redis group
@@ -116,7 +119,7 @@ sudo apt-get install pkg-config cmake
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.0.0
+sudo -u git -H git checkout v2.0.1
```
## 7. Install libs, migrations, etc.
@@ -151,14 +154,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
TIP: to see what changed in `gitlab.yml.example` in this release use next command:
```
-git diff 6-0-stable:config/gitlab.yml.example 7-3-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-4-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/unicorn.rb.example but with your settings.
-* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.0/config.yml.example but with your settings.
-* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.1/config.yml.example but with your settings.
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings.
* Copy rack attack middleware config
```bash
@@ -195,6 +198,77 @@ When using Google omniauth login, changes of the Google account required.
Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/).
More details can be found at the [integration documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/google.md).
+## 12. Optional optimizations for GitLab setups with MySQL databases
+
+Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
+
+```
+# Stop GitLab
+sudo service gitlab stop
+
+# Secure your MySQL installation (added in GitLab 6.2)
+sudo mysql_secure_installation
+
+# Login to MySQL
+mysql -u root -p
+
+# do not type the 'mysql>', this is part of the prompt
+
+# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all outputed SQL statements
+
+# Convert all tables to correct character set
+SET foreign_key_checks = 0;
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all outputed SQL statements
+
+# turn foreign key checks back on
+SET foreign_key_checks = 1;
+
+# Find MySQL users
+mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
+
+# If git user exists and gitlab user does not exist
+# you are done with the database cleanup tasks
+mysql> \q
+
+# If both users exist skip to Delete gitlab user
+
+# Create new user for GitLab (changed in GitLab 6.4)
+# change $password in the command below to a real password you pick
+mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+
+# Grant the git user necessary permissions on the database
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
+
+# Delete the old gitlab user
+mysql> DELETE FROM mysql.user WHERE user='gitlab';
+
+# Quit the database session
+mysql> \q
+
+# Try connecting to the new database with the new user
+sudo -u git -H mysql -u git -p -D gitlabhq_production
+
+# Type the password you replaced $password with earlier
+
+# You should now see a 'mysql>' prompt
+
+# Quit the database session
+mysql> \q
+
+# Update database configuration details
+# See config/database.yml.mysql for latest recommended configuration details
+# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
+# Set production -> pool: 10 (updated in GitLab 5.3)
+# Set production -> username: git
+# Set production -> password: the password your replaced $password with earlier
+sudo -u git -H editor /home/git/gitlab/config/database.yml
+```
+
## Things went south? Revert to previous version (6.0)
### 1. Revert the code to the previous version
diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md
index 48f515ad467..ebdd4ff60fa 100644
--- a/doc/update/7.2-to-7.3.md
+++ b/doc/update/7.2-to-7.3.md
@@ -18,6 +18,7 @@ sudo service gitlab stop
```bash
cd /home/git/gitlab
sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
@@ -39,7 +40,7 @@ sudo -u git -H git checkout 7-3-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.0.0
+sudo -u git -H git checkout v2.0.1
```
### 4. Install libs, migrations, etc.
@@ -73,7 +74,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
# Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
- sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
+ sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
# Activate the changes to redis.conf
sudo service redis-server restart
# Add git to the redis group
diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md
new file mode 100644
index 00000000000..3f471500c82
--- /dev/null
+++ b/doc/update/7.3-to-7.4.md
@@ -0,0 +1,191 @@
+# From 7.3 to 7.4
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-4-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-4-stable-ee
+```
+
+### 3. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 4. Update config files
+
+#### New configuration options for gitlab.yml
+
+There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
+
+```
+git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gitlab.yml.example
+```
+
+#### Change timeout for unicorn
+
+```
+# set timeout to 60
+sudo -u git -H editor config/unicorn.rb
+```
+
+#### Change nginx https settings
+
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting
+
+#### MySQL Databases: Update database.yml config file
+
+* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql)
+
+
+### 5. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+
+### 7. Optional optimizations for GitLab setups with MySQL databases
+
+Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
+
+```
+# Stop GitLab
+sudo service gitlab stop
+
+# Secure your MySQL installation (added in GitLab 6.2)
+sudo mysql_secure_installation
+
+# Login to MySQL
+mysql -u root -p
+
+# do not type the 'mysql>', this is part of the prompt
+
+# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all outputed SQL statements
+
+# Convert all tables to correct character set
+SET foreign_key_checks = 0;
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all outputed SQL statements
+
+# turn foreign key checks back on
+SET foreign_key_checks = 1;
+
+# Find MySQL users
+mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
+
+# If git user exists and gitlab user does not exist
+# you are done with the database cleanup tasks
+mysql> \q
+
+# If both users exist skip to Delete gitlab user
+
+# Create new user for GitLab (changed in GitLab 6.4)
+# change $password in the command below to a real password you pick
+mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+
+# Grant the git user necessary permissions on the database
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
+
+# Delete the old gitlab user
+mysql> DELETE FROM mysql.user WHERE user='gitlab';
+
+# Quit the database session
+mysql> \q
+
+# Try connecting to the new database with the new user
+sudo -u git -H mysql -u git -p -D gitlabhq_production
+
+# Type the password you replaced $password with earlier
+
+# You should now see a 'mysql>' prompt
+
+# Quit the database session
+mysql> \q
+
+# Update database configuration details
+# See config/database.yml.mysql for latest recommended configuration details
+# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
+# Set production -> pool: 10 (updated in GitLab 5.3)
+# Set production -> username: git
+# Set production -> password: the password your replaced $password with earlier
+sudo -u git -H editor /home/git/gitlab/config/database.yml
+
+# Run thorough check
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+
+## Things went south? Revert to previous version (7.3)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.2 to 7.3](7.2-to-7.3.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+
+
+
diff --git a/doc/update/README.md b/doc/update/README.md
index 9a6f09b370a..30e9137d7b7 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -1,4 +1,16 @@
-- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update)
-- [Upgrader](upgrader.md)
-- [Patch versions](patch_versions.md)
-- [MySQL to PostgreSQL](mysql_to_postgresql.md)
+Depending on the installation method and your GitLab version, there are multiple update guides. Choose one that fits your needs.
+
+## Omnibus Packages
+
+- [Omnibus update guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md) contains the steps needed to update a GitLab [package](https://about.gitlab.com/downloads/).
+
+## Manual Installation
+
+- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab manually.
+- [The CE to EE update guides](https://gitlab.com/subscribers/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status.
+- [Upgrader](upgrader.md) is an automatic ruby script that performs the update for manual installations.
+- [Patch versions](patch_versions.md) guide includes the steps needed for a patch version, eg. 6.2.0 to 6.2.1.
+
+## Miscellaneous
+
+- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostrgreSQL.
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index c4a77d12800..629c46ad030 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -26,16 +26,14 @@ sudo -u git -H git checkout LATEST_TAG
Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`.
-### 3. Update gitlab-shell if it is not the latest version
+### 3. Update gitlab-shell to the corresponding version
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout LATEST_TAG
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
```
-Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`.
-
### 4. Install libs, migrations, etc.
```bash
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index 9943e9205db..44e18a9ed42 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -43,28 +43,31 @@ Check if GitLab and its dependencies are configured correctly:
If all items are green, then congratulations upgrade is complete!
-## 5. Upgrade GitLab Shell (if needed)
+## 5. Upgrade GitLab Shell
-If the `gitlab:check` task reports an outdated version of `gitlab-shell` you should upgrade it.
-
-Upgrade it by running the commands below after replacing 2.0.0 with the correct version number:
+GitLab Shell might be outdated, running the commands below ensures you're using a compatible version:
```
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.0.0
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
```
## One line upgrade command
You've read through the entire guide and probably already did all the steps one by one.
-Here is a one line command with step 1 to 4 for the next time you upgrade:
+Here is a one line command with step 1 to 5 for the next time you upgrade:
```bash
-cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
+cd /home/git/gitlab; \
+ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
sudo service gitlab stop; \
if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \
+ cd /home/git/gitlab-shell; \
+ sudo -u git -H git fetch; \
+ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \
+ cd /home/git/gitlab; \
sudo service gitlab start; \
sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 16817d1933d..f19517c0f18 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -63,6 +63,11 @@ Triggered when a new issue is created or an existing issue was updated/closed/re
```json
{
"object_kind": "issue",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
"object_attributes": {
"id": 301,
"title": "New API: create/update/delete file",
@@ -92,6 +97,11 @@ Triggered when a new merge request is created or an existing merge request was u
```json
{
"object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
"object_attributes": {
"id": 99,
"target_branch": "master",
@@ -109,7 +119,31 @@ Triggered when a new merge request is created or an existing merge request was u
"merge_status": "unchecked",
"target_project_id": 14,
"iid": 1,
- "description": ""
+ "description": "",
+ "source": {
+ "name": "awesome_project",
+ "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
+ "http_url": "http://example.com/awesome_space/awesome_project.git",
+ "visibility_level": 20,
+ "namespace": "awesome_space"
+ },
+ "target": {
+ "name": "awesome_project",
+ "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
+ "http_url": "http://example.com/awesome_space/awesome_project.git",
+ "visibility_level": 20,
+ "namespace": "awesome_space"
+ },
+ "last_commit": {
+ "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "message": "fixed readme",
+ "timestamp": "2012-01-03T23:36:29+02:00",
+ "url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ }
+ }
}
}
```
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index b18f62a1fa6..c26d85e9955 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -3,3 +3,6 @@
- [Authorization for merge requests](authorization_for_merge_requests.md)
- [Groups](groups.md)
- [Labels](labels.md)
+- [GitLab Flow](gitlab_flow.md)
+- [Notifications](notifications.md)
+- [Migrating from SVN to GitLab](migrating_from_svn.md)
diff --git a/doc/workflow/ci_mr.png b/doc/workflow/ci_mr.png
new file mode 100644
index 00000000000..a577356f8e8
--- /dev/null
+++ b/doc/workflow/ci_mr.png
Binary files differ
diff --git a/doc/workflow/close_issue_mr.png b/doc/workflow/close_issue_mr.png
new file mode 100644
index 00000000000..a136d642e12
--- /dev/null
+++ b/doc/workflow/close_issue_mr.png
Binary files differ
diff --git a/doc/workflow/environment_branches.png b/doc/workflow/environment_branches.png
new file mode 100644
index 00000000000..ee893ced13b
--- /dev/null
+++ b/doc/workflow/environment_branches.png
Binary files differ
diff --git a/doc/workflow/four_stages.png b/doc/workflow/four_stages.png
new file mode 100644
index 00000000000..2f444fc6f79
--- /dev/null
+++ b/doc/workflow/four_stages.png
Binary files differ
diff --git a/doc/workflow/git_pull.png b/doc/workflow/git_pull.png
new file mode 100644
index 00000000000..7d47064eb14
--- /dev/null
+++ b/doc/workflow/git_pull.png
Binary files differ
diff --git a/doc/workflow/gitdashflow.png b/doc/workflow/gitdashflow.png
new file mode 100644
index 00000000000..f2f091dd10b
--- /dev/null
+++ b/doc/workflow/gitdashflow.png
Binary files differ
diff --git a/doc/workflow/github_flow.png b/doc/workflow/github_flow.png
new file mode 100644
index 00000000000..88addb623ee
--- /dev/null
+++ b/doc/workflow/github_flow.png
Binary files differ
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
new file mode 100644
index 00000000000..f8fd7c97e2a
--- /dev/null
+++ b/doc/workflow/gitlab_flow.md
@@ -0,0 +1,316 @@
+![GitLab Flow](gitlab_flow.png)
+
+# Introduction
+
+Version management with git makes branching and merging much easier than older versioning systems such as SVN.
+This allows a wide variety of branching strategies and workflows.
+Almost all of these are an improvement over the methods used before git.
+But many organizations end up with a workflow that is not clearly defined, overly complex or not integrated with issue tracking systems.
+Therefore we propose the GitLab flow as clearly defined set of best practices.
+It combines [feature driven development](http://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](http://martinfowler.com/bliki/FeatureBranch.html) with issue tracking.
+
+Organizations coming to git from other version control systems frequently find it hard to develop an effective workflow.
+This article describes the GitLab flow that integrates the git workflow with an issue tracking system.
+It offers a simple, transparent and effective way to work with git.
+
+![Four stages (working copy, index, local repo, remote repo) and three steps between them](four_stages.png)
+
+When converting to git you have to get used to the fact that there are three steps before a commit is shared with colleagues.
+Most version control systems have only step, committing from the working copy to a shared server.
+In git you add files from the working copy to the staging area. After that you commit them to the local repo.
+The third step is pushing to a shared remote repository.
+After getting used to these three steps the branching model becomes the challenge.
+
+![Multiple long running branches and merging in all directions](messy_flow.png)
+
+Since many organizations new to git have no conventions how to work with it, it can quickly become a mess.
+The biggest problem they run into is that many long running branches that each contain part of the changes are around.
+People have a hard time figuring out which branch they should develop on or deploy to production.
+Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html)
+We think there is still room for improvement and will detail a set of practices we call GitLab flow.
+
+# Git flow and its problems
+
+[![Git Flow timeline by Vincent Driessen, used with persmission](gitdashflow.png)
+
+Git flow was one of the first proposals to use git branches and it has gotten a lot of attention.
+It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes.
+The development happens on the develop branch, moves to a release branch and is finally merged into the master branch.
+Git flow is a well defined standard but its complexity introduces two problems.
+The first problem is that developers must use the develop branch and not master, master is reserved for code that is released to production.
+It is a convention to call your default branch master and to mostly branch from and merge to this.
+Since most tools automatically make the master branch the default one and display that one by default it is annoying to have to switch to another one.
+The second problem of git flow is the complexity introduced by the hotfix and release branches.
+These branches can be a good idea for some organizations but are overkill for the vast majority of them.
+Nowadays most organizations practice continuous delivery which means that your default branch can be deployed.
+This means that hotfixed and release branches can be prevented including all the ceremony they introduce.
+An example of this ceremony is the merging back of release branches.
+Though specialized tools do exist to solve this, they require documentation and add complexity.
+Frequently developers make a mistake and for example changes are only merged into master and not into the develop branch.
+The root cause of these errors is that git flow is too complex for most of the use cases.
+And doing releases doesn't automatically mean also doing hotfixes.
+
+# GitHub flow as a simpler alternative
+
+![Master branch with feature branches merged in](github_flow.png)
+
+ In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html).
+This flow has only feature branches and a master branch.
+This is very simple and clean, many organizations have adopted it with great success.
+Atlassian recommends [a similar strategy](http://blogs.atlassian.com/2014/01/simple-git-workflow-simple/) although they rebase feature branches.
+Merging everything into the master branch and deploying often means you minimize the amount of code in 'inventory' which is in line with the lean and continuous delivery best practices.
+But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues.
+With GitLab flow we offer additional guidance for these questions.
+
+# Production branch with GitLab flow
+
+![Master branch and production branch with arrow that indicate deployments](production_branch.png)
+
+GitHub flow does assume you are able to deploy to production every time you merge a feature branch.
+This is possible for SaaS applications but are many cases where this is not possible.
+One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass AppStore validation.
+Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times.
+In these cases you can make a production branch that reflects the deployed code.
+You can deploy a new version by merging in master to the production branch.
+If you need to know what code is in production you can just checkout the production branch to see.
+The approximate time of deployment is easily visible as the merge commit in the version control system.
+This time is pretty accurate if you automatically deploy your production branch.
+If you need a more exact time you can have your deployment script create a tag on each deployment.
+This flow prevents the overhead of releasing, tagging and merging that is common to git flow.
+
+# Environment branches with GitLab flow
+
+![Multiple branches with the code cascading from one to another](environment_branches.png)
+
+It might be a good idea to have an environment that is automatically updated to the master branch.
+Only in this case, the name of this environment might differ from the branch name.
+Suppose you have a staging environment, a pre-production environment and a production environment.
+In this case the master branch is deployed on staging. When someone wants to deploy to pre-production they create a merge request from the master branch to the pre-production branch.
+And going live with code happens by merging the pre-production branch into the production branch.
+This workflow where commits only flow downstream ensures that everything has been tested on all environments.
+If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch.
+If master is good to go (it should be if you a practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches.
+If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches.
+An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](http://teatro.io/).
+
+# Release branches with GitLab flow
+
+![Master and multiple release branches that vary in length with cherrypicks from master](release_branches.png)
+
+Only in case you need to release software to the outside world you need to work with release branches.
+In this case, each branch contains a minor version (2-3-stable, 2-4-stable, etc.).
+The stable branch uses master as a starting point and is created as late as possible.
+By branching as late as possible you minimize the time you have to apply bugfixes to multiple branches.
+After a release branch is announced, only serious bug fixes are included in the release branch.
+If possible these bug fixes are first merged into master and then cherry-picked into the release branch.
+This way you can't forget to cherry-pick them into master and encounter the same bug on subsequent releases.
+This is called an 'upstream first' policy that is also practiced by [Google](http://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](http://www.redhat.com/about/news/archive/2013/5/a-community-for-using-openstack-with-red-hat-rdo).
+Every time a bug-fix is included in a release branch the patch version is raised (to comply with [Semantic Versioning](http://semver.org/)) by setting a new tag.
+Some projects also have a stable branch that points to the same commit as the latest released branch.
+In this flow it is not common to have a production branch (or git flow master branch).
+
+# Merge/pull requests with GitLab flow
+
+![Merge request with line comments](mr_inline_comments.png)
+
+Merge or pull requests are created in a git management application and ask an assigned person to merge two branches.
+Tools such as GitHub and Bitbucket choose the name pull request since the first manual action would be to pull the feature branch.
+Tools such as GitLab and Gitorious choose the name merge request since that is the final action that is requested of the assignee.
+In this article we'll refer to them as merge requests.
+
+If you work on a feature branch for more than a few hours it is good to share the intermediate result with the rest of the team.
+This can be done by creating a merge request without assigning it to anyone, instead you mention people in the description or a comment (/cc @mark @susan).
+This means it is not ready to be merged but feedback is welcome.
+Your team members can comment on the merge request in general or on specific lines with line comments.
+The merge requests serves as a code review tool and no separate tools such as Gerrit and reviewboard should be needed.
+If the review reveals shortcomings anyone can commit and push a fix.
+Commonly the person to do this is the creator of the merge/pull request.
+The diff in the merge/pull requests automatically updates when new commits are pushed on the branch.
+
+When you feel comfortable with it to be merged you assign it to the person that knows most about the codebase you are changing and mention any other people you would like feedback from.
+There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged.
+If the assigned person does not feel comfortable they can close the merge request without merging.
+
+In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md).
+So if you want to merge it into a protected branch you assign it to someone with master authorizations.
+
+# Issues with GitLab flow
+
+![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png)
+
+GitLab flow is a way to make the relation between the code and the issue tracker more transparent.
+
+Any significant change to the code should start with an issue where the goal is described.
+Having a reason for every code change is important to inform everyone on the team and to help people keep the scope of a feature branch small.
+In GitLab each change to the codebase starts with an issue in the issue tracking system.
+If there is no issue yet it should be created first provided there is significant work involved (more than 1 hour).
+For many organizations this will be natural since the issue will have to be estimated for the sprint.
+Issue titles should describe the desired state of the system, e.g. "As an administrator I want to remove users without receiving an error" instead of "Admin can't remove users.".
+
+When you are ready to code you start a branch for the issue from the master branch.
+The name of this branch should start with the issue number, for example '15-require-a-password-to-change-it'.
+
+When you are done or want to discuss the code you open a merge request.
+This is an online place to discuss the change and review the code.
+Creating a branch is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
+If you create the merge request but do not assign it to anyone it is a 'work-in-process' merge request.
+These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet.
+
+When the author thinks the code is ready the merge request is assigned to reviewer.
+The reviewer presses the merge button when they think the code is ready for inclusion in the master branch.
+In this case the code is merged and a merge commit is generated that makes this event easily visible later on.
+Merge requests always create a merge commit even when the commit could be added without one.
+This merge strategy is called 'no fast-forward' in git.
+After the merge the feature branch is deleted since it is no longer needed, in GitLab this deletion is an option when merging.
+
+Suppose that a branch is merged but a problem occurs and the issue is reopened.
+In this case it is no problem to reuse the same branch name since it was deleted when the branch was merged.
+At any time there is at most one branch for every issue.
+It is possible that one feature branch solves more than one issue.
+
+# Linking and closing issues from merge requests
+
+![Merge request showing the linked issues that will be closed](close_issue_mr.png)
+
+Linking to the issue can happen by mentioning them from commit messages (fixes #14, closes #67, etc.) or from the merge request description.
+In GitLab this creates a comment in the issue that the merge requests mentions the issue.
+And the merge request shows the linked issues.
+These issues are closed once code is merged into the default branch.
+
+If you only want to make the reference without closing the issue you can also just mention it: "Ducktyping is preferred. #12".
+
+If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue.
+
+# Squashing commits with rebase
+
+![Vim screen showing the rebase view](rebase.png)
+
+With git you can use an interactive rebase (rebase -i) to squash multiple commits into one and reorder them.
+This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
+However you should never rebase commits you have pushed to a remote server.
+Somebody can have referred to the commits or cherry-picked them.
+When you rebase you change the identifier (SHA1) of the commit and this is confusing.
+If you do that the same change will be known under multiple identifiers and this can cause much confusion.
+If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
+
+People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on.
+This will lead to many commits per change which makes the history harder to understand.
+But the advantages of having stable identifiers outweigh this drawback.
+And to understand a change in context one can always look at the merge commit that groups all the commits together when the code is merged into the master branch.
+
+After you merge multiple commits from a feature branch into the master branch this is harder to undo.
+If you would have squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed.
+Fortunately [reverting a merge made some time ago](http://git-scm.com/blog/2010/03/02/undoing-merges.html) can be done with git.
+This however, requires having specific merge commits for the commits your want to revert.
+If you revert a merge and you change your mind, revert the revert instead of merging again since git will not allow you to merge the code again otherwise.
+
+Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option.
+Git management software will always create a merge commit when you accept a merge request.
+
+# Do not order commits with rebase
+
+![List of sequential merge commits](merge_commits.png)
+
+With git you can also rebase your feature branch commits to order them after the commits on the master branch.
+This prevents creating a merge commit when merging master into your feature branch and creates a nice linear history.
+However, just like with squashing you should never rebase commits you have pushed to a remote server.
+This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend.
+When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](http://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/).
+You can reuse recorded resolutions (rerere) sometimes, but with without rebasing you only have to solve the conflicts one time and you’re set.
+There has to be a better way to avoid many merge commits.
+
+The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
+We'll discuss the three reasons to merge in master: leveraging code, solving merge conflicts and long running branches.
+If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit.
+If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this.
+You should aim to prevent merge conflicts where they are likely to occur.
+One example is the CHANGELOG file where each significant change in the codebase is documented under a version header.
+Instead of everyone adding their change at the bottom of the list for the current version it is better to randomly insert it in the current list for that version.
+This it is likely that multiple feature branches that add to the CHANGELOG can be merged before a conflict occurs.
+The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project.
+Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
+At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
+That's continuous building, and a Good Thing, but there's no integration, so it's not CI.".
+The solution to prevent many merge commits is to keep your feature branches shortlived, the vast majority should take less than one day of work.
+If your feature branches commenly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html).
+As for the long running branches that take more than one day there are two strategies.
+In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time.
+In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release.
+This strategy is [advocated by Linus Torvalds](https://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html) because the state of the code at these points is better known.
+
+In conclusion, we can say that you should try to prevent merge commits, but not eliminate them.
+Your codebase should be clean but your history should represent what actually happened.
+Developing software happen in small messy steps and it is OK to have your history reflect this.
+You can use tools to view the network graphs of commits and understand the messy history that created your code.
+If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
+
+# Voting on merge requests
+
+![Voting slider in GitLab](voting_slider.png)
+
+It is common to voice approval or disapproval by using +1 or -1 emoticons.
+In GitLab the +1 and -1 are aggregated and shown at the top of the merge request.
+As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet.
+
+# Pushing and removing branches
+
+![Remove checkbox for branch in merge requests](remove_checkbox.png)
+
+We recommend that people push their feature branches frequently, even when they are not ready for review yet.
+By doing this you prevent team members from accidentally starting to work on the same issue.
+Of course this situation should already be prevented by assigning someone to the issue in the issue tracking software.
+However sometimes one of the two parties forgets to assign someone in the issue tracking software.
+After a branch is merged it should be removed from the source control software.
+In GitLab and similar systems this is an option when merging.
+This ensures that the branch overview in the repository management software shows only work in progress.
+This also ensures that when someone reopens the issue a new branch with the same name can be used without problem.
+When you reopen an issue you need to create a new merge request.
+
+# Committing often and with the right message
+
+![Good and bad commit message](good_commit.png)
+
+We recommend to commit early and often.
+Each time you have a functioning set of tests and code a commit can be made.
+The advantage is that when an extension or refactor goes wrong it is easy to revert to a working version.
+This is quite a change for programmers that used SVN before, they used to commit when their work was ready to share.
+The trick is to use the merge/pull request with multiple commits when your work is ready to share.
+The commit message should reflect your intention, not the contents of the commit.
+The contents of the commit can be easily seen anyway, the question is why you did it.
+An example of a good commit message is: "Combine templates to dry up the user views.".
+Some words that are bad commit messages because they don't contain munch information are: change, improve and refactor.
+The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number.
+To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
+
+# Testing before merging
+
+![Merge requests showing the test states, red, yellow and green](ci_mr.png)
+
+In old workflows the Continuous Integration (CI) server commonly ran tests on the master branch only.
+Developers had to ensure their code did not break the master branch.
+When using GitLab flow developers create their branches from this master branch so it is essential it is green.
+Therefore each merge request must be tested before it is accepted.
+CI software like Travis and GitLab CI show the build results right in the merge request itself to make this easy.
+One drawback is that they are testing the feature branch itself and not the merged result.
+What one can do to improve this is to test the merged result itself.
+The problem is that the merge result changes every time something is merged into master.
+Retesting on every commit to master is computationally expensive and means you are more frequently waiting for test results.
+If there are no merge conflicts and the feature branches are short lived the risk is acceptable.
+If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
+If you have long lived feature branches that last for more than a few days you should make your issues smaller.
+
+# Merging in other code
+
+![Shell output showing git pull output](git_pull.png)
+
+When initiating a feature branch, always start with an up to date master to branch off from.
+If you know beforehand that your work absolutely depends on another branch you can also branch from there.
+If you need to merge in another branch after starting explain the reason in the merge commit.
+If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch.
+Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](http://lwn.net/Articles/328438/).
+Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history.
+
+### References
+
+- [Sketch file](https://www.dropbox.com/s/58dvsj5votbwrzv/git_flows.sketch?dl=0) with vectors of images in this article
+- [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/)
diff --git a/doc/workflow/gitlab_flow.png b/doc/workflow/gitlab_flow.png
new file mode 100644
index 00000000000..1ea191a672b
--- /dev/null
+++ b/doc/workflow/gitlab_flow.png
Binary files differ
diff --git a/doc/workflow/good_commit.png b/doc/workflow/good_commit.png
new file mode 100644
index 00000000000..3737a026644
--- /dev/null
+++ b/doc/workflow/good_commit.png
Binary files differ
diff --git a/doc/workflow/merge_commits.png b/doc/workflow/merge_commits.png
new file mode 100644
index 00000000000..757b589d0db
--- /dev/null
+++ b/doc/workflow/merge_commits.png
Binary files differ
diff --git a/doc/workflow/merge_request.png b/doc/workflow/merge_request.png
new file mode 100644
index 00000000000..fde3ff5c854
--- /dev/null
+++ b/doc/workflow/merge_request.png
Binary files differ
diff --git a/doc/workflow/messy_flow.png b/doc/workflow/messy_flow.png
new file mode 100644
index 00000000000..1addb95ca54
--- /dev/null
+++ b/doc/workflow/messy_flow.png
Binary files differ
diff --git a/doc/workflow/migrating_from_svn.md b/doc/workflow/migrating_from_svn.md
new file mode 100644
index 00000000000..207e3641802
--- /dev/null
+++ b/doc/workflow/migrating_from_svn.md
@@ -0,0 +1,17 @@
+# Migrating from SVN to GitLab
+
+SVN stands for Subversion and is a version control system (VCS).
+Git is a distributed version control system.
+
+There are some major differences between the two, for more information consult your favourite search engine.
+
+Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at
+[git documentation pages](http://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion).
+
+Apart from the [official git documentation](http://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also
+user created step by step guide for migrating from SVN to GitLab.
+
+[Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca).
+
+## Contribute to this guide
+We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems.
diff --git a/doc/workflow/mr_inline_comments.png b/doc/workflow/mr_inline_comments.png
new file mode 100644
index 00000000000..e851b95bcef
--- /dev/null
+++ b/doc/workflow/mr_inline_comments.png
Binary files differ
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
new file mode 100644
index 00000000000..3c3ce162df5
--- /dev/null
+++ b/doc/workflow/notifications.md
@@ -0,0 +1,71 @@
+# GitLab Notifications
+
+GitLab has notifications system in place to notify a user of events important for the workflow.
+
+## Notification settings
+
+Under user profile page you can find the notification settings.
+
+![notification settings](notifications/settings.png)
+
+Notification settings are divided into three groups:
+
+* Global Settings
+* Group Settings
+* Project Settings
+
+Each of these settings have levels of notification:
+
+* Disabled - turns off notifications
+* Participating - receive notifications from related resources
+* Watch - receive notifications from projects or groups user is a member of
+* Global - notifications as set at the global settings
+
+#### Global Settings
+
+Global Settings are at the bottom of the hierarchy.
+Any setting set here will be overriden by a setting at the group or a project level.
+
+Group or Project settings can use `global` notification setting which will then use
+anything that is set at Global Settings.
+
+#### Group Settings
+
+Group Settings are taking presedence over Global Settings but are on a level below Project Settings.
+This means that you can set a different level of notifications per group while still being able
+to have a finer level setting per project.
+Organization like this is suitable for users that belong to different groups but don't have the
+same need for being notified for every group they are member of.
+
+#### Project Settings
+
+Project Settings are at the top level and any setting placed at this level will take presedence of any
+other setting.
+This is suitable for users that have different needs for notifications per project basis.
+
+## Notification events
+
+Below is the table of events users can be notified of:
+
+| Event | Sent to | Settings level |
+|------------------------------|-------------------------------------------------------------------|------------------------------|
+| New SSH key added | User | Security email, always sent. |
+| New email added | User | Security email, always sent. |
+| New user created | User | Sent on user creation, except for omniauth (LDAP)|
+| New issue created | Issue assignee [1], project members [2] | [1] not disabled, [2] higher than participating |
+| User added to project | User | Sent when user is added to project |
+| Project access level changed | User | Sent when user project access level is changed |
+| User added to group | User | Sent when user is added to group |
+| Project moved | Project members [1] | [1] not disabled |
+| Group access level changed | User | Sent when user group access level is changed |
+| Close issue | Issue author [1], issue assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+| Reassign issue | New issue assignee [1], old issue assignee [2] | [1] [2] not disabled |
+| Reopen issue | Project members [1] | [1] higher than participating |
+| New merge request | MR assignee [1] | [1] not disabled |
+| Reassign merge request | New MR assignee [1], old MR assignee [2] | [1] [2] not disabled |
+| Close merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+| Reopen merge request | Project members [1] | [1] higher than participating |
+| Merge merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+| New comment | Mentioned users [1], users participating [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+
+
diff --git a/doc/workflow/notifications/settings.png b/doc/workflow/notifications/settings.png
new file mode 100644
index 00000000000..e5b50ee2494
--- /dev/null
+++ b/doc/workflow/notifications/settings.png
Binary files differ
diff --git a/doc/workflow/production_branch.png b/doc/workflow/production_branch.png
new file mode 100644
index 00000000000..33fb26dd621
--- /dev/null
+++ b/doc/workflow/production_branch.png
Binary files differ
diff --git a/doc/workflow/rebase.png b/doc/workflow/rebase.png
new file mode 100644
index 00000000000..ef82c834755
--- /dev/null
+++ b/doc/workflow/rebase.png
Binary files differ
diff --git a/doc/workflow/release_branches.png b/doc/workflow/release_branches.png
new file mode 100644
index 00000000000..da7ae53413a
--- /dev/null
+++ b/doc/workflow/release_branches.png
Binary files differ
diff --git a/doc/workflow/remove_checkbox.png b/doc/workflow/remove_checkbox.png
new file mode 100644
index 00000000000..3e247d38155
--- /dev/null
+++ b/doc/workflow/remove_checkbox.png
Binary files differ
diff --git a/doc/workflow/voting_slider.png b/doc/workflow/voting_slider.png
new file mode 100644
index 00000000000..4c660ef9593
--- /dev/null
+++ b/doc/workflow/voting_slider.png
Binary files differ
diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature
index b28e16f0d6a..5de07e90e28 100644
--- a/features/admin/active_tab.feature
+++ b/features/admin/active_tab.feature
@@ -1,5 +1,5 @@
@admin
-Feature: Admin active tab
+Feature: Admin Active Tab
Background:
Given I sign in as an admin
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index 1a465c1be55..aa365a6ea1a 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -20,3 +20,10 @@ Feature: Admin Groups
When I visit admin group page
When I select user "John Doe" from user list as "Reporter"
Then I should see "John Doe" in team list in every project as "Reporter"
+
+ @javascript
+ Scenario: Remove user from group
+ Given we have user "John Doe" in group
+ When I visit admin group page
+ And I remove user "John Doe" from group
+ Then I should not see "John Doe" in team list
diff --git a/features/admin/users.feature b/features/admin/users.feature
index d8c1288e5f0..278f6a43e94 100644
--- a/features/admin/users.feature
+++ b/features/admin/users.feature
@@ -16,6 +16,12 @@ Feature: Admin Users
Then See username error message
And Not changed form action url
+ Scenario: Show user attributes
+ Given user "Mike" with groups and projects
+ Given I visit admin users page
+ And click on "Mike" link
+ Then I should see user "Mike" details
+
Scenario: Edit my user attributes
Given I visit admin users page
And click edit on my user
diff --git a/features/dashboard/active_tab.feature b/features/dashboard/active_tab.feature
index 22dfa2f7840..08b87808f33 100644
--- a/features/dashboard/active_tab.feature
+++ b/features/dashboard/active_tab.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Dashboard active tab
+Feature: Dashboard Active Tab
Background:
Given I sign in as a user
diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature
index e23238d225c..3af93bc373c 100644
--- a/features/dashboard/archived_projects.feature
+++ b/features/dashboard/archived_projects.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Dashboard with archived projects
+Feature: Dashboard Archived Projects
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/dashboard/event_filters.feature b/features/dashboard/event_filters.feature
index 41de0ae8317..ec5680caba6 100644
--- a/features/dashboard/event_filters.feature
+++ b/features/dashboard/event_filters.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Event filters
+Feature: Event Filters
Background:
Given I sign in as a user
And I own a project
diff --git a/features/dashboard/help.feature b/features/dashboard/help.feature
index 56a0a860ab9..bca2772897b 100644
--- a/features/dashboard/help.feature
+++ b/features/dashboard/help.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Help
+Feature: Dashboard Help
Background:
Given I sign in as a user
And I visit the "Rake Tasks" help page
diff --git a/features/dashboard/projects.feature b/features/dashboard/projects.feature
index 5214cfc28ef..bb4e84f0159 100644
--- a/features/dashboard/projects.feature
+++ b/features/dashboard/projects.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Dashboard projects
+Feature: Dashboard Projects
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/dashboard/shortcuts.feature b/features/dashboard/shortcuts.feature
index 7c25b3926c9..41d79aa6ec8 100644
--- a/features/dashboard/shortcuts.feature
+++ b/features/dashboard/shortcuts.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Dashboard shortcuts
+Feature: Dashboard Shortcuts
Background:
Given I sign in as a user
And I visit dashboard page
diff --git a/features/explore/public_groups.feature b/features/explore/groups.feature
index 825e3da934e..b50a3e766c6 100644
--- a/features/explore/public_groups.feature
+++ b/features/explore/groups.feature
@@ -1,5 +1,5 @@
@public
-Feature: Explore Groups Feature
+Feature: Explore Groups
Background:
Given group "TestGroup" has private project "Enterprise"
diff --git a/features/explore/projects.feature b/features/explore/projects.feature
index 9827ebe3e57..a1b29722678 100644
--- a/features/explore/projects.feature
+++ b/features/explore/projects.feature
@@ -1,5 +1,5 @@
@public
-Feature: Explore Projects Feature
+Feature: Explore Projects
Background:
Given public project "Community"
And internal project "Internal"
diff --git a/features/group.feature b/features/groups.feature
index b5ff03db844..b5ff03db844 100644
--- a/features/group.feature
+++ b/features/groups.feature
diff --git a/features/profile/active_tab.feature b/features/profile/active_tab.feature
index a99409d9fd7..7801ae5b8ca 100644
--- a/features/profile/active_tab.feature
+++ b/features/profile/active_tab.feature
@@ -1,5 +1,5 @@
@profile
-Feature: Profile active tab
+Feature: Profile Active Tab
Background:
Given I sign in as a user
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index d2125e013bc..d7fa370fe2a 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -83,3 +83,22 @@ Feature: Profile
Given I visit profile design page
When I change my code preview theme
Then I should receive feedback that the changes were saved
+
+ @javascript
+ Scenario: I see the password strength indicator
+ Given I visit profile password page
+ When I try to set a weak password
+ Then I should see the input field yellow
+
+ @javascript
+ Scenario: I see the password strength indicator error
+ Given I visit profile password page
+ When I try to set a short password
+ Then I should see the input field red
+ And I should see the password error message
+
+ @javascript
+ Scenario: I see the password strength indicator with success
+ Given I visit profile password page
+ When I try to set a strong password
+ Then I should see the input field green \ No newline at end of file
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index ce90a086688..8d3e0bd967f 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -1,4 +1,4 @@
-Feature: Project active tab
+Feature: Project Active Tab
Background:
Given I sign in as a user
And I own a project
diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature
index d124cb7eecd..65d8e48b9b3 100644
--- a/features/project/commits/branches.feature
+++ b/features/project/commits/branches.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse branches
+Feature: Project Commits Branches
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/commits/comments.feature b/features/project/commits/comments.feature
index a1aa745a681..e176752cfbf 100644
--- a/features/project/commits/comments.feature
+++ b/features/project/commits/comments.feature
@@ -1,4 +1,4 @@
-Feature: Comments on commits
+Feature: Project Commits Comments
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature
index 7c6db3c465a..46076b6f3e6 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse commits
+Feature: Project Commits
Background:
Given I sign in as a user
And I own a project
@@ -30,9 +30,10 @@ Feature: Project Browse commits
Given I visit my project's commits page for a specific path
Then I see breadcrumb links
- Scenario: I browse commits stats
- Given I visit my project's commits stats page
- Then I see commits stats
+ # TODO: Implement feature in graphs
+ #Scenario: I browse commits stats
+ #Given I visit my project's commits stats page
+ #Then I see commits stats
Scenario: I browse big commit
Given I visit big commit page
diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature
index b26019f832f..a145ec84b78 100644
--- a/features/project/commits/diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
@@ -1,4 +1,4 @@
-Feature: Comments on commit diffs
+Feature: Project Commits Diff Comments
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature
index bea463cb786..02f399f7cad 100644
--- a/features/project/commits/tags.feature
+++ b/features/project/commits/tags.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse tags
+Feature: Project Commits Tags
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/commits/user_lookup.feature b/features/project/commits/user_lookup.feature
index 7b194ab9206..db51d4a6cfa 100644
--- a/features/project/commits/user_lookup.feature
+++ b/features/project/commits/user_lookup.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse Commits User Lookup
+Feature: Project Commits User Lookup
Background:
Given I sign in as a user
And I own a project
diff --git a/features/project/create.feature b/features/project/create.feature
index bb8e3a368ed..e9dc4fe6b3c 100644
--- a/features/project/create.feature
+++ b/features/project/create.feature
@@ -1,4 +1,4 @@
-Feature: Create Project
+Feature: Project Create
In order to get access to project sections
A user with ability to create a project
Should be able to create a new one
diff --git a/features/project/fork.feature b/features/project/fork.feature
index dc477ca3bf3..d3d1180db04 100644
--- a/features/project/fork.feature
+++ b/features/project/fork.feature
@@ -1,4 +1,4 @@
-Feature: Fork Project
+Feature: Project Fork
Background:
Given I sign in as a user
And I am a member of project "Shop"
diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature
index 7442145d87e..d9fbb875c28 100644
--- a/features/project/forked_merge_requests.feature
+++ b/features/project/forked_merge_requests.feature
@@ -11,18 +11,20 @@ Feature: Project Forked Merge Requests
And I submit the merge request
Then I should see merge request "Merge Request On Forked Project"
- @javascript
- Scenario: I can edit a forked merge request
- Given I visit project "Forked Shop" merge requests page
- And I click link "New Merge Request"
- And I fill out a "Merge Request On Forked Project" merge request
- And I submit the merge request
- And I should see merge request "Merge Request On Forked Project"
- And I click link edit "Merge Request On Forked Project"
- Then I see the edit page prefilled for "Merge Request On Forked Project"
- And I update the merge request title
- And I save the merge request
- Then I should see the edited merge request
+ # TODO: Improve it so it does not fail randomly
+ #
+ #@javascript
+ #Scenario: I can edit a forked merge request
+ #Given I visit project "Forked Shop" merge requests page
+ #And I click link "New Merge Request"
+ #And I fill out a "Merge Request On Forked Project" merge request
+ #And I submit the merge request
+ #And I should see merge request "Merge Request On Forked Project"
+ #And I click link edit "Merge Request On Forked Project"
+ #Then I see the edit page prefilled for "Merge Request On Forked Project"
+ #And I update the merge request title
+ #And I save the merge request
+ #Then I should see the edited merge request
@javascript
Scenario: I cannot submit an invalid merge request
diff --git a/features/project/graph.feature b/features/project/graph.feature
index cda95f5dda6..89064242c1c 100644
--- a/features/project/graph.feature
+++ b/features/project/graph.feature
@@ -2,8 +2,13 @@ Feature: Project Graph
Background:
Given I sign in as a user
And I own project "Shop"
- And I visit project "Shop" graph page
@javascript
Scenario: I should see project graphs
+ When I visit project "Shop" graph page
Then page should have graphs
+
+ @javascript
+ Scenario: I should see project commits graphs
+ When I visit project "Shop" commits graph page
+ Then page should have commits graphs
diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature
index f4a0a7977cc..2c69a78a749 100644
--- a/features/project/issues/filter_labels.feature
+++ b/features/project/issues/filter_labels.feature
@@ -1,4 +1,4 @@
-Feature: Project Filter Labels
+Feature: Project Issues Filter Labels
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index ae6a03ce865..4db8551559b 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -126,3 +126,36 @@ Feature: Project Issues
When I click label 'bug'
And I should see "Release 0.4" in issues
And I should not see "Tweet control" in issues
+
+ Scenario: Issue description should render task checkboxes
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ Then I should see task checkboxes in the description
+
+ @javascript
+ Scenario: Issue notes should not render task checkboxes
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ And I leave a comment with task markdown
+ Then I should not see task checkboxes in the comment
+
+ # Task status in issues list
+
+ Scenario: Issues list should display task status
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit project "Shop" issues page
+ Then I should see the task status for the Taskable
+
+ # Toggling task items
+
+ @javascript
+ Scenario: Task checkboxes should be enabled for an open issue
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ Then Task checkboxes should be enabled
+
+ @javascript
+ Scenario: Task checkboxes should be disabled for a closed issue
+ Given project "Shop" has "Tasks-closed" closed issue with task markdown
+ When I visit issue page "Tasks-closed"
+ Then Task checkboxes should be disabled
diff --git a/features/project/issues/labels.feature b/features/project/issues/labels.feature
index a9fe1595fc5..039a7d83cb1 100644
--- a/features/project/issues/labels.feature
+++ b/features/project/issues/labels.feature
@@ -1,4 +1,4 @@
-Feature: Project Labels
+Feature: Project Issues Labels
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
index e67b5d2d860..9ac65b1257c 100644
--- a/features/project/issues/milestones.feature
+++ b/features/project/issues/milestones.feature
@@ -1,4 +1,4 @@
-Feature: Project Milestones
+Feature: Project Issues Milestones
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index f8dccc15c0e..d20358a7dc6 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -96,6 +96,16 @@ Feature: Project Merge Requests
And I leave a comment with a header containing "Comment with a header"
Then The comment with the header should not have an ID
+ Scenario: Merge request description should render task checkboxes
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ When I visit merge request page "MR-task-open"
+ Then I should see task checkboxes in the description
+
+ Scenario: Merge request notes should not render task checkboxes
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ When I visit merge request page "MR-task-open"
+ Then I should not see task checkboxes in the comment
+
# Toggling inline comments
@javascript
@@ -105,7 +115,7 @@ Feature: Project Merge Requests
And I switch to the diff tab
And I leave a comment like "Line is wrong" on line 39 of the second file
And I click link "Hide inline discussion" of the second file
- Then I should not see a comment like "Line is wrong" in the second file
+ Then I should not see a comment like "Line is wrong here" in the second file
@javascript
Scenario: I show comments on a merge request diff with comments in a single file
@@ -113,8 +123,6 @@ Feature: Project Merge Requests
And I visit merge request page "Bug NS-05"
And I switch to the diff tab
And I leave a comment like "Line is wrong" on line 39 of the second file
- And I click link "Hide inline discussion" of the second file
- And I click link "Show inline discussion" of the second file
Then I should see a comment like "Line is wrong" in the second file
@javascript
@@ -125,7 +133,7 @@ Feature: Project Merge Requests
And I leave a comment like "Line is correct" on line 12 of the first file
And I leave a comment like "Line is wrong" on line 39 of the second file
And I click link "Hide inline discussion" of the second file
- Then I should not see a comment like "Line is wrong" in the second file
+ Then I should not see a comment like "Line is wrong here" in the second file
And I should still see a comment like "Line is correct" in the first file
@javascript
@@ -157,3 +165,25 @@ Feature: Project Merge Requests
And I leave a comment like "Line is wrong" on line 39 of the second file
And I click Side-by-side Diff tab
Then I should see comments on the side-by-side diff page
+
+ # Task status in issues list
+
+ Scenario: Merge requests list should display task status
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ When I visit project "Shop" merge requests page
+ Then I should see the task status for the Taskable
+
+ # Toggling task items
+
+ @javascript
+ Scenario: Task checkboxes should be enabled for an open merge request
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ When I visit merge request page "MR-task-open"
+ Then Task checkboxes should be enabled
+
+ @javascript
+ Scenario: Task checkboxes should be disabled for a closed merge request
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ And I visit merge request page "MR-task-open"
+ And I click link "Close"
+ Then Task checkboxes should be disabled
diff --git a/features/project/network.feature b/features/project/network_graph.feature
index 8beb6043aff..8beb6043aff 100644
--- a/features/project/network.feature
+++ b/features/project/network_graph.feature
diff --git a/features/project/project.feature b/features/project/project.feature
index c1f192f123e..7bb24e013a9 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -1,4 +1,4 @@
-Feature: Project Feature
+Feature: Project
Background:
Given I sign in as a user
And I own project "Shop"
@@ -27,7 +27,6 @@ Feature: Project Feature
Scenario: I should see project readme and version
When I visit project "Shop" page
- Then I should see project "Shop" README link
And I should see project "Shop" version
Scenario: I should change project default branch
@@ -35,3 +34,11 @@ Feature: Project Feature
And change project default branch
And I save project
Then I should see project default branch changed
+
+ @javascript
+ Scenario: I should have default tab per my preference
+ And I own project "Forum"
+ When I select project "Forum" README tab
+ Then I should see project "Forum" README
+ And I visit project "Shop" page
+ Then I should see project "Shop" README
diff --git a/features/project/service.feature b/features/project/service.feature
index a5af065c9e7..88fd038d45f 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -43,8 +43,20 @@ Feature: Project Services
And I fill Slack settings
Then I should see Slack service settings saved
+ Scenario: Activate Pushover service
+ When I visit project "Shop" services page
+ And I click Pushover service link
+ And I fill Pushover settings
+ Then I should see Pushover service settings saved
+
Scenario: Activate email on push service
When I visit project "Shop" services page
And I click email on push service link
And I fill email on push settings
Then I should see email on push service settings saved
+
+ Scenario: Activate Atlassian Bamboo CI service
+ When I visit project "Shop" services page
+ And I click Atlassian Bamboo CI service link
+ And I fill Atlassian Bamboo CI settings
+ Then I should see Atlassian Bamboo CI service settings saved
diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature
index e5f9c103fb1..cfb68bf1f50 100644
--- a/features/project/shortcuts.feature
+++ b/features/project/shortcuts.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Project shortcuts
+Feature: Project Shortcuts
Background:
Given I sign in as a user
And I own a project
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index e4f10c0de2e..b7d70881d56 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse files
+Feature: Project Source Browse Files
Background:
Given I sign in as a user
And I own project "Shop"
@@ -17,7 +17,7 @@ Feature: Project Browse files
Scenario: I browse raw file
Given I visit blob file from repo
- And I click link "raw"
+ And I click link "Raw"
Then I should see raw file content
Scenario: I can create file
@@ -25,38 +25,88 @@ Feature: Project Browse files
Then I can see new file page
@javascript
+ Scenario: I can create and commit file
+ Given I click on "new file" link in repo
+ And I edit code
+ And I fill the new file name
+ And I fill the commit message
+ And I click on "Commit Changes"
+ Then I am redirected to the new file
+ And I should see its new content
+
+ @javascript
+ Scenario: If I enter an illegal file name I see an error message
+ Given I click on "new file" link in repo
+ And I fill the new file name with an illegal name
+ And I edit code
+ And I fill the commit message
+ And I click on "Commit changes"
+ Then I am on the new file page
+ And I see a commit error message
+
+ @javascript
Scenario: I can edit file
Given I click on ".gitignore" file in repo
- And I click button "edit"
+ And I click button "Edit"
Then I can edit code
@javascript
- Scenario: I can see editing preview
+ Scenario: I can edit and commit file
+ Given I click on ".gitignore" file in repo
+ And I click button "Edit"
+ And I edit code
+ And I fill the commit message
+ And I click on "Commit Changes"
+ Then I am redirected to the ".gitignore"
+ And I should see its new content
+
+ @javascript @wip
+ Scenario: If I don't change the content of the file I see an error message
Given I click on ".gitignore" file in repo
And I click button "edit"
+ And I fill the commit message
+ And I click on "Commit changes"
+ # Test fails because carriage returns are added to the file.
+ Then I am on the ".gitignore" edit file page
+ And I see a commit error message
+
+ @javascript
+ Scenario: I can see editing preview
+ Given I click on ".gitignore" file in repo
+ And I click button "Edit"
And I edit code
And I click link "Diff"
Then I see diff
+ @javascript
+ Scenario: I can remove file and commit
+ Given I click on ".gitignore" file in repo
+ And I see the ".gitignore"
+ And I click on "Remove"
+ And I fill the commit message
+ And I click on "Remove file"
+ Then I am redirected to the files URL
+ And I don't see the ".gitignore"
+
Scenario: I can browse directory with Browse Dir
Given I click on files directory
- And I click on history link
+ And I click on History link
Then I see Browse dir link
Scenario: I can browse file with Browse File
Given I click on readme file
- And I click on history link
+ And I click on History link
Then I see Browse file link
Scenario: I can browse code with Browse Code
- Given I click on history link
+ Given I click on History link
Then I see Browse code link
# Permalink
Scenario: I click on the permalink link from a branch ref
Given I click on ".gitignore" file in repo
- And I click on permalink
+ And I click on Permalink
Then I am redirected to the permalink URL
Scenario: I don't see the permalink link from a SHA ref
diff --git a/features/project/source/git_blame.feature b/features/project/source/git_blame.feature
index ae62c166c12..48b1077dc6b 100644
--- a/features/project/source/git_blame.feature
+++ b/features/project/source/git_blame.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse git repo
+Feature: Project Source Git Blame
Background:
Given I sign in as a user
And I own project "Shop"
@@ -6,5 +6,5 @@ Feature: Project Browse git repo
Scenario: I blame file
Given I click on ".gitignore" file in repo
- And I click blame button
+ And I click Blame button
Then I should see git file blame
diff --git a/features/project/source/markdown_render.feature b/features/project/source/markdown_render.feature
index fce351317c6..ecbd721c281 100644
--- a/features/project/source/markdown_render.feature
+++ b/features/project/source/markdown_render.feature
@@ -1,4 +1,4 @@
-Feature: Project markdown render
+Feature: Project Source Markdown Render
Background:
Given I sign in as a user
And I own project "Delta"
diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature
index f60b646a8d9..63b7cb77a93 100644
--- a/features/project/source/multiselect_blob.feature
+++ b/features/project/source/multiselect_blob.feature
@@ -1,4 +1,4 @@
-Feature: Project Multiselect Blob
+Feature: Project Source Multiselect Blob
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/source/search_code.feature b/features/project/source/search_code.feature
index 93b326696d0..4f9dcea249f 100644
--- a/features/project/source/search_code.feature
+++ b/features/project/source/search_code.feature
@@ -1,4 +1,4 @@
-Feature: Project Search code
+Feature: Project Source Search Code
Background:
Given I sign in as a user
diff --git a/features/project/team_management.feature b/features/project/team_management.feature
index e153978e043..86ea6cd6e91 100644
--- a/features/project/team_management.feature
+++ b/features/project/team_management.feature
@@ -1,4 +1,4 @@
-Feature: Project Team management
+Feature: Project Team Management
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/snippets/discover.feature b/features/snippets/discover.feature
index f0b8d3a408a..1a7e132ea25 100644
--- a/features/snippets/discover.feature
+++ b/features/snippets/discover.feature
@@ -1,11 +1,13 @@
@snippets
-Feature: Discover Snippets
+Feature: Snippets Discover
Background:
Given I sign in as a user
And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet
+ And I have internal "Personal snippet internal" snippet
Scenario: I should see snippets
Given I visit snippets page
Then I should see "Personal snippet one" in snippets
+ And I should see "Personal snippet internal" in snippets
And I should not see "Personal snippet private" in snippets
diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature
new file mode 100644
index 00000000000..c2afb63b6d8
--- /dev/null
+++ b/features/snippets/public_snippets.feature
@@ -0,0 +1,10 @@
+Feature: Public snippets
+ Scenario: Unauthenticated user should see public snippets
+ Given There is public "Personal snippet one" snippet
+ And I visit snippet page "Personal snippet one"
+ Then I should see snippet "Personal snippet one"
+
+ Scenario: Unauthenticated user should see raw public snippets
+ Given There is public "Personal snippet one" snippet
+ And I visit snippet raw page "Personal snippet one"
+ Then I should see raw snippet "Personal snippet one"
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
index 38216dd5b7b..6e8019c326f 100644
--- a/features/snippets/snippets.feature
+++ b/features/snippets/snippets.feature
@@ -1,5 +1,5 @@
@snippets
-Feature: Snippets Feature
+Feature: Snippets
Background:
Given I sign in as a user
And I have public "Personal snippet one" snippet
@@ -25,4 +25,4 @@ Feature: Snippets Feature
Scenario: I destroy "Personal snippet one"
Given I visit snippet page "Personal snippet one"
And I click link "Destroy"
- Then I should not see "Personal snippet one" in snippets
+ Then I should not see "Personal snippet one" in snippets \ No newline at end of file
diff --git a/features/snippets/user.feature b/features/snippets/user.feature
index d032a33686b..5b5dadb7b39 100644
--- a/features/snippets/user.feature
+++ b/features/snippets/user.feature
@@ -1,19 +1,22 @@
@snippets
-Feature: User Snippets
+Feature: Snippets User
Background:
Given I sign in as a user
And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet
+ And I have internal "Personal snippet internal" snippet
Scenario: I should see all my snippets
Given I visit my snippets page
Then I should see "Personal snippet one" in snippets
And I should see "Personal snippet private" in snippets
+ And I should see "Personal snippet internal" in snippets
Scenario: I can see only my private snippets
Given I visit my snippets page
And I click "Private" filter
Then I should not see "Personal snippet one" in snippets
+ And I should not see "Personal snippet internal" in snippets
And I should see "Personal snippet private" in snippets
Scenario: I can see only my public snippets
@@ -21,3 +24,11 @@ Feature: User Snippets
And I click "Public" filter
Then I should see "Personal snippet one" in snippets
And I should not see "Personal snippet private" in snippets
+ And I should not see "Personal snippet internal" in snippets
+
+ Scenario: I can see only my internal snippets
+ Given I visit my snippets page
+ And I click "Internal" filter
+ Then I should see "Personal snippet internal" in snippets
+ And I should not see "Personal snippet private" in snippets
+ And I should not see "Personal snippet one" in snippets
diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb
index 4f0ba05606d..d69a87cd07e 100644
--- a/features/steps/admin/groups.rb
+++ b/features/steps/admin/groups.rb
@@ -37,8 +37,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
When 'I select user "John Doe" from user list as "Reporter"' do
- user = User.find_by(name: "John Doe")
- select2(user.id, from: "#user_ids", multiple: true)
+ select2(user_john.id, from: "#user_ids", multiple: true)
within "#new_team_member" do
select "Reporter", from: "access_level"
end
@@ -58,9 +57,29 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
end
+ step 'we have user "John Doe" in group' do
+ current_group.add_user(user_john, Gitlab::Access::REPORTER)
+ end
+
+ step 'I remove user "John Doe" from group' do
+ within "#user_#{user_john.id}" do
+ click_link 'Remove user from group'
+ end
+ end
+
+ step 'I should not see "John Doe" in team list' do
+ within ".group-users-list" do
+ page.should_not have_content "John Doe"
+ end
+ end
+
protected
def current_group
@group ||= Group.first
end
+
+ def user_john
+ @user_john ||= User.find_by(name: "John Doe")
+ end
end
diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb
index 3422dc2b5af..546c1bf2a12 100644
--- a/features/steps/admin/users.rb
+++ b/features/steps/admin/users.rb
@@ -63,4 +63,23 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
step 'I should not see secondary email anymore' do
page.should_not have_content "Secondary email:"
end
+
+ step 'user "Mike" with groups and projects' do
+ user = create(:user, name: 'Mike')
+
+ project = create(:empty_project)
+ project.team << [user, :developer]
+
+ group = create(:group)
+ group.add_user(user, Gitlab::Access::DEVELOPER)
+ end
+
+ step 'click on "Mike" link' do
+ click_link "Mike"
+ end
+
+ step 'I should see user "Mike" details' do
+ page.should have_content 'Account'
+ page.should have_content 'Personal projects limit'
+ end
end
diff --git a/features/steps/dashboard/with_archived_projects.rb b/features/steps/dashboard/archived_projects.rb
index 256629382a7..969baf92287 100644
--- a/features/steps/dashboard/with_archived_projects.rb
+++ b/features/steps/dashboard/archived_projects.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::DashboardWithArchivedProjects < Spinach::FeatureSteps
+class Spinach::Features::DashboardArchivedProjects < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 7fcca732626..1826ead1d51 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -82,8 +82,4 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
step 'I should see 1 project at group list' do
find('span.last_activity/span').should have_content('1')
end
-
- def project
- @project ||= Project.find_by(name: "Shop")
- end
end
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
index 332bfa95d97..3da3d62d0c0 100644
--- a/features/steps/dashboard/event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
@@ -29,7 +29,7 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
step 'this project has push event' do
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/new_design",
user_id: @user.id,
diff --git a/features/steps/help.rb b/features/steps/dashboard/help.rb
index 0d1c9c00376..ef433c57c6e 100644
--- a/features/steps/help.rb
+++ b/features/steps/dashboard/help.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::Help < Spinach::FeatureSteps
+class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedMarkdown
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 6b5f88e5895..2a5850d091b 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -10,6 +10,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I should see issues authored by me' do
should_see(authored_issue)
+ should_see(authored_issue_on_public_project)
should_not_see(assigned_issue)
should_not_see(other_issue)
end
@@ -22,6 +23,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I have authored issues' do
authored_issue
+ authored_issue_on_public_project
end
step 'I have assigned issues' do
@@ -64,6 +66,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
@other_issue ||= create :issue, project: project
end
+ def authored_issue_on_public_project
+ @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project
+ end
+
def project
@project ||= begin
project =create :project
@@ -71,4 +77,8 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
project
end
end
+
+ def public_project
+ @public_project ||= create :project, :public
+ end
end
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 95c378fa201..75e53173d3f 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -4,13 +4,17 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I should see merge requests assigned to me' do
should_see(assigned_merge_request)
+ should_see(assigned_merge_request_from_fork)
should_not_see(authored_merge_request)
+ should_not_see(authored_merge_request_from_fork)
should_not_see(other_merge_request)
end
step 'I should see merge requests authored by me' do
should_see(authored_merge_request)
+ should_see(authored_merge_request_from_fork)
should_not_see(assigned_merge_request)
+ should_not_see(assigned_merge_request_from_fork)
should_not_see(other_merge_request)
end
@@ -22,10 +26,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I have authored merge requests' do
authored_merge_request
+ authored_merge_request_from_fork
end
step 'I have assigned merge requests' do
assigned_merge_request
+ assigned_merge_request_from_fork
end
step 'I have other merge requests' do
@@ -53,15 +59,41 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
end
def assigned_merge_request
- @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project
+ @assigned_merge_request ||= create :merge_request,
+ assignee: current_user,
+ target_project: project,
+ source_project: project
end
def authored_merge_request
- @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project
+ @authored_merge_request ||= create :merge_request,
+ source_branch: 'simple_merge_request',
+ author: current_user,
+ target_project: project,
+ source_project: project
end
def other_merge_request
- @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project
+ @other_merge_request ||= create :merge_request,
+ source_branch: '2_3_notes_fix',
+ target_project: project,
+ source_project: project
+ end
+
+ def authored_merge_request_from_fork
+ @authored_merge_request_from_fork ||= create :merge_request,
+ source_branch: 'basic_page',
+ author: current_user,
+ target_project: public_project,
+ source_project: forked_project
+ end
+
+ def assigned_merge_request_from_fork
+ @assigned_merge_request_from_fork ||= create :merge_request,
+ source_branch: 'basic_page_fix',
+ assignee: current_user,
+ target_project: public_project,
+ source_project: forked_project
end
def project
@@ -71,4 +103,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
project
end
end
+
+ def public_project
+ @public_project ||= create :project, :public
+ end
+
+ def forked_project
+ @forked_project ||= Projects::ForkService.new(public_project, current_user).execute
+ end
end
diff --git a/features/steps/explore/groups_feature.rb b/features/steps/explore/groups.rb
index 94c918d932b..ccbf6cda07e 100644
--- a/features/steps/explore/groups_feature.rb
+++ b/features/steps/explore/groups.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ExploreGroupsFeature < Spinach::FeatureSteps
+class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedGroup
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 7248b2e8d8c..8172f7922cc 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps
+class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
diff --git a/features/steps/group/group.rb b/features/steps/groups.rb
index da0cf7bbd45..616a297db99 100644
--- a/features/steps/group/group.rb
+++ b/features/steps/groups.rb
@@ -201,7 +201,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
protected
- def assigned_to_me key
+ def assigned_to_me(key)
project.send(key).where(assignee_id: current_user.id)
end
diff --git a/features/steps/profile/group.rb b/features/steps/profile/group.rb
index 81d5bc15e21..0a10e04e219 100644
--- a/features/steps/profile/group.rb
+++ b/features/steps/profile/group.rb
@@ -7,22 +7,22 @@ class Spinach::Features::ProfileGroup < Spinach::FeatureSteps
# Leave
step 'I click on the "Leave" button for group "Owned"' do
- find(:css, 'li', text: "Owner").find(:css, 'i.icon-signout').click
+ find(:css, 'li', text: "Owner").find(:css, 'i.fa.fa-sign-out').click
# poltergeist always confirms popups.
end
step 'I click on the "Leave" button for group "Guest"' do
- find(:css, 'li', text: "Guest").find(:css, 'i.icon-signout').click
+ find(:css, 'li', text: "Guest").find(:css, 'i.fa.fa-sign-out').click
# poltergeist always confirms popups.
end
step 'I should not see the "Leave" button for group "Owned"' do
- find(:css, 'li', text: "Owner").should_not have_selector(:css, 'i.icon-signout')
+ find(:css, 'li', text: "Owner").should_not have_selector(:css, 'i.fa.fa-sign-out')
# poltergeist always confirms popups.
end
step 'I should not see the "Leave" button for groupr "Guest"' do
- find(:css, 'li', text: "Guest").should_not have_selector(:css, 'i.icon-signout')
+ find(:css, 'li', text: "Guest").should_not have_selector(:css, 'i.fa.fa-sign-out')
# poltergeist always confirms popups.
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index adfaefb1644..6d747b65bae 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -58,16 +58,34 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I try change my password w/o old one' do
within '.update-password' do
- fill_in "user_password", with: "22233344"
+ fill_in "user_password_profile", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
click_button "Save"
end
end
+ step 'I try to set a weak password' do
+ within '.update-password' do
+ fill_in "user_password_profile", with: "22233344"
+ end
+ end
+
+ step 'I try to set a short password' do
+ within '.update-password' do
+ fill_in "user_password_profile", with: "short"
+ end
+ end
+
+ step 'I try to set a strong password' do
+ within '.update-password' do
+ fill_in "user_password_profile", with: "Itulvo9z8uud%$"
+ end
+ end
+
step 'I change my password' do
within '.update-password' do
fill_in "user_current_password", with: "12345678"
- fill_in "user_password", with: "22233344"
+ fill_in "user_password_profile", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
click_button "Save"
end
@@ -76,7 +94,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I unsuccessfully change my password' do
within '.update-password' do
fill_in "user_current_password", with: "12345678"
- fill_in "user_password", with: "password"
+ fill_in "user_password_profile", with: "password"
fill_in "user_password_confirmation", with: "confirmation"
click_button "Save"
end
@@ -86,6 +104,22 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
page.should have_content "You must provide a valid current password"
end
+ step 'I should see the input field yellow' do
+ page.should have_css 'div.has-warning'
+ end
+
+ step 'I should see the input field green' do
+ page.should have_css 'div.has-success'
+ end
+
+ step 'I should see the input field red' do
+ page.should have_css 'div.has-error'
+ end
+
+ step 'I should see the password error message' do
+ page.should have_content 'Your password is too short'
+ end
+
step "I should see a password error message" do
page.should have_content "Password confirmation doesn't match"
end
@@ -146,7 +180,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I submit new password' do
fill_in :user_current_password, with: '12345678'
- fill_in :user_password, with: '12345678'
+ fill_in :user_password_profile, with: '12345678'
fill_in :user_password_confirmation, with: '12345678'
click_button "Set new password"
end
diff --git a/features/steps/project/browse_files.rb b/features/steps/project/browse_files.rb
deleted file mode 100644
index 536902934ba..00000000000
--- a/features/steps/project/browse_files.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-class Spinach::Features::ProjectBrowseFiles < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
- include RepoHelpers
-
- step 'I should see files from repository' do
- page.should have_content "VERSION"
- page.should have_content ".gitignore"
- page.should have_content "LICENSE"
- end
-
- step 'I should see files from repository for "6d39438"' do
- current_path.should == project_tree_path(@project, "6d39438")
- page.should have_content ".gitignore"
- page.should have_content "LICENSE"
- end
-
- step 'I click on ".gitignore" file in repo' do
- click_link ".gitignore"
- end
-
- step 'I should see its content' do
- page.should have_content "*.rbc"
- end
-
- step 'I click link "raw"' do
- click_link "raw"
- end
-
- step 'I should see raw file content' do
- source.should == sample_blob.data
- end
-
- step 'I click button "edit"' do
- click_link 'edit'
- end
-
- step 'I can edit code' do
- execute_script('editor.setValue("GitlabFileEditor")')
- evaluate_script('editor.getValue()').should == "GitlabFileEditor"
- end
-
- step 'I edit code' do
- execute_script('editor.setValue("GitlabFileEditor")')
- end
-
- step 'I click link "Diff"' do
- click_link 'Diff'
- end
-
- step 'I see diff' do
- page.should have_css '.line_holder.new'
- end
-
- step 'I click on "new file" link in repo' do
- click_link 'new-file-link'
- end
-
- step 'I can see new file page' do
- page.should have_content "New file"
- page.should have_content "File name"
- page.should have_content "Commit message"
- end
-
- step 'I click on files directory' do
- click_link 'files'
- end
-
- step 'I click on history link' do
- click_link 'history'
- end
-
- step 'I see Browse dir link' do
- page.should have_link 'Browse Dir »'
- page.should_not have_link 'Browse Code »'
- end
-
- step 'I click on readme file' do
- within '.tree-table' do
- click_link 'README.md'
- end
- end
-
- step 'I see Browse file link' do
- page.should have_link 'Browse File »'
- page.should_not have_link 'Browse Code »'
- end
-
- step 'I see Browse code link' do
- page.should have_link 'Browse Code »'
- page.should_not have_link 'Browse File »'
- page.should_not have_link 'Browse Dir »'
- end
-
- step 'I click on permalink' do
- click_link 'permalink'
- end
-
- step 'I am redirected to the permalink URL' do
- expect(current_path).to eq(project_blob_path(
- @project, @project.repository.commit.sha + '/.gitignore'))
- end
-
- step "I don't see the permalink link" do
- expect(page).not_to have_link('permalink')
- end
-end
diff --git a/features/steps/project/browse_branches.rb b/features/steps/project/commits/branches.rb
index 3b1e51f179a..07f7e5796a3 100644
--- a/features/steps/project/browse_branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectBrowseBranches < Spinach::FeatureSteps
+class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
diff --git a/features/steps/project/comments_on_commits.rb b/features/steps/project/commits/comments.rb
index 03e6867c7b1..3d4d8ad6368 100644
--- a/features/steps/project/comments_on_commits.rb
+++ b/features/steps/project/commits/comments.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::CommentsOnCommits < Spinach::FeatureSteps
+class Spinach::Features::ProjectCommitsComments < Spinach::FeatureSteps
include SharedAuthentication
include SharedNote
include SharedPaths
diff --git a/features/steps/project/browse_commits.rb b/features/steps/project/commits/commits.rb
index 2048818e88c..935f313e298 100644
--- a/features/steps/project/browse_commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectBrowseCommits < Spinach::FeatureSteps
+class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
@@ -8,7 +8,7 @@ class Spinach::Features::ProjectBrowseCommits < Spinach::FeatureSteps
commit = @project.repository.commit
page.should have_content(@project.name)
page.should have_content(commit.message[0..20])
- page.should have_content(commit.id.to_s[0..5])
+ page.should have_content(commit.short_id)
end
step 'I click atom feed link' do
diff --git a/features/steps/project/comments_on_commit_diffs.rb b/features/steps/project/commits/diff_comments.rb
index 50b978a5d23..b9d8cf2c5a5 100644
--- a/features/steps/project/comments_on_commit_diffs.rb
+++ b/features/steps/project/commits/diff_comments.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::CommentsOnCommitDiffs < Spinach::FeatureSteps
+class Spinach::Features::ProjectCommitsDiffComments < Spinach::FeatureSteps
include SharedAuthentication
include SharedDiffNote
include SharedPaths
diff --git a/features/steps/project/browse_tags.rb b/features/steps/project/commits/tags.rb
index 722c0a91076..3465fcbfd07 100644
--- a/features/steps/project/browse_tags.rb
+++ b/features/steps/project/commits/tags.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectBrowseTags < Spinach::FeatureSteps
+class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
diff --git a/features/steps/project/browse_commits_user_lookup.rb b/features/steps/project/commits/user_lookup.rb
index 196c69eda65..0622fef43bb 100644
--- a/features/steps/project/browse_commits_user_lookup.rb
+++ b/features/steps/project/commits/user_lookup.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectBrowseCommitsUserLookup < Spinach::FeatureSteps
+class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index bcd2524c205..e1062a6ce39 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::CreateProject < Spinach::FeatureSteps
+class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 791c6b1ffb9..da50ba9ced0 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ForkProject < Spinach::FeatureSteps
+class Spinach::Features::ProjectFork < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index e8a6d635308..ccef84cdcc5 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -128,10 +128,6 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
page.should have_select("merge_request_target_project_id", selected: project.path_with_namespace)
end
- def project
- @project ||= Project.find_by!(name: "Shop")
- end
-
# Verify a link is generated against the correct project
def verify_commit_link(container_div, container_project)
# This should force a wait for the javascript to execute
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index c54321717b3..ba460ac8097 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -10,4 +10,14 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
project = Project.find_by(name: "Shop")
visit project_graph_path(project, "master")
end
+
+ step 'I visit project "Shop" commits graph page' do
+ project = Project.find_by(name: "Shop")
+ visit commits_project_graph_path(project, "master")
+ end
+
+ step 'page should have commits graphs' do
+ page.should have_content "Commits statistic for master"
+ page.should have_content "Commits per day of month"
+ end
end
diff --git a/features/steps/project/filter_labels.rb b/features/steps/project/issues/filter_labels.rb
index aaa0cfe3379..e62fa9c84c8 100644
--- a/features/steps/project/filter_labels.rb
+++ b/features/steps/project/issues/filter_labels.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectFilterLabels < Spinach::FeatureSteps
+class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
diff --git a/features/steps/project/issues.rb b/features/steps/project/issues/issues.rb
index 137eac33238..640603562dd 100644
--- a/features/steps/project/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -153,6 +153,14 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
author: project.users.first)
end
+ step 'project "Shop" has "Tasks-open" open issue with task markdown' do
+ create_taskable(:issue, 'Tasks-open')
+ end
+
+ step 'project "Shop" has "Tasks-closed" closed issue with task markdown' do
+ create_taskable(:closed_issue, 'Tasks-closed')
+ end
+
step 'empty project "Empty Project"' do
create :empty_project, name: 'Empty Project', namespace: @user.namespace
end
@@ -232,12 +240,5 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
def filter_issue(text)
fill_in 'issue_search', with: text
-
- # make sure AJAX request finished
- URI.parse(current_url).request_uri == project_issues_path(project, issue_search: text)
- end
-
- def project
- @project ||= Project.find_by(name: 'Shop')
end
end
diff --git a/features/steps/project/labels.rb b/features/steps/project/issues/labels.rb
index b287e183b91..3e3e90824b4 100644
--- a/features/steps/project/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectLabels < Spinach::FeatureSteps
+class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
diff --git a/features/steps/project/milestones.rb b/features/steps/project/issues/milestones.rb
index 13f917a6279..89d7af3c9ee 100644
--- a/features/steps/project/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectMilestones < Spinach::FeatureSteps
+class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index cd8475c14ac..fae0cec53a6 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -97,6 +97,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first)
end
+ step 'project "Shop" has "MR-task-open" open MR with task markdown' do
+ create_taskable(:merge_request, 'MR-task-open')
+ end
+
step 'I switch to the diff tab' do
visit diffs_project_merge_request_path(project, merge_request)
end
@@ -107,7 +111,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I click on the commit in the merge request' do
within '.mr-commits' do
- click_link sample_commit.id[0..8]
+ click_link Commit.truncate_sha(sample_commit.id)
end
end
@@ -154,7 +158,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I modify merge commit message' do
find('.modify-merge-commit-link').click
- fill_in 'merge_commit_message', with: "wow such merge"
+ fill_in 'commit_message', with: 'wow such merge'
end
step 'merge request "Bug NS-05" is mergeable' do
@@ -211,6 +215,18 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
end
+ step 'I should not see a comment like "Line is wrong here" in the second file' do
+ within '.files [id^=diff]:nth-child(2)' do
+ page.should_not have_visible_content "Line is wrong here"
+ end
+ end
+
+ step 'I should see a comment like "Line is wrong here" in the second file' do
+ within '.files [id^=diff]:nth-child(2) .note-text' do
+ page.should have_visible_content "Line is wrong here"
+ end
+ end
+
step 'I leave a comment like "Line is correct" on line 12 of the first file' do
init_diff_note_first_file
@@ -228,13 +244,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
init_diff_note_second_file
within(".js-discussion-note-form") do
- fill_in "note_note", with: "Line is wrong"
+ fill_in "note_note", with: "Line is wrong on here"
click_button "Add Comment"
end
-
- within ".files [id^=diff]:nth-child(2) .note-text" do
- page.should have_content "Line is wrong"
- end
end
step 'I should still see a comment like "Line is correct" in the first file' do
@@ -261,10 +273,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
end
- def project
- @project ||= Project.find_by!(name: "Shop")
- end
-
def merge_request
@merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 3e7061a421f..5e7312d90ff 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -1,10 +1,10 @@
-class Spinach::Features::ProjectFeature < Spinach::FeatureSteps
+class Spinach::Features::Project < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
step 'change project settings' do
- fill_in 'project_name', with: 'NewName'
+ fill_in 'project_name_edit', with: 'NewName'
uncheck 'project_issues_enabled'
end
@@ -25,12 +25,6 @@ class Spinach::Features::ProjectFeature < Spinach::FeatureSteps
project.path.should == "new-path"
end
- step 'I should see project "Shop" README link' do
- within '.project-side' do
- page.should have_content "README.md"
- end
- end
-
step 'I should see project "Shop" version' do
within '.project-side' do
page.should have_content "Version: 6.7.0.pre"
@@ -45,4 +39,18 @@ class Spinach::Features::ProjectFeature < Spinach::FeatureSteps
step 'I should see project default branch changed' do
find(:css, 'select#project_default_branch').value.should == 'fix'
end
+
+ step 'I select project "Forum" README tab' do
+ click_link 'Readme'
+ end
+
+ step 'I should see project "Forum" README' do
+ page.should have_link "README.md"
+ page.should have_content "Sample repo for testing gitlab features"
+ end
+
+ step 'I should see project "Shop" README' do
+ page.should have_link "README.md"
+ page.should have_content "testme"
+ end
end
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index b56b413eac8..d5d58070d86 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -13,6 +13,8 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
page.should have_content 'Hipchat'
page.should have_content 'GitLab CI'
page.should have_content 'Assembla'
+ page.should have_content 'Pushover'
+ page.should have_content 'Atlassian Bamboo'
end
step 'I click gitlab-ci service link' do
@@ -107,15 +109,52 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
step 'I fill Slack settings' do
check 'Active'
- fill_in 'Subdomain', with: 'gitlab'
- fill_in 'Room', with: '#gitlab'
- fill_in 'Token', with: 'verySecret'
+ fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
click_button 'Save'
end
step 'I should see Slack service settings saved' do
- find_field('Subdomain').value.should == 'gitlab'
- find_field('Room').value.should == '#gitlab'
- find_field('Token').value.should == 'verySecret'
+ find_field('Webhook').value.should == 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
+ end
+
+ step 'I click Pushover service link' do
+ click_link 'Pushover'
+ end
+
+ step 'I fill Pushover settings' do
+ check 'Active'
+ fill_in 'Api key', with: 'verySecret'
+ fill_in 'User key', with: 'verySecret'
+ fill_in 'Device', with: 'myDevice'
+ select 'High Priority', from: 'Priority'
+ select 'Bike', from: 'Sound'
+ click_button 'Save'
+ end
+
+ step 'I should see Pushover service settings saved' do
+ find_field('Api key').value.should == 'verySecret'
+ find_field('User key').value.should == 'verySecret'
+ find_field('Device').value.should == 'myDevice'
+ find_field('Priority').find('option[selected]').value.should == '1'
+ find_field('Sound').find('option[selected]').value.should == 'bike'
+ end
+
+ step 'I click Atlassian Bamboo CI service link' do
+ click_link 'Atlassian Bamboo CI'
+ end
+
+ step 'I fill Atlassian Bamboo CI settings' do
+ check 'Active'
+ fill_in 'Bamboo url', with: 'http://bamboo.example.com'
+ fill_in 'Build key', with: 'KEY'
+ fill_in 'Username', with: 'user'
+ fill_in 'Password', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see Atlassian Bamboo CI service settings saved' do
+ find_field('Bamboo url').value.should == 'http://bamboo.example.com'
+ find_field('Build key').value.should == 'KEY'
+ find_field('Username').value.should == 'user'
end
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index 2f8d3205d6c..4a39bfdbb79 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -89,10 +89,6 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
visit project_snippet_path(project, project_snippet)
end
- def project
- @project ||= Project.find_by!(name: "Shop")
- end
-
def project_snippet
@project_snippet ||= ProjectSnippet.find_by!(title: "Snippet one")
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
new file mode 100644
index 00000000000..ddd501d4f88
--- /dev/null
+++ b/features/steps/project/source/browse_files.rb
@@ -0,0 +1,184 @@
+class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+ include RepoHelpers
+
+ step 'I should see files from repository' do
+ page.should have_content "VERSION"
+ page.should have_content ".gitignore"
+ page.should have_content "LICENSE"
+ end
+
+ step 'I should see files from repository for "6d39438"' do
+ current_path.should == project_tree_path(@project, "6d39438")
+ page.should have_content ".gitignore"
+ page.should have_content "LICENSE"
+ end
+
+ step 'I see the ".gitignore"' do
+ page.should have_content '.gitignore'
+ end
+
+ step 'I don\'t see the ".gitignore"' do
+ page.should_not have_content '.gitignore'
+ end
+
+ step 'I click on ".gitignore" file in repo' do
+ click_link ".gitignore"
+ end
+
+ step 'I should see its content' do
+ page.should have_content old_gitignore_content
+ end
+
+ step 'I should see its new content' do
+ page.should have_content new_gitignore_content
+ end
+
+ step 'I click link "Raw"' do
+ click_link 'Raw'
+ end
+
+ step 'I should see raw file content' do
+ source.should == sample_blob.data
+ end
+
+ step 'I click button "Edit"' do
+ click_link 'Edit'
+ end
+
+ step 'I can edit code' do
+ set_new_content
+ evaluate_script('editor.getValue()').should == new_gitignore_content
+ end
+
+ step 'I edit code' do
+ set_new_content
+ end
+
+ step 'I fill the new file name' do
+ fill_in :file_name, with: new_file_name
+ end
+
+ step 'I fill the new file name with an illegal name' do
+ fill_in :file_name, with: '.git'
+ end
+
+ step 'I fill the commit message' do
+ fill_in :commit_message, with: 'Not yet a commit message.'
+ end
+
+ step 'I click link "Diff"' do
+ click_link 'Diff'
+ end
+
+ step 'I click on "Commit Changes"' do
+ click_button 'Commit Changes'
+ end
+
+ step 'I click on "Remove"' do
+ click_button 'Remove'
+ end
+
+ step 'I click on "Remove file"' do
+ click_button 'Remove file'
+ end
+
+ step 'I see diff' do
+ page.should have_css '.line_holder.new'
+ end
+
+ step 'I click on "new file" link in repo' do
+ click_link 'new-file-link'
+ end
+
+ step 'I can see new file page' do
+ page.should have_content "New file"
+ page.should have_content "File name"
+ page.should have_content "Commit message"
+ end
+
+ step 'I click on files directory' do
+ click_link 'files'
+ end
+
+ step 'I click on History link' do
+ click_link 'History'
+ end
+
+ step 'I see Browse dir link' do
+ page.should have_link 'Browse Dir »'
+ page.should_not have_link 'Browse Code »'
+ end
+
+ step 'I click on readme file' do
+ within '.tree-table' do
+ click_link 'README.md'
+ end
+ end
+
+ step 'I see Browse file link' do
+ page.should have_link 'Browse File »'
+ page.should_not have_link 'Browse Code »'
+ end
+
+ step 'I see Browse code link' do
+ page.should have_link 'Browse Code »'
+ page.should_not have_link 'Browse File »'
+ page.should_not have_link 'Browse Dir »'
+ end
+
+ step 'I click on Permalink' do
+ click_link 'Permalink'
+ end
+
+ step 'I am redirected to the files URL' do
+ current_path.should == project_tree_path(@project, 'master')
+ end
+
+ step 'I am redirected to the ".gitignore"' do
+ expect(current_path).to eq(project_blob_path(@project, 'master/.gitignore'))
+ end
+
+ step 'I am redirected to the permalink URL' do
+ expect(current_path).to eq(project_blob_path(
+ @project, @project.repository.commit.sha + '/.gitignore'))
+ end
+
+ step 'I am redirected to the new file' do
+ expect(current_path).to eq(project_blob_path(
+ @project, 'master/' + new_file_name))
+ end
+
+ step "I don't see the permalink link" do
+ expect(page).not_to have_link('permalink')
+ end
+
+ step 'I see a commit error message' do
+ expect(page).to have_content('Your changes could not be committed')
+ end
+
+ private
+
+ def set_new_content
+ execute_script("editor.setValue('#{new_gitignore_content}')")
+ end
+
+ # Content of the gitignore file on the seed repository.
+ def old_gitignore_content
+ '*.rbc'
+ end
+
+ # Constant value that differs from the content
+ # of the gitignore of the seed repository.
+ def new_gitignore_content
+ old_gitignore_content + 'a'
+ end
+
+ # Constant value that is a valid filename and
+ # not a filename present at root of the seed repository.
+ def new_file_name
+ 'not_a_file.md'
+ end
+end
diff --git a/features/steps/project/browse_git_repo.rb b/features/steps/project/source/git_blame.rb
index 09f44eb4c6d..e29a816c51b 100644
--- a/features/steps/project/browse_git_repo.rb
+++ b/features/steps/project/source/git_blame.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectBrowseGitRepo < Spinach::FeatureSteps
+class Spinach::Features::ProjectSourceGitBlame < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
@@ -7,8 +7,8 @@ class Spinach::Features::ProjectBrowseGitRepo < Spinach::FeatureSteps
click_link ".gitignore"
end
- step 'I click blame button' do
- click_link "blame"
+ step 'I click Blame button' do
+ click_link 'Blame'
end
step 'I should see git file blame' do
diff --git a/features/steps/project/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index a8631af2e05..53578ee5970 100644
--- a/features/steps/project/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -1,7 +1,7 @@
# If you need to modify the existing seed repository for your tests,
# it is recommended that you make the changes on the `markdown` branch of the seed project repository,
# which should only be used by tests in this file. See `/spec/factories.rb#project` for more info.
-class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
+class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedMarkdown
diff --git a/features/steps/project/multiselect_blob.rb b/features/steps/project/source/multiselect_blob.rb
index 611289a6162..b749ba49371 100644
--- a/features/steps/project/multiselect_blob.rb
+++ b/features/steps/project/source/multiselect_blob.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectMultiselectBlob < Spinach::FeatureSteps
+class Spinach::Features::ProjectSourceMultiselectBlob < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
diff --git a/features/steps/project/search_code.rb b/features/steps/project/source/search_code.rb
index 5753296251f..9c2864cc936 100644
--- a/features/steps/project/search_code.rb
+++ b/features/steps/project/source/search_code.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProjectSearchCode < Spinach::FeatureSteps
+class Spinach::Features::ProjectSourceSearchCode < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
diff --git a/features/steps/search.rb b/features/steps/search.rb
index b1058989d0b..f3d8bd80f13 100644
--- a/features/steps/search.rb
+++ b/features/steps/search.rb
@@ -66,8 +66,4 @@ class Spinach::Features::Search < Spinach::FeatureSteps
step 'I should not see "Bar" link' do
page.should_not have_link "Bar"
end
-
- def project
- @project ||= Project.find_by(name: "Shop")
- end
end
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
index 092f2fceb57..8bf138065b0 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -6,7 +6,52 @@ module SharedMarkdown
find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/
end
+ def create_taskable(type, title)
+ desc_text = <<EOT.gsub(/^ {6}/, '')
+ * [ ] Task 1
+ * [x] Task 2
+EOT
+
+ case type
+ when :issue, :closed_issue
+ options = { project: project }
+ when :merge_request
+ options = { source_project: project, target_project: project }
+ end
+
+ create(
+ type,
+ options.merge(title: title,
+ author: project.users.first,
+ description: desc_text)
+ )
+ end
+
step 'Header "Description header" should have correct id and link' do
header_should_have_correct_id_and_link(1, 'Description header', 'description-header')
end
+
+ step 'I should see task checkboxes in the description' do
+ expect(page).to have_selector(
+ 'div.description li.task-list-item input[type="checkbox"]'
+ )
+ end
+
+ step 'I should see the task status for the Taskable' do
+ expect(find(:css, 'span.task-status').text).to eq(
+ '2 tasks (1 done, 1 unfinished)'
+ )
+ end
+
+ step 'Task checkboxes should be enabled' do
+ expect(page).to have_selector(
+ 'div.description li.task-list-item input[type="checkbox"]:enabled'
+ )
+ end
+
+ step 'Task checkboxes should be disabled' do
+ expect(page).to have_selector(
+ 'div.description li.task-list-item input[type="checkbox"]:disabled'
+ )
+ end
end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 4019fe3697a..2b2cb47a715 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -119,4 +119,18 @@ module SharedNote
page.should_not have_css("#comment-with-a-header")
end
end
+
+ step 'I leave a comment with task markdown' do
+ within('.js-main-target-form') do
+ fill_in 'note[note]', with: '* [x] Task item'
+ click_button 'Add Comment'
+ sleep 0.05
+ end
+ end
+
+ step 'I should not see task checkboxes in the comment' do
+ expect(page).not_to have_selector(
+ 'li.note div.timeline-content input[type="checkbox"]'
+ )
+ end
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index babbef33ec4..5f292255ce1 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -265,6 +265,15 @@ module SharedPaths
visit project_blob_path(@project, File.join(root_ref, '.gitignore'))
end
+ step 'I am on the new file page' do
+ current_path.should eq(project_new_tree_path(@project, root_ref))
+ end
+
+ step 'I am on the ".gitignore" edit file page' do
+ current_path.should eq(project_edit_tree_path(
+ @project, File.join(root_ref, '.gitignore')))
+ end
+
step 'I visit project source page for "6d39438"' do
visit project_tree_path(@project, "6d39438")
end
@@ -292,6 +301,16 @@ module SharedPaths
visit project_issue_path(issue.project, issue)
end
+ step 'I visit issue page "Tasks-open"' do
+ issue = Issue.find_by(title: 'Tasks-open')
+ visit project_issue_path(issue.project, issue)
+ end
+
+ step 'I visit issue page "Tasks-closed"' do
+ issue = Issue.find_by(title: 'Tasks-closed')
+ visit project_issue_path(issue.project, issue)
+ end
+
step 'I visit project "Shop" labels page' do
project = Project.find_by(name: 'Shop')
visit project_labels_path(project)
@@ -322,6 +341,16 @@ module SharedPaths
visit project_merge_request_path(mr.target_project, mr)
end
+ step 'I visit merge request page "MR-task-open"' do
+ mr = MergeRequest.find_by(title: 'MR-task-open')
+ visit project_merge_request_path(mr.target_project, mr)
+ end
+
+ step 'I visit merge request page "MR-task-closed"' do
+ mr = MergeRequest.find_by(title: 'MR-task-closed')
+ visit project_merge_request_path(mr.target_project, mr)
+ end
+
step 'I visit project "Shop" merge requests page' do
visit project_merge_requests_path(project)
end
@@ -411,7 +440,7 @@ module SharedPaths
end
def project
- project = Project.find_by!(name: "Shop")
+ Project.find_by!(name: 'Shop')
end
# ----------------------------------------
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 4b833850a1c..bd7e6e1d8b3 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -32,7 +32,7 @@ module SharedProject
@project = Project.find_by(name: "Shop")
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "6d394385cf567f80a8fd85055db1ab4c5295806f",
ref: "refs/heads/fix",
user_id: @user.id,
diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb
index 5a27e8750cf..bb596c1620a 100644
--- a/features/steps/shared/snippet.rb
+++ b/features/steps/shared/snippet.rb
@@ -6,7 +6,7 @@ module SharedSnippet
title: "Personal snippet one",
content: "Test content",
file_name: "snippet.rb",
- private: false,
+ visibility_level: Snippet::PUBLIC,
author: current_user)
end
@@ -15,9 +15,19 @@ module SharedSnippet
title: "Personal snippet private",
content: "Provate content",
file_name: "private_snippet.rb",
- private: true,
+ visibility_level: Snippet::PRIVATE,
author: current_user)
end
+
+ step 'I have internal "Personal snippet internal" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet internal",
+ content: "Provate content",
+ file_name: "internal_snippet.rb",
+ visibility_level: Snippet::INTERNAL,
+ author: current_user)
+ end
+
step 'I have a public many lined snippet' do
create(:personal_snippet,
title: 'Many lined snippet',
@@ -38,7 +48,16 @@ module SharedSnippet
|line fourteen
END
file_name: 'many_lined_snippet.rb',
- private: true,
+ visibility_level: Snippet::PUBLIC,
author: current_user)
end
+
+ step 'There is public "Personal snippet one" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet one",
+ content: "Test content",
+ file_name: "snippet.rb",
+ visibility_level: Snippet::PUBLIC,
+ author: create(:user))
+ end
end
diff --git a/features/steps/snippets/discover.rb b/features/steps/snippets/discover.rb
index da73dfc68be..2667c1e3d44 100644
--- a/features/steps/snippets/discover.rb
+++ b/features/steps/snippets/discover.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::DiscoverSnippets < Spinach::FeatureSteps
+class Spinach::Features::SnippetsDiscover < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedSnippet
@@ -7,6 +7,10 @@ class Spinach::Features::DiscoverSnippets < Spinach::FeatureSteps
page.should have_content "Personal snippet one"
end
+ step 'I should see "Personal snippet internal" in snippets' do
+ page.should have_content "Personal snippet internal"
+ end
+
step 'I should not see "Personal snippet private" in snippets' do
page.should_not have_content "Personal snippet private"
end
diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb
new file mode 100644
index 00000000000..67669dc0a69
--- /dev/null
+++ b/features/steps/snippets/public_snippets.rb
@@ -0,0 +1,25 @@
+class Spinach::Features::PublicSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedSnippet
+
+ step 'I should see snippet "Personal snippet one"' do
+ page.should have_no_xpath("//i[@class='public-snippet']")
+ end
+
+ step 'I should see raw snippet "Personal snippet one"' do
+ page.should have_text(snippet.content)
+ end
+
+ step 'I visit snippet page "Personal snippet one"' do
+ visit snippet_path(snippet)
+ end
+
+ step 'I visit snippet raw page "Personal snippet one"' do
+ visit raw_snippet_path(snippet)
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
+ end
+end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index e8154c8ce57..de936db85ee 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::SnippetsFeature < Spinach::FeatureSteps
+class Spinach::Features::Snippets < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
@@ -46,7 +46,7 @@ class Spinach::Features::SnippetsFeature < Spinach::FeatureSteps
end
step 'I uncheck "Private" checkbox' do
- choose "Public"
+ choose "Internal"
click_button "Save"
end
diff --git a/features/steps/snippets/user.rb b/features/steps/snippets/user.rb
index 71a1952926f..866f637ab6c 100644
--- a/features/steps/snippets/user.rb
+++ b/features/steps/snippets/user.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::UserSnippets < Spinach::FeatureSteps
+class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedSnippet
@@ -15,6 +15,10 @@ class Spinach::Features::UserSnippets < Spinach::FeatureSteps
page.should have_content "Personal snippet private"
end
+ step 'I should see "Personal snippet internal" in snippets' do
+ page.should have_content "Personal snippet internal"
+ end
+
step 'I should not see "Personal snippet one" in snippets' do
page.should_not have_content "Personal snippet one"
end
@@ -23,9 +27,13 @@ class Spinach::Features::UserSnippets < Spinach::FeatureSteps
page.should_not have_content "Personal snippet private"
end
- step 'I click "Public" filter' do
+ step 'I should not see "Personal snippet internal" in snippets' do
+ page.should_not have_content "Personal snippet internal"
+ end
+
+ step 'I click "Internal" filter' do
within('.nav-stacked') do
- click_link "Public"
+ click_link "Internal"
end
end
@@ -35,6 +43,12 @@ class Spinach::Features::UserSnippets < Spinach::FeatureSteps
end
end
+ step 'I click "Public" filter' do
+ within('.nav-stacked') do
+ click_link "Public"
+ end
+ end
+
def snippet
@snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end
diff --git a/features/support/env.rb b/features/support/env.rb
index 4b76faab564..67660777842 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -4,7 +4,7 @@ end
if ENV['COVERALLS']
require 'coveralls'
- Coveralls.wear_merged!('rails')
+ Coveralls.wear_merged!
end
ENV['RAILS_ENV'] = 'test'
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 2c7cd9038c3..d26667ba3f7 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -27,6 +27,7 @@ module API
helpers APIHelpers
mount Groups
+ mount GroupMembers
mount Users
mount Projects
mount Repositories
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 14f8b20f6b2..6ec1a753a69 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -82,6 +82,7 @@ module API
authorize_push_project
result = CreateBranchService.new(user_project, current_user).
execute(params[:branch_name], params[:ref])
+
if result[:status] == :success
present result[:branch],
with: Entities::RepoObject,
@@ -104,7 +105,9 @@ module API
execute(params[:branch])
if result[:status] == :success
- true
+ {
+ branch_name: params[:branch]
+ }
else
render_api_error!(result[:message], result[:return_code])
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index c7b86ed3d76..42e4442365d 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -16,7 +16,8 @@ module API
class UserFull < User
expose :email
- expose :theme_id, :color_scheme_id, :extern_uid, :provider
+ expose :theme_id, :color_scheme_id, :extern_uid, :provider, \
+ :projects_limit
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
end
@@ -30,7 +31,8 @@ module API
end
class ProjectHook < Hook
- expose :project_id, :push_events, :issues_events, :merge_requests_events
+ expose :project_id, :push_events
+ expose :issues_events, :merge_requests_events, :tag_push_events
end
class ForkedFromProject < Grape::Entity
@@ -72,6 +74,25 @@ module API
end
end
+ class RepoTag < Grape::Entity
+ expose :name
+ expose :message do |repo_obj, _options|
+ if repo_obj.respond_to?(:message)
+ repo_obj.message
+ else
+ nil
+ end
+ end
+
+ expose :commit do |repo_obj, options|
+ if repo_obj.respond_to?(:commit)
+ repo_obj.commit
+ elsif options[:project]
+ options[:project].repository.commit(repo_obj.target)
+ end
+ end
+ end
+
class RepoObject < Grape::Entity
expose :name
@@ -171,6 +192,12 @@ module API
expose :target_id, :target_type, :author_id
expose :data, :target_title
expose :created_at
+
+ expose :author_username do |event, options|
+ if event.author
+ event.author.username
+ end
+ end
end
class Namespace < Grape::Entity
diff --git a/lib/api/files.rb b/lib/api/files.rb
index e63e635a4d3..84e1d311781 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -85,7 +85,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
@@ -117,7 +117,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
@@ -149,7 +149,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
end
diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb
new file mode 100644
index 00000000000..d596517c816
--- /dev/null
+++ b/lib/api/group_members.rb
@@ -0,0 +1,80 @@
+module API
+ class GroupMembers < Grape::API
+ before { authenticate! }
+
+ resource :groups do
+ helpers do
+ def find_group(id)
+ group = Group.find(id)
+
+ if can?(current_user, :read_group, group)
+ group
+ else
+ render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403)
+ end
+ end
+
+ def validate_access_level?(level)
+ Gitlab::Access.options_with_owner.values.include? level.to_i
+ end
+ end
+
+ # Get a list of group members viewable by the authenticated user.
+ #
+ # Example Request:
+ # GET /groups/:id/members
+ get ":id/members" do
+ group = find_group(params[:id])
+ members = group.group_members
+ users = (paginate members).collect(&:user)
+ present users, with: Entities::GroupMember, group: group
+ end
+
+ # Add a user to the list of group members
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /groups/:id/members
+ post ":id/members" do
+ group = find_group(params[:id])
+ authorize! :manage_group, group
+ required_attributes! [:user_id, :access_level]
+
+ unless validate_access_level?(params[:access_level])
+ render_api_error!("Wrong access level", 422)
+ end
+
+ if group.group_members.find_by(user_id: params[:user_id])
+ render_api_error!("Already exists", 409)
+ end
+
+ group.add_users([params[:user_id]], params[:access_level])
+ member = group.group_members.find_by(user_id: params[:user_id])
+ present member.user, with: Entities::GroupMember, group: group
+ end
+
+ # Remove member.
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ #
+ # Example Request:
+ # DELETE /groups/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ group = find_group(params[:id])
+ authorize! :manage_group, group
+ member = group.group_members.find_by(user_id: params[:user_id])
+
+ if member.nil?
+ render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
+ else
+ member.destroy
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 4841e04689d..f0ab6938b1c 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -97,57 +97,6 @@ module API
not_found!
end
end
-
- # Get a list of group members viewable by the authenticated user.
- #
- # Example Request:
- # GET /groups/:id/members
- get ":id/members" do
- group = find_group(params[:id])
- members = group.group_members
- users = (paginate members).collect(&:user)
- present users, with: Entities::GroupMember, group: group
- end
-
- # Add a user to the list of group members
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- # access_level (required) - Project access level
- # Example Request:
- # POST /groups/:id/members
- post ":id/members" do
- required_attributes! [:user_id, :access_level]
- unless validate_access_level?(params[:access_level])
- render_api_error!("Wrong access level", 422)
- end
- group = find_group(params[:id])
- if group.group_members.find_by(user_id: params[:user_id])
- render_api_error!("Already exists", 409)
- end
- group.add_users([params[:user_id]], params[:access_level])
- member = group.group_members.find_by(user_id: params[:user_id])
- present member.user, with: Entities::GroupMember, group: group
- end
-
- # Remove member.
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- #
- # Example Request:
- # DELETE /groups/:id/members/:user_id
- delete ":id/members/:user_id" do
- group = find_group(params[:id])
- member = group.group_members.find_by(user_id: params[:user_id])
- if member.nil?
- render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
- else
- member.destroy
- end
- end
end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 3a619169eca..027fb20ec46 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -67,11 +67,15 @@ module API
unauthorized! unless current_user
end
+ def authenticate_by_gitlab_shell_token!
+ unauthorized! unless secret_token == params['secret_token']
+ end
+
def authenticated_as_admin!
forbidden! unless current_user.is_admin?
end
- def authorize! action, subject
+ def authorize!(action, subject)
unless abilities.allowed?(current_user, action, subject)
forbidden!
end
@@ -193,5 +197,9 @@ module API
abilities
end
end
+
+ def secret_token
+ File.read(Rails.root.join('.gitlab_shell_secret'))
+ end
end
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 5f484f63418..ebf2296097d 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,6 +1,10 @@
module API
# Internal access API
class Internal < Grape::API
+ before {
+ authenticate_by_gitlab_shell_token!
+ }
+
namespace 'internal' do
# Check if git command is allowed to project
#
@@ -14,13 +18,20 @@ module API
#
post "/allowed" do
status 200
+ project_path = params[:project]
# Check for *.wiki repositories.
# Strip out the .wiki from the pathname before finding the
# project. This applies the correct project permissions to
# the wiki repository as well.
- project_path = params[:project]
- project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/
+ access =
+ if project_path =~ /\.wiki\Z/
+ project_path.sub!(/\.wiki\Z/, '')
+ Gitlab::GitAccessWiki.new
+ else
+ Gitlab::GitAccess.new
+ end
+
project = Project.find_with_namespace(project_path)
return false unless project
@@ -32,7 +43,7 @@ module API
return false unless actor
- Gitlab::GitAccess.new.allowed?(
+ access.allowed?(
actor,
params[:action],
project,
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 30170c657ba..d2828b24c36 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -4,7 +4,7 @@ module API
before { authenticate! }
helpers do
- def filter_issues_state(issues, state = nil)
+ def filter_issues_state(issues, state)
case state
when 'opened' then issues.opened
when 'closed' then issues.closed
@@ -13,7 +13,11 @@ module API
end
def filter_issues_labels(issues, labels)
- issues.includes(:labels).where("labels.title" => labels.split(','))
+ issues.includes(:labels).where('labels.title' => labels.split(','))
+ end
+
+ def filter_issues_milestone(issues, milestone)
+ issues.includes(:milestone).where('milestones.title' => milestone)
end
end
@@ -48,19 +52,24 @@ module API
# id (required) - The ID of a project
# state (optional) - Return "opened" or "closed" issues
# labels (optional) - Comma-separated list of label names
+ # milestone (optional) - Milestone title
#
# Example Requests:
# GET /projects/:id/issues
# GET /projects/:id/issues?state=opened
# GET /projects/:id/issues?state=closed
- # GET /projects/:id/issues
# GET /projects/:id/issues?labels=foo
# GET /projects/:id/issues?labels=foo,bar
# GET /projects/:id/issues?labels=foo,bar&state=opened
+ # GET /projects/:id/issues?milestone=1.0.0
+ # GET /projects/:id/issues?milestone=1.0.0&state=closed
get ":id/issues" do
issues = user_project.issues
issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
+ unless params[:milestone].nil?
+ issues = filter_issues_milestone(issues, params[:milestone])
+ end
issues = issues.order('issues.id DESC')
present paginate(issues), with: Entities::Issue
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 79c3d122d32..7d056b9bf58 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -38,7 +38,13 @@ module API
# POST /projects/:id/hooks
post ":id/hooks" do
required_attributes! [:url]
- attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events]
+ attrs = attributes_for_keys [
+ :url,
+ :push_events,
+ :issues_events,
+ :merge_requests_events,
+ :tag_push_events
+ ]
@hook = user_project.hooks.new(attrs)
if @hook.save
@@ -62,7 +68,13 @@ module API
put ":id/hooks/:hook_id" do
@hook = user_project.hooks.find(params[:hook_id])
required_attributes! [:url]
- attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events]
+ attrs = attributes_for_keys [
+ :url,
+ :push_events,
+ :issues_events,
+ :merge_requests_events,
+ :tag_push_events
+ ]
if @hook.update_attributes attrs
present @hook, with: Entities::ProjectHook
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index f555819df1b..7fcf97d1ad6 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -153,6 +153,23 @@ module API
end
end
+ # Fork new project for the current user.
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request
+ # POST /projects/fork/:id
+ post 'fork/:id' do
+ @forked_project =
+ ::Projects::ForkService.new(user_project,
+ current_user).execute
+ if @forked_project.errors.any?
+ conflict!(@forked_project.errors.messages)
+ else
+ present @forked_project, with: Entities::Project
+ end
+ end
+
# Remove project
#
# Parameters:
@@ -161,7 +178,7 @@ module API
# DELETE /projects/:id
delete ":id" do
authorize! :remove_project, user_project
- user_project.destroy
+ ::Projects::DestroyService.new(user_project, current_user, {}).execute
end
# Mark this project as forked from another
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 626d99c2649..a1a7721b288 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -23,7 +23,8 @@ module API
# Example Request:
# GET /projects/:id/repository/tags
get ":id/repository/tags" do
- present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project
+ present user_project.repo.tags.sort_by(&:name).reverse,
+ with: Entities::RepoTag, project: user_project
end
# Create tag
@@ -43,7 +44,7 @@ module API
if result[:status] == :success
present result[:tag],
- with: Entities::RepoObject,
+ with: Entities::RepoTag,
project: user_project
else
render_api_error!(result[:message], 400)
diff --git a/lib/api/services.rb b/lib/api/services.rb
index bde502e32e1..3ad59cf3adf 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -28,7 +28,7 @@ module API
# Delete GitLab CI service settings
#
# Example Request:
- # DELETE /projects/:id/keys/:id
+ # DELETE /projects/:id/services/gitlab-ci
delete ":id/services/gitlab-ci" do
if user_project.gitlab_ci_service
user_project.gitlab_ci_service.update_attributes(
@@ -38,7 +38,41 @@ module API
)
end
end
+
+ # Set Hipchat service for project
+ #
+ # Parameters:
+ # token (required) - Hipchat token
+ # room (required) - Hipchat room name
+ #
+ # Example Request:
+ # PUT /projects/:id/services/hipchat
+ put ':id/services/hipchat' do
+ required_attributes! [:token, :room]
+ attrs = attributes_for_keys [:token, :room]
+ user_project.build_missing_services
+
+ if user_project.hipchat_service.update_attributes(
+ attrs.merge(active: true))
+ true
+ else
+ not_found!
+ end
+ end
+
+ # Delete Hipchat service settings
+ #
+ # Example Request:
+ # DELETE /projects/:id/services/hipchat
+ delete ':id/services/hipchat' do
+ if user_project.hipchat_service
+ user_project.hipchat_service.update_attributes(
+ active: false,
+ token: nil,
+ room: nil
+ )
+ end
+ end
end
end
end
-
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 7b6908ccad8..d12d30a9110 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -21,6 +21,7 @@ module Backup
system('pg_dump', config['database'], out: db_file_name)
end
report_success(success)
+ abort 'Backup failed' unless success
end
def restore
@@ -37,6 +38,7 @@ module Backup
system('psql', config['database'], '-f', db_file_name)
end
report_success(success)
+ abort 'Restore failed' unless success
end
protected
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 28e323fe30d..03fe0f0b02f 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -9,6 +9,7 @@ module Backup
s[:backup_created_at] = Time.now
s[:gitlab_version] = Gitlab::VERSION
s[:tar_version] = tar_version
+ tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar"
Dir.chdir(Gitlab.config.backup.path)
@@ -17,11 +18,34 @@ module Backup
end
# create archive
- print "Creating backup archive: #{s[:backup_created_at].to_i}_gitlab_backup.tar ... "
- if Kernel.system('tar', '-cf', "#{s[:backup_created_at].to_i}_gitlab_backup.tar", *BACKUP_CONTENTS)
+ print "Creating backup archive: #{tar_file} ... "
+ if Kernel.system('tar', '-cf', tar_file, *BACKUP_CONTENTS)
puts "done".green
else
puts "failed".red
+ abort 'Backup failed'
+ end
+
+ upload(tar_file)
+ end
+
+ def upload(tar_file)
+ remote_directory = Gitlab.config.backup.upload.remote_directory
+ print "Uploading backup archive to remote storage #{remote_directory} ... "
+
+ connection_settings = Gitlab.config.backup.upload.connection
+ if connection_settings.blank?
+ puts "skipped".yellow
+ return
+ end
+
+ connection = ::Fog::Storage.new(connection_settings)
+ directory = connection.directories.get(remote_directory)
+ if directory.files.create(key: tar_file, body: File.open(tar_file), public: false)
+ puts "done".green
+ else
+ puts "failed".red
+ abort 'Backup failed'
end
end
@@ -31,6 +55,7 @@ module Backup
puts "done".green
else
puts "failed".red
+ abort 'Backup failed'
end
end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index ea05fa2c261..0bb02f1a357 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -15,22 +15,31 @@ module Backup
if project.empty_repo?
puts "[SKIPPED]".cyan
- elsif system(*%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all), silent)
- puts "[DONE]".green
else
- puts "[FAILED]".red
+ output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all))
+ if status.zero?
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ puts output
+ abort 'Backup failed'
+ end
end
wiki = ProjectWiki.new(project)
if File.exists?(path_to_repo(wiki))
print " * #{wiki.path_with_namespace} ... "
- if wiki.empty?
+ if wiki.repository.empty?
puts " [SKIPPED]".cyan
- elsif system(*%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all), silent)
- puts " [DONE]".green
else
- puts " [FAILED]".red
+ output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all))
+ if status.zero?
+ puts " [DONE]".green
+ else
+ puts " [FAILED]".red
+ abort 'Backup failed'
+ end
end
end
end
@@ -54,6 +63,7 @@ module Backup
puts "[DONE]".green
else
puts "[FAILED]".red
+ abort 'Restore failed'
end
wiki = ProjectWiki.new(project)
@@ -64,6 +74,7 @@ module Backup
puts " [DONE]".green
else
puts " [FAILED]".red
+ abort 'Restore failed'
end
end
end
@@ -80,7 +91,7 @@ module Backup
protected
def path_to_repo(project)
- File.join(repos_path, project.path_with_namespace + '.git')
+ project.repository.path_to_repo
end
def path_to_bundle(project)
diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb
new file mode 100644
index 00000000000..1b80be112a4
--- /dev/null
+++ b/lib/disable_email_interceptor.rb
@@ -0,0 +1,8 @@
+# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
+class DisableEmailInterceptor
+
+ def self.delivering_email(message)
+ message.perform_deliveries = false
+ Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}"
+ end
+end
diff --git a/lib/event_filter.rb b/lib/event_filter.rb
index 9b4b8c3801a..163937c02cf 100644
--- a/lib/event_filter.rb
+++ b/lib/event_filter.rb
@@ -23,7 +23,7 @@ class EventFilter
end
end
- def initialize params
+ def initialize(params)
@params = if params
params.dup
else
@@ -31,7 +31,7 @@ class EventFilter
end
end
- def apply_filter events
+ def apply_filter(events)
return events unless params.present?
filter = params.dup
@@ -50,7 +50,7 @@ class EventFilter
events = events.where(action: actions)
end
- def options key
+ def options(key)
filter = params.dup
if filter.include? key
@@ -62,7 +62,7 @@ class EventFilter
filter
end
- def active? key
+ def active?(key)
params.include? key
end
end
diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb
index 8e4717b46e6..dddcb2538f9 100644
--- a/lib/gitlab/app_logger.rb
+++ b/lib/gitlab/app_logger.rb
@@ -1,7 +1,7 @@
module Gitlab
class AppLogger < Gitlab::Logger
- def self.file_name
- 'application.log'
+ def self.file_name_noext
+ 'application'
end
def format_message(severity, timestamp, progname, msg)
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 955abc1bedd..30509528b8b 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,24 +1,18 @@
module Gitlab
class Auth
def find(login, password)
- user = User.find_by(email: login) || User.find_by(username: login)
+ user = User.by_login(login)
+ # If no user is found, or it's an LDAP server, try LDAP.
+ # LDAP users are only authenticated via LDAP
if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication
- return nil unless ldap_conf.enabled
+ return nil unless Gitlab::LDAP::Config.enabled?
- Gitlab::LDAP::User.authenticate(login, password)
+ Gitlab::LDAP::Authentication.login(login, password)
else
user if user.valid_password?(password)
end
end
-
- def log
- Gitlab::AppLogger
- end
-
- def ldap_conf
- @ldap_conf ||= Gitlab.config.ldap
- end
end
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index c2f3b851c07..df1461a45c9 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -90,7 +90,7 @@ module Grack
when *Gitlab::GitAccess::PUSH_COMMANDS
if user
# Skip user authorization on upload request.
- # It will be serverd by update hook in repository
+ # It will be done by the pre-receive hook in the repository.
true
else
false
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 907373ab991..aabc7f1e69a 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -8,6 +8,13 @@ module Gitlab
end
end
+ class << self
+ def version_required
+ @version_required ||= File.read(Rails.root.
+ join('GITLAB_SHELL_VERSION')).strip
+ end
+ end
+
# Init new repository
#
# name - project path with namespace
@@ -16,7 +23,8 @@ module Gitlab
# add_repository("gitlab/gitlab-ci")
#
def add_repository(name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path,
+ 'add-project', "#{name}.git"])
end
# Import repository
@@ -27,7 +35,8 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(name, url)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url, '240'
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project',
+ "#{name}.git", url, '240'])
end
# Move repository
@@ -39,7 +48,8 @@ module Gitlab
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
#
def mv_repository(path, new_path)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
+ "#{path}.git", "#{new_path}.git"])
end
# Update HEAD for repository
@@ -51,7 +61,8 @@ module Gitlab
# update_repository_head("gitlab/gitlab-ci", "3-1-stable")
#
def update_repository_head(path, branch)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head',
+ "#{path}.git", branch])
end
# Fork repository to new namespace
@@ -63,7 +74,8 @@ module Gitlab
# fork_repository("gitlab/gitlab-ci", "randx")
#
def fork_repository(path, fork_namespace)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
+ "#{path}.git", fork_namespace])
end
# Remove repository from file system
@@ -74,7 +86,8 @@ module Gitlab
# remove_repository("gitlab/gitlab-ci")
#
def remove_repository(name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path,
+ 'rm-project', "#{name}.git"])
end
# Add repository branch from passed ref
@@ -87,7 +100,8 @@ module Gitlab
# add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
#
def add_branch(path, branch_name, ref)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch',
+ "#{path}.git", branch_name, ref])
end
# Remove repository branch
@@ -99,7 +113,8 @@ module Gitlab
# rm_branch("gitlab/gitlab-ci", "4-0-stable")
#
def rm_branch(path, branch_name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch',
+ "#{path}.git", branch_name])
end
# Add repository tag from passed ref
@@ -117,7 +132,7 @@ module Gitlab
cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git
#{tag_name} #{ref})
cmd << message unless message.nil? || message.empty?
- system *cmd
+ Gitlab::Utils.system_silent(cmd)
end
# Remove repository tag
@@ -129,7 +144,8 @@ module Gitlab
# rm_tag("gitlab/gitlab-ci", "v4.0")
#
def rm_tag(path, tag_name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag',
+ "#{path}.git", tag_name])
end
# Add new key to gitlab-shell
@@ -138,7 +154,8 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
- system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path,
+ 'add-key', key_id, key_content])
end
# Batch-add keys to authorized_keys
@@ -157,7 +174,8 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...")
#
def remove_key(key_id, key_content)
- system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path,
+ 'rm-key', key_id, key_content])
end
# Remove all ssh keys from gitlab shell
@@ -166,7 +184,7 @@ module Gitlab
# remove_all_keys
#
def remove_all_keys
- system "#{gitlab_shell_path}/bin/gitlab-keys", "clear"
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear'])
end
# Add empty directory for storing repositories
@@ -213,7 +231,7 @@ module Gitlab
FileUtils.rm_r(satellites_path, force: true)
end
- def url_to_repo path
+ def url_to_repo(path)
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
end
@@ -249,5 +267,13 @@ module Gitlab
def exists?(dir_name)
File.exists?(full_path(dir_name))
end
+
+ def gitlab_shell_projects_path
+ File.join(gitlab_shell_path, 'bin', 'gitlab-projects')
+ end
+
+ def gitlab_shell_keys_path
+ File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
+ end
end
end
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index 90f1370c209..401e6e047b1 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -6,7 +6,7 @@ module Gitlab
md = ISSUE_CLOSING_REGEX.match(message)
if md
extractor = Gitlab::ReferenceExtractor.new
- extractor.analyze(md[0])
+ extractor.analyze(md[0], project)
extractor.issues_for(project)
else
[]
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index 9d6309954a4..f7c1f20d762 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -72,7 +72,7 @@ module Gitlab
end
end
- def html_escape str
+ def html_escape(str)
replacements = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
str.gsub(/[&"'><]/, replacements)
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
new file mode 100644
index 00000000000..67aca5e36e9
--- /dev/null
+++ b/lib/gitlab/git.rb
@@ -0,0 +1,5 @@
+module Gitlab
+ module Git
+ BLANK_SHA = '0' * 40
+ end
+end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 6247dd59867..129881060d5 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -49,25 +49,7 @@ module Gitlab
# Iterate over all changes to find if user allowed all of them to be applied
changes.each do |change|
- oldrev, newrev, ref = change.split(' ')
-
- action = if project.protected_branch?(branch_name(ref))
- # we dont allow force push to protected branch
- if forced_push?(project, oldrev, newrev)
- :force_push_code_to_protected_branches
- # and we dont allow remove of protected branch
- elsif newrev =~ /0000000/
- :remove_protected_branches
- else
- :push_code_to_protected_branches
- end
- elsif project.repository && project.repository.tag_names.include?(tag_name(ref))
- # Prevent any changes to existing git tag unless user has permissions
- :admin_project
- else
- :push_code
- end
- unless user.can?(action, project)
+ unless change_allowed?(user, project, change)
# If user does not have access to make at least one change - cancel all push
return false
end
@@ -77,10 +59,33 @@ module Gitlab
true
end
+ def change_allowed?(user, project, change)
+ oldrev, newrev, ref = change.split(' ')
+
+ action = if project.protected_branch?(branch_name(ref))
+ # we dont allow force push to protected branch
+ if forced_push?(project, oldrev, newrev)
+ :force_push_code_to_protected_branches
+ # and we dont allow remove of protected branch
+ elsif newrev == Gitlab::Git::BLANK_SHA
+ :remove_protected_branches
+ else
+ :push_code_to_protected_branches
+ end
+ elsif project.repository && project.repository.tag_names.include?(tag_name(ref))
+ # Prevent any changes to existing git tag unless user has permissions
+ :admin_project
+ else
+ :push_code
+ end
+
+ user.can?(action, project)
+ end
+
def forced_push?(project, oldrev, newrev)
return false if project.empty_repo?
- if oldrev !~ /00000000/ && newrev !~ /00000000/
+ if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA
missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read
missed_refs.split("\n").size > 0
else
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
new file mode 100644
index 00000000000..9f0eb3be20f
--- /dev/null
+++ b/lib/gitlab/git_access_wiki.rb
@@ -0,0 +1,7 @@
+module Gitlab
+ class GitAccessWiki < GitAccess
+ def change_allowed?(user, project, change)
+ user.can?(:write_wiki, project)
+ end
+ end
+end
diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb
index fbfed205a0f..9e02ccc0f44 100644
--- a/lib/gitlab/git_logger.rb
+++ b/lib/gitlab/git_logger.rb
@@ -1,7 +1,7 @@
module Gitlab
class GitLogger < Gitlab::Logger
- def self.file_name
- 'githost.log'
+ def self.file_name_noext
+ 'githost'
end
def format_message(severity, timestamp, progname, msg)
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
index 13cb08948bb..39d17def930 100644
--- a/lib/gitlab/git_ref_validator.rb
+++ b/lib/gitlab/git_ref_validator.rb
@@ -5,7 +5,8 @@ module Gitlab
#
# Returns true for a valid reference name, false otherwise
def validate(ref_name)
- system *%W(git check-ref-format refs/#{ref_name})
+ Gitlab::Utils.system_silent(
+ %W(git check-ref-format refs/#{ref_name}))
end
end
end
diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb
new file mode 100644
index 00000000000..2122339d2db
--- /dev/null
+++ b/lib/gitlab/graphs/commits.rb
@@ -0,0 +1,49 @@
+module Gitlab
+ module Graphs
+ class Commits
+ attr_reader :commits, :start_date, :end_date, :duration,
+ :commits_per_week_days, :commits_per_time, :commits_per_month
+
+ def initialize(commits)
+ @commits = commits
+ @start_date = commits.last.committed_date.to_date
+ @end_date = commits.first.committed_date.to_date
+ @duration = (@end_date - @start_date).to_i
+
+ collect_data
+ end
+
+ def authors
+ @authors ||= @commits.map(&:author_email).uniq.size
+ end
+
+ def commit_per_day
+ @commit_per_day ||= (@commits.size.to_f / @duration).round(1)
+ end
+
+ def collect_data
+ @commits_per_week_days = {}
+ Date::DAYNAMES.each { |day| @commits_per_week_days[day] = 0 }
+
+ @commits_per_time = {}
+ (0..23).to_a.each { |hour| @commits_per_time[hour] = 0 }
+
+ @commits_per_month = {}
+ (1..31).to_a.each { |day| @commits_per_month[day] = 0 }
+
+ @commits.each do |commit|
+ hour = commit.committed_date.strftime('%k').to_i
+ day_of_month = commit.committed_date.strftime('%e').to_i
+ weekday = commit.committed_date.strftime('%A')
+
+ @commits_per_week_days[weekday] ||= 0
+ @commits_per_week_days[weekday] += 1
+ @commits_per_time[hour] ||= 0
+ @commits_per_time[hour] += 1
+ @commits_per_month[day_of_month] ||= 0
+ @commits_per_month[day_of_month] += 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb
index 89c8e0680c3..3517ecdf5cf 100644
--- a/lib/gitlab/inline_diff.rb
+++ b/lib/gitlab/inline_diff.rb
@@ -5,7 +5,7 @@ module Gitlab
START = "#!idiff-start!#"
FINISH = "#!idiff-finish!#"
- def processing diff_arr
+ def processing(diff_arr)
indexes = _indexes_of_changed_lines diff_arr
indexes.each do |index|
@@ -52,7 +52,7 @@ module Gitlab
diff_arr
end
- def _indexes_of_changed_lines diff_arr
+ def _indexes_of_changed_lines(diff_arr)
chain_of_first_symbols = ""
diff_arr.each_with_index do |line, i|
chain_of_first_symbols += line[0]
@@ -68,7 +68,7 @@ module Gitlab
indexes
end
- def replace_markers line
+ def replace_markers(line)
line.gsub!(START, "<span class='idiff'>")
line.gsub!(FINISH, "</span>")
line
diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb
index 0d34976736f..1bec6088292 100644
--- a/lib/gitlab/issues_labels.rb
+++ b/lib/gitlab/issues_labels.rb
@@ -15,7 +15,6 @@ module Gitlab
{ title: "support", color: yellow },
{ title: "discussion", color: blue },
{ title: "suggestion", color: blue },
- { title: "feature", color: green },
{ title: "enhancement", color: green }
]
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index c054b6f5865..eb2c4e48ff2 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -1,18 +1,21 @@
+# LDAP authorization model
+#
+# * Check if we are allowed access (not blocked)
+#
module Gitlab
module LDAP
class Access
- attr_reader :adapter
+ attr_reader :adapter, :provider, :user
- def self.open(&block)
- Gitlab::LDAP::Adapter.open do |adapter|
- block.call(self.new(adapter))
+ def self.open(user, &block)
+ Gitlab::LDAP::Adapter.open(user.provider) do |adapter|
+ block.call(self.new(user, adapter))
end
end
def self.allowed?(user)
- self.open do |access|
- if access.allowed?(user)
- # GitLab EE LDAP code goes here
+ self.open(user) do |access|
+ if access.allowed?
user.last_credential_check_at = Time.now
user.save
true
@@ -22,12 +25,15 @@ module Gitlab
end
end
- def initialize(adapter=nil)
+ def initialize(user, adapter=nil)
@adapter = adapter
+ @user = user
+ @provider = user.provider
end
- def allowed?(user)
+ def allowed?
if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
+ return true unless ldap_config.active_directory
!Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter)
else
false
@@ -35,6 +41,14 @@ module Gitlab
rescue
false
end
+
+ def adapter
+ @adapter ||= Gitlab::LDAP::Adapter.new(provider)
+ end
+
+ def ldap_config
+ Gitlab::LDAP::Config.new(provider)
+ end
end
end
end
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index 68ac1b22909..256cdb4c2f1 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -1,55 +1,28 @@
module Gitlab
module LDAP
class Adapter
- attr_reader :ldap
+ attr_reader :provider, :ldap
- def self.open(&block)
- Net::LDAP.open(adapter_options) do |ldap|
- block.call(self.new(ldap))
+ def self.open(provider, &block)
+ Net::LDAP.open(config(provider).adapter_options) do |ldap|
+ block.call(self.new(provider, ldap))
end
end
- def self.config
- Gitlab.config.ldap
+ def self.config(provider)
+ Gitlab::LDAP::Config.new(provider)
end
- def self.adapter_options
- encryption =
- case config['method'].to_s
- when 'ssl'
- :simple_tls
- when 'tls'
- :start_tls
- else
- nil
- end
-
- options = {
- host: config['host'],
- port: config['port'],
- encryption: encryption
- }
-
- auth_options = {
- auth: {
- method: :simple,
- username: config['bind_dn'],
- password: config['password']
- }
- }
-
- if config['password'] || config['bind_dn']
- options.merge!(auth_options)
- end
- options
+ def initialize(provider, ldap=nil)
+ @provider = provider
+ @ldap = ldap || Net::LDAP.new(config.adapter_options)
end
-
- def initialize(ldap=nil)
- @ldap = ldap || Net::LDAP.new(self.class.adapter_options)
+ def config
+ Gitlab::LDAP::Config.new(provider)
end
- def users(field, value)
+ def users(field, value, limit = nil)
if field.to_sym == :dn
options = {
base: value,
@@ -57,13 +30,13 @@ module Gitlab
}
else
options = {
- base: config['base'],
+ base: config.base,
filter: Net::LDAP::Filter.eq(field, value)
}
end
- if config['user_filter'].present?
- user_filter = Net::LDAP::Filter.construct(config['user_filter'])
+ if config.user_filter.present?
+ user_filter = Net::LDAP::Filter.construct(config.user_filter)
options[:filter] = if options[:filter]
Net::LDAP::Filter.join(options[:filter], user_filter)
@@ -72,12 +45,16 @@ module Gitlab
end
end
+ if limit.present?
+ options.merge!(size: limit)
+ end
+
entries = ldap_search(options).select do |entry|
entry.respond_to? config.uid
end
entries.map do |entry|
- Gitlab::LDAP::Person.new(entry)
+ Gitlab::LDAP::Person.new(entry, provider)
end
end
@@ -105,12 +82,6 @@ module Gitlab
results
end
end
-
- private
-
- def config
- @config ||= self.class.config
- end
end
end
end
diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb
new file mode 100644
index 00000000000..8af2c74e959
--- /dev/null
+++ b/lib/gitlab/ldap/authentication.rb
@@ -0,0 +1,71 @@
+# This calls helps to authenticate to LDAP by providing username and password
+#
+# Since multiple LDAP servers are supported, it will loop through all of them
+# until a valid bind is found
+#
+
+module Gitlab
+ module LDAP
+ class Authentication
+ def self.login(login, password)
+ return unless Gitlab::LDAP::Config.enabled?
+ return unless login.present? && password.present?
+
+ auth = nil
+ # loop through providers until valid bind
+ providers.find do |provider|
+ auth = new(provider)
+ auth.login(login, password) # true will exit the loop
+ end
+
+ # If (login, password) was invalid for all providers, the value of auth is now the last
+ # Gitlab::LDAP::Authentication instance we tried.
+ auth.user
+ end
+
+ def self.providers
+ Gitlab::LDAP::Config.providers
+ end
+
+ attr_accessor :provider, :ldap_user
+
+ def initialize(provider)
+ @provider = provider
+ end
+
+ def login(login, password)
+ @ldap_user = adapter.bind_as(
+ filter: user_filter(login),
+ size: 1,
+ password: password
+ )
+ end
+
+ def adapter
+ OmniAuth::LDAP::Adaptor.new(config.options.symbolize_keys)
+ end
+
+ def config
+ Gitlab::LDAP::Config.new(provider)
+ end
+
+ def user_filter(login)
+ filter = Net::LDAP::Filter.eq(config.uid, login)
+
+ # Apply LDAP user filter if present
+ if config.user_filter.present?
+ filter = Net::LDAP::Filter.join(
+ filter,
+ Net::LDAP::Filter.construct(config.user_filter)
+ )
+ end
+ filter
+ end
+
+ def user
+ return nil unless ldap_user
+ Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
new file mode 100644
index 00000000000..0cb24d0ccc1
--- /dev/null
+++ b/lib/gitlab/ldap/config.rb
@@ -0,0 +1,120 @@
+# Load a specific server configuration
+module Gitlab
+ module LDAP
+ class Config
+ attr_accessor :provider, :options
+
+ def self.enabled?
+ Gitlab.config.ldap.enabled
+ end
+
+ def self.servers
+ Gitlab.config.ldap.servers.values
+ end
+
+ def self.providers
+ servers.map {|server| server['provider_name'] }
+ end
+
+ def self.valid_provider?(provider)
+ providers.include?(provider)
+ end
+
+ def self.invalid_provider(provider)
+ raise "Unknown provider (#{provider}). Available providers: #{providers}"
+ end
+
+ def initialize(provider)
+ if self.class.valid_provider?(provider)
+ @provider = provider
+ elsif provider == 'ldap'
+ @provider = self.class.providers.first
+ else
+ self.class.invalid_provider(provider)
+ end
+ @options = config_for(@provider) # Use @provider, not provider
+ end
+
+ def enabled?
+ base_config.enabled
+ end
+
+ def adapter_options
+ {
+ host: options['host'],
+ port: options['port'],
+ encryption: encryption
+ }.tap do |options|
+ options.merge!(auth_options) if has_auth?
+ end
+ end
+
+ def base
+ options['base']
+ end
+
+ def uid
+ options['uid']
+ end
+
+ def sync_ssh_keys?
+ sync_ssh_keys.present?
+ end
+
+ # The LDAP attribute in which the ssh keys are stored
+ def sync_ssh_keys
+ options['sync_ssh_keys']
+ end
+
+ def user_filter
+ options['user_filter']
+ end
+
+ def group_base
+ options['group_base']
+ end
+
+ def admin_group
+ options['admin_group']
+ end
+
+ def active_directory
+ options['active_directory']
+ end
+
+ protected
+ def base_config
+ Gitlab.config.ldap
+ end
+
+ def config_for(provider)
+ base_config.servers.values.find { |server| server['provider_name'] == provider }
+ end
+
+ def encryption
+ case options['method'].to_s
+ when 'ssl'
+ :simple_tls
+ when 'tls'
+ :start_tls
+ else
+ nil
+ end
+ end
+
+ def auth_options
+ {
+ auth: {
+ method: :simple,
+ username: options['bind_dn'],
+ password: options['password']
+ }
+ }
+ end
+
+ def has_auth?
+ options['password'] || options['bind_dn']
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb
index 87c3d711db4..3e0b3e6cbf8 100644
--- a/lib/gitlab/ldap/person.rb
+++ b/lib/gitlab/ldap/person.rb
@@ -6,24 +6,24 @@ module Gitlab
# Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
- def self.find_by_uid(uid, adapter=nil)
- adapter ||= Gitlab::LDAP::Adapter.new
- adapter.user(config.uid, uid)
+ attr_accessor :entry, :provider
+
+ def self.find_by_uid(uid, adapter)
+ adapter.user(adapter.config.uid, uid)
end
- def self.find_by_dn(dn, adapter=nil)
- adapter ||= Gitlab::LDAP::Adapter.new
+ def self.find_by_dn(dn, adapter)
adapter.user('dn', dn)
end
- def self.disabled_via_active_directory?(dn, adapter=nil)
- adapter ||= Gitlab::LDAP::Adapter.new
+ def self.disabled_via_active_directory?(dn, adapter)
adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
end
- def initialize(entry)
+ def initialize(entry, provider)
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
@entry = entry
+ @provider = provider
end
def name
@@ -38,6 +38,10 @@ module Gitlab
uid
end
+ def email
+ entry.try(:mail)
+ end
+
def dn
entry.dn
end
@@ -48,12 +52,8 @@ module Gitlab
@entry
end
- def adapter
- @adapter ||= Gitlab::LDAP::Adapter.new
- end
-
def config
- @config ||= Gitlab.config.ldap
+ @config ||= Gitlab::LDAP::Config.new(provider)
end
end
end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 25b5a702f9a..3176e9790a7 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -10,77 +10,52 @@ module Gitlab
module LDAP
class User < Gitlab::OAuth::User
class << self
- def find_or_create(auth_hash)
- self.auth_hash = auth_hash
- find(auth_hash) || find_and_connect_by_email(auth_hash) || create(auth_hash)
- end
-
- def find_and_connect_by_email(auth_hash)
- self.auth_hash = auth_hash
- user = model.find_by(email: self.auth_hash.email)
-
- if user
- user.update_attributes(extern_uid: auth_hash.uid, provider: auth_hash.provider)
- Gitlab::AppLogger.info("(LDAP) Updating legacy LDAP user #{self.auth_hash.email} with extern_uid => #{auth_hash.uid}")
- return user
- end
- end
-
- def authenticate(login, password)
- # Check user against LDAP backend if user is not authenticated
- # Only check with valid login and password to prevent anonymous bind results
- return nil unless ldap_conf.enabled && login.present? && password.present?
-
- ldap_user = adapter.bind_as(
- filter: user_filter(login),
- size: 1,
- password: password
- )
-
- find_by_uid(ldap_user.dn) if ldap_user
- end
-
- def adapter
- @adapter ||= OmniAuth::LDAP::Adaptor.new(ldap_conf)
+ def find_by_uid_and_provider(uid, provider)
+ # LDAP distinguished name is case-insensitive
+ ::User.
+ where(provider: [provider, :ldap]).
+ where('lower(extern_uid) = ?', uid.downcase).last
end
+ end
- protected
-
- def find_by_uid_and_provider
- find_by_uid(auth_hash.uid)
- end
+ def initialize(auth_hash)
+ super
+ update_user_attributes
+ end
- def find_by_uid(uid)
- # LDAP distinguished name is case-insensitive
- model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last
- end
+ # instance methods
+ def gl_user
+ @gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user
+ end
- def provider
- 'ldap'
- end
+ def find_by_uid_and_provider
+ self.class.find_by_uid_and_provider(
+ auth_hash.uid.downcase, auth_hash.provider)
+ end
- def raise_error(message)
- raise OmniAuth::Error, "(LDAP) " + message
- end
+ def find_by_email
+ model.find_by(email: auth_hash.email)
+ end
- def ldap_conf
- Gitlab.config.ldap
- end
+ def update_user_attributes
+ gl_user.attributes = {
+ extern_uid: auth_hash.uid,
+ provider: auth_hash.provider,
+ email: auth_hash.email
+ }
+ end
- def user_filter(login)
- filter = Net::LDAP::Filter.eq(adapter.uid, login)
- # Apply LDAP user filter if present
- if ldap_conf['user_filter'].present?
- user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter'])
- filter = Net::LDAP::Filter.join(filter, user_filter)
- end
- filter
- end
+ def changed?
+ gl_user.changed?
end
def needs_blocking?
false
end
+
+ def allowed?
+ Gitlab::LDAP::Access.allowed?(gl_user)
+ end
end
end
end
diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb
index 64cf3303ea3..59b21149a9a 100644
--- a/lib/gitlab/logger.rb
+++ b/lib/gitlab/logger.rb
@@ -1,5 +1,9 @@
module Gitlab
class Logger < ::Logger
+ def self.file_name
+ file_name_noext + '.log'
+ end
+
def self.error(message)
build.error(message)
end
@@ -15,7 +19,7 @@ module Gitlab
tail_output.split("\n")
end
- def self.read_latest_for filename
+ def self.read_latest_for(filename)
path = Rails.root.join("log", filename)
tail_output, _ = Gitlab::Popen.popen(%W(tail -n 2000 #{path}))
tail_output.split("\n")
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 6017a4c86c1..068c342398b 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -1,3 +1,6 @@
+require 'html/pipeline'
+require 'html/pipeline/gitlab'
+
module Gitlab
# Custom parser for GitLab-flavored Markdown
#
@@ -30,6 +33,11 @@ module Gitlab
attr_reader :html_options
+ def gfm_with_tasks(text, project = @project, html_options = {})
+ text = gfm(text, project, html_options)
+ parse_tasks(text)
+ end
+
# Public: Parse the provided text with GitLab-Flavored Markdown
#
# text - the source text
@@ -62,6 +70,24 @@ module Gitlab
insert_piece($1)
end
+ # Used markdown pipelines in GitLab:
+ # GitlabEmojiFilter - performs emoji replacement.
+ #
+ # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
+ filters = [
+ HTML::Pipeline::Gitlab::GitlabEmojiFilter
+ ]
+
+ markdown_context = {
+ asset_root: Gitlab.config.gitlab.url,
+ asset_host: Gitlab::Application.config.asset_host
+ }
+
+ markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
+
+ result = markdown_pipeline.call(text, markdown_context)
+ text = result[:output].to_html(save_with: 0)
+
allowed_attributes = ActionView::Base.sanitized_allowed_attributes
allowed_tags = ActionView::Base.sanitized_allowed_tags
@@ -91,20 +117,22 @@ module Gitlab
# Returns parsed text
def parse(text, project = @project)
parse_references(text, project) if project
- parse_emoji(text)
text
end
+ NAME_STR = '[a-zA-Z][a-zA-Z0-9_\-\.]*'
+ PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})"
+
REFERENCE_PATTERN = %r{
(?<prefix>\W)? # Prefix
( # Reference
- @(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
+ @(?<user>#{NAME_STR}) # User name
|(?<issue>([A-Z\-]+-)\d+) # JIRA Issue ID
- |\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
- |!(?<merge_request>\d+) # MR ID
+ |#{PROJ_STR}?\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
+ |#{PROJ_STR}?!(?<merge_request>\d+) # MR ID
|\$(?<snippet>\d+) # Snippet ID
- |(?<commit>[\h]{6,40}) # Commit ID
+ |(#{PROJ_STR}@)?(?<commit>[\h]{6,40}) # Commit ID
|(?<skip>gfm-extraction-[\h]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit
)
(?<suffix>\W)? # Suffix
@@ -115,47 +143,46 @@ module Gitlab
def parse_references(text, project = @project)
# parse reference links
text.gsub!(REFERENCE_PATTERN) do |match|
- prefix = $~[:prefix]
- suffix = $~[:suffix]
type = TYPES.select{|t| !$~[t].nil?}.first
- if type
- identifier = $~[type]
-
- # Avoid HTML entities
- if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
- match
- elsif ref_link = reference_link(type, identifier, project)
- "#{prefix}#{ref_link}#{suffix}"
- else
- match
- end
- else
- match
+ actual_project = project
+ project_prefix = nil
+ project_path = $LAST_MATCH_INFO[:project]
+ if project_path
+ actual_project = ::Project.find_with_namespace(project_path)
+ project_prefix = project_path
end
+
+ parse_result($LAST_MATCH_INFO, type,
+ actual_project, project_prefix) || match
end
end
- EMOJI_PATTERN = %r{(:(\S+):)}.freeze
+ # Called from #parse_references. Attempts to build a gitlab reference
+ # link. Returns nil if +type+ is nil, if the match string is an HTML
+ # entity, if the reference is invalid, or if the matched text includes an
+ # invalid project path.
+ def parse_result(match_info, type, project, project_prefix)
+ prefix = match_info[:prefix]
+ suffix = match_info[:suffix]
- def parse_emoji(text)
- # parse emoji
- text.gsub!(EMOJI_PATTERN) do |match|
- if valid_emoji?($2)
- image_tag(url_to_image("emoji/#{$2}.png"), class: 'emoji', title: $1, alt: $1, size: "20x20")
- else
- match
- end
+ return nil if html_entity?(prefix, suffix) || type.nil?
+ return nil if project.nil? && !project_prefix.nil?
+
+ identifier = match_info[type]
+ ref_link = reference_link(type, identifier, project, project_prefix)
+
+ if ref_link
+ "#{prefix}#{ref_link}#{suffix}"
+ else
+ nil
end
end
- # Private: Checks if an emoji icon exists in the image asset directory
- #
- # emoji - Identifier of the emoji as a string (e.g., "+1", "heart")
- #
- # Returns boolean
- def valid_emoji?(emoji)
- Emoji.find_by_name(emoji)
+ # Return true if the +prefix+ and +suffix+ indicate that the matched string
+ # is an HTML entity like &amp;
+ def html_entity?(prefix, suffix)
+ prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
end
# Private: Dispatches to a dedicated processing method based on reference
@@ -164,55 +191,57 @@ module Gitlab
# identifier - Object identifier (Issue ID, SHA hash, etc.)
#
# Returns string rendered by the processing method
- def reference_link(type, identifier, project = @project)
- send("reference_#{type}", identifier, project)
+ def reference_link(type, identifier, project = @project, prefix_text = nil)
+ send("reference_#{type}", identifier, project, prefix_text)
end
- def reference_user(identifier, project = @project)
+ def reference_user(identifier, project = @project, _ = nil)
options = html_options.merge(
class: "gfm gfm-team_member #{html_options[:class]}"
)
if identifier == "all"
link_to("@all", project_url(project), options)
- elsif user = User.find_by(username: identifier)
+ elsif User.find_by(username: identifier)
link_to("@#{identifier}", user_url(identifier), options)
end
end
- def reference_issue(identifier, project = @project)
+ def reference_issue(identifier, project = @project, prefix_text = nil)
if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
if project.issue_exists? identifier
url = url_for_issue(identifier, project)
- title = title_for_issue(identifier)
+ title = title_for_issue(identifier, project)
options = html_options.merge(
title: "Issue: #{title}",
class: "gfm gfm-issue #{html_options[:class]}"
)
- link_to("##{identifier}", url, options)
+ link_to("#{prefix_text}##{identifier}", url, options)
end
else
config = Gitlab.config
external_issue_tracker = config.issues_tracker[project.issues_tracker]
if external_issue_tracker.present?
- reference_external_issue(identifier, external_issue_tracker, project)
+ reference_external_issue(identifier, external_issue_tracker, project,
+ prefix_text)
end
end
end
- def reference_merge_request(identifier, project = @project)
+ def reference_merge_request(identifier, project = @project,
+ prefix_text = nil)
if merge_request = project.merge_requests.find_by(iid: identifier)
options = html_options.merge(
title: "Merge Request: #{merge_request.title}",
class: "gfm gfm-merge_request #{html_options[:class]}"
)
url = project_merge_request_url(project, merge_request)
- link_to("!#{identifier}", url, options)
+ link_to("#{prefix_text}!#{identifier}", url, options)
end
end
- def reference_snippet(identifier, project = @project)
+ def reference_snippet(identifier, project = @project, _ = nil)
if snippet = project.snippets.find_by(id: identifier)
options = html_options.merge(
title: "Snippet: #{snippet.title}",
@@ -223,17 +252,23 @@ module Gitlab
end
end
- def reference_commit(identifier, project = @project)
+ def reference_commit(identifier, project = @project, prefix_text = nil)
if project.valid_repo? && commit = project.repository.commit(identifier)
options = html_options.merge(
title: commit.link_title,
class: "gfm gfm-commit #{html_options[:class]}"
)
- link_to(identifier, project_commit_url(project, commit), options)
+ prefix_text = "#{prefix_text}@" if prefix_text
+ link_to(
+ "#{prefix_text}#{identifier}",
+ project_commit_url(project, commit),
+ options
+ )
end
end
- def reference_external_issue(identifier, issue_tracker, project = @project)
+ def reference_external_issue(identifier, issue_tracker, project = @project,
+ prefix_text = nil)
url = url_for_issue(identifier, project)
title = issue_tracker['title']
@@ -241,7 +276,26 @@ module Gitlab
title: "Issue in #{title}",
class: "gfm gfm-issue #{html_options[:class]}"
)
- link_to("##{identifier}", url, options)
+ link_to("#{prefix_text}##{identifier}", url, options)
+ end
+
+ # Turn list items that start with "[ ]" into HTML checkbox inputs.
+ def parse_tasks(text)
+ li_tag = '<li class="task-list-item">'
+ unchecked_box = '<input type="checkbox" value="on" disabled />'
+ checked_box = unchecked_box.sub(/\/>$/, 'checked="checked" />')
+
+ # Regexp captures don't seem to work when +text+ is an
+ # ActiveSupport::SafeBuffer, hence the `String.new`
+ String.new(text).gsub(Taskable::TASK_PATTERN_HTML) do
+ checked = $LAST_MATCH_INFO[:checked].downcase == 'x'
+
+ if checked
+ "#{li_tag}#{checked_box}"
+ else
+ "#{li_tag}#{unchecked_box}"
+ end
+ end
end
end
end
diff --git a/lib/gitlab/markdown_helper.rb b/lib/gitlab/markdown_helper.rb
index abed12fe570..5e3cfc0585b 100644
--- a/lib/gitlab/markdown_helper.rb
+++ b/lib/gitlab/markdown_helper.rb
@@ -21,5 +21,9 @@ module Gitlab
def gitlab_markdown?(filename)
filename.downcase.end_with?(*%w(.mdown .md .markdown))
end
+
+ def previewable?(filename)
+ gitlab_markdown?(filename) || markup?(filename)
+ end
end
end
diff --git a/lib/gitlab/oauth/auth_hash.rb b/lib/gitlab/oauth/auth_hash.rb
index 0198f61f427..ce52beec78e 100644
--- a/lib/gitlab/oauth/auth_hash.rb
+++ b/lib/gitlab/oauth/auth_hash.rb
@@ -21,7 +21,7 @@ module Gitlab
end
def name
- (info.name || full_name).to_s.force_encoding('utf-8')
+ (info.try(:name) || full_name).to_s.force_encoding('utf-8')
end
def full_name
diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb
index b768eda185f..47f62153a50 100644
--- a/lib/gitlab/oauth/user.rb
+++ b/lib/gitlab/oauth/user.rb
@@ -6,55 +6,77 @@
module Gitlab
module OAuth
class User
- class << self
- attr_reader :auth_hash
+ attr_accessor :auth_hash, :gl_user
- def find(auth_hash)
- self.auth_hash = auth_hash
- find_by_uid_and_provider
- end
+ def initialize(auth_hash)
+ self.auth_hash = auth_hash
+ end
- def create(auth_hash)
- user = new(auth_hash)
- user.save_and_trigger_callbacks
- end
+ def persisted?
+ gl_user.try(:persisted?)
+ end
- def model
- ::User
- end
+ def new?
+ !persisted?
+ end
+
+ def valid?
+ gl_user.try(:valid?)
+ end
+
+ def save
+ unauthorized_to_create unless gl_user
- def auth_hash=(auth_hash)
- @auth_hash = AuthHash.new(auth_hash)
+ if needs_blocking?
+ gl_user.save!
+ gl_user.block
+ else
+ gl_user.save!
end
- protected
- def find_by_uid_and_provider
- model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last
+ log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
+ gl_user
+ rescue ActiveRecord::RecordInvalid => e
+ log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}"
+ return self, e.record.errors
+ end
+
+ def gl_user
+ @user ||= find_by_uid_and_provider
+
+ if signup_enabled?
+ @user ||= build_new_user
end
+
+ @user
end
- # Instance methods
- attr_accessor :auth_hash, :user
+ protected
- def initialize(auth_hash)
- self.auth_hash = auth_hash
- self.user = self.class.model.new(user_attributes)
- user.skip_confirmation!
+ def needs_blocking?
+ new? && block_after_signup?
+ end
+
+ def signup_enabled?
+ Gitlab.config.omniauth.allow_single_sign_on
+ end
+
+ def block_after_signup?
+ Gitlab.config.omniauth.block_auto_created_users
end
def auth_hash=(auth_hash)
@auth_hash = AuthHash.new(auth_hash)
end
- def save_and_trigger_callbacks
- user.save!
- log.info "(OAuth) Creating user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
- user.block if needs_blocking?
+ def find_by_uid_and_provider
+ model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last
+ end
- user
- rescue ActiveRecord::RecordInvalid => e
- log.info "(OAuth) Email #{e.record.errors[:email]}. Username #{e.record.errors[:username]}"
- return nil, e.record.errors
+ def build_new_user
+ model.new(user_attributes).tap do |user|
+ user.skip_confirmation!
+ end
end
def user_attributes
@@ -73,12 +95,12 @@ module Gitlab
Gitlab::AppLogger
end
- def raise_error(message)
- raise OmniAuth::Error, "(OAuth) " + message
+ def model
+ ::User
end
- def needs_blocking?
- Gitlab.config.omniauth['block_auto_created_users']
+ def raise_unauthorized_to_create
+ raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
end
end
end
diff --git a/lib/gitlab/production_logger.rb b/lib/gitlab/production_logger.rb
new file mode 100644
index 00000000000..89ce7144b1b
--- /dev/null
+++ b/lib/gitlab/production_logger.rb
@@ -0,0 +1,7 @@
+module Gitlab
+ class ProductionLogger < Gitlab::Logger
+ def self.file_name_noext
+ 'production'
+ end
+ end
+end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 1eda614807f..99165950aef 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -9,51 +9,63 @@ module Gitlab
@users, @issues, @merge_requests, @snippets, @commits = [], [], [], [], []
end
- def analyze string
- parse_references(string.dup)
+ def analyze(string, project)
+ parse_references(string.dup, project)
end
# Given a valid project, resolve the extracted identifiers of the requested type to
# model objects.
- def users_for project
- users.map do |identifier|
- project.users.where(username: identifier).first
+ def users_for(project)
+ users.map do |entry|
+ project.users.where(username: entry[:id]).first
end.reject(&:nil?)
end
- def issues_for project
- issues.map do |identifier|
- project.issues.where(iid: identifier).first
+ def issues_for(project = nil)
+ issues.map do |entry|
+ if should_lookup?(project, entry[:project])
+ entry[:project].issues.where(iid: entry[:id]).first
+ end
end.reject(&:nil?)
end
- def merge_requests_for project
- merge_requests.map do |identifier|
- project.merge_requests.where(iid: identifier).first
+ def merge_requests_for(project = nil)
+ merge_requests.map do |entry|
+ if should_lookup?(project, entry[:project])
+ entry[:project].merge_requests.where(iid: entry[:id]).first
+ end
end.reject(&:nil?)
end
- def snippets_for project
- snippets.map do |identifier|
- project.snippets.where(id: identifier).first
+ def snippets_for(project)
+ snippets.map do |entry|
+ project.snippets.where(id: entry[:id]).first
end.reject(&:nil?)
end
- def commits_for project
- repo = project.repository
- return [] if repo.nil?
-
- commits.map do |identifier|
- repo.commit(identifier)
+ def commits_for(project = nil)
+ commits.map do |entry|
+ repo = entry[:project].repository if entry[:project]
+ if should_lookup?(project, entry[:project])
+ repo.commit(entry[:id]) if repo
+ end
end.reject(&:nil?)
end
private
- def reference_link(type, identifier, project)
+ def reference_link(type, identifier, project, _)
# Append identifier to the appropriate collection.
- send("#{type}s") << identifier
+ send("#{type}s") << { project: project, id: identifier }
+ end
+
+ def should_lookup?(project, entry_project)
+ if entry_project.nil?
+ false
+ else
+ project.nil? || project.id == entry_project.id
+ end
end
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 4b8038843b0..c4d0d85b7f5 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -67,8 +67,7 @@ module Gitlab
def default_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \
- "It must start with letter, digit or '_', optionally preceeded by '.'. " \
- "It must not end in '.git'."
+ "Cannot start with '-' or end in '.git'" \
end
def default_regex
diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb
index f34d661c9fc..1de84309d15 100644
--- a/lib/gitlab/satellite/satellite.rb
+++ b/lib/gitlab/satellite/satellite.rb
@@ -11,7 +11,7 @@ module Gitlab
@project = project
end
- def log message
+ def log(message)
Gitlab::Satellite::Logger.error(message)
end
diff --git a/lib/gitlab/sidekiq_logger.rb b/lib/gitlab/sidekiq_logger.rb
new file mode 100644
index 00000000000..c1dab87a432
--- /dev/null
+++ b/lib/gitlab/sidekiq_logger.rb
@@ -0,0 +1,7 @@
+module Gitlab
+ class SidekiqLogger < Gitlab::Logger
+ def self.file_name_noext
+ 'sidekiq'
+ end
+ end
+end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index de7e0404086..877488d8471 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -19,7 +19,7 @@ module Gitlab
issue = Issue.find(id)
project_issue_url(id: issue.iid,
project_id: issue.project,
- host: Settings.gitlab['url'])
+ host: Gitlab.config.gitlab['url'])
end
end
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
new file mode 100644
index 00000000000..bd184c27187
--- /dev/null
+++ b/lib/gitlab/utils.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ module Utils
+ extend self
+
+ # Run system command without outputting to stdout.
+ #
+ # @param cmd [Array<String>]
+ # @return [Boolean]
+ def system_silent(cmd)
+ Popen::popen(cmd).last.zero?
+ end
+ end
+end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index bb225f1acd8..54d740908d5 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -10,6 +10,17 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
super options
end
+ # If project has issue number 39, apostrophe will be linked in
+ # regular text to the issue as Redcarpet will convert apostrophe to
+ # #39;
+ # We replace apostrophe with right single quote before Redcarpet
+ # does the processing and put the apostrophe back in postprocessing.
+ # This only influences regular text, code blocks are untouched.
+ def normal_text(text)
+ return text unless text.present?
+ text.gsub("'", "&rsquo;")
+ end
+
def block_code(code, language)
# New lines are placed to fix an rendering issue
# with code wrapped inside <h1> tag for next case:
@@ -44,9 +55,14 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end
def postprocess(full_document)
+ full_document.gsub!("&rsquo;", "'")
unless @template.instance_variable_get("@project_wiki") || @project.nil?
full_document = h.create_relative_links(full_document)
end
- h.gfm(full_document)
+ if @options[:parse_tasks]
+ h.gfm_with_tasks(full_document)
+ else
+ h.gfm(full_document)
+ end
end
end
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 49a68c62293..c8b769ace8e 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -1,5 +1,5 @@
## GitLab
-## Maintainer: @randx
+## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller
##
## Lines starting with two hashes (##) are comments with information.
## Lines starting with one hash (#) are configuration parameters that can be uncommented.
@@ -15,7 +15,7 @@
## - installing an old version of Nginx with the chunkin module [2] compiled in, or
## - using a newer version of Nginx.
##
-## At the time of writing we do not know if either of these theoretical solutions works.
+## At the time of writing we do not know if either of these theoretical solutions works.
## As a workaround users can use Git over SSH to push large files.
##
## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
@@ -26,6 +26,7 @@
## configuration ##
###################################
##
+## See installation.md#using-https for additional HTTPS configuration details.
upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
@@ -33,7 +34,8 @@ upstream gitlab {
## Normal HTTP host
server {
- listen *:80 default_server;
+ listen 0.0.0.0:80 default_server;
+ listen [::]:80 default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public;
@@ -42,6 +44,8 @@ server {
## Or if you want to accept large git objects over http
client_max_body_size 20m;
+ ## See app/controllers/application_controller.rb for headers set
+
## Individual nginx logs for this GitLab vhost
access_log /var/log/nginx/gitlab_access.log;
error_log /var/log/nginx/gitlab_error.log;
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 19409e41f40..4e53d5e8b50 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -1,5 +1,5 @@
## GitLab
-## Contributors: randx, yin8086, sashkab, orkoden, axilleas
+## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller
##
## Modified from nginx http version
## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/
@@ -19,16 +19,15 @@
## - installing an old version of Nginx with the chunkin module [2] compiled in, or
## - using a newer version of Nginx.
##
-## At the time of writing we do not know if either of these theoretical solutions works.
+## At the time of writing we do not know if either of these theoretical solutions works.
## As a workaround users can use Git over SSH to push large files.
##
## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
## [1] https://github.com/agentzh/chunkin-nginx-module#status
## [2] https://github.com/agentzh/chunkin-nginx-module
##
-##
###################################
-## SSL configuration ##
+## configuration ##
###################################
##
## See installation.md#using-https for additional HTTPS configuration details.
@@ -37,22 +36,24 @@ upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
}
-## Normal HTTP host
+## Redirects all HTTP traffic to the HTTPS host
server {
- listen *:80 default_server;
+ listen 0.0.0.0:80;
+ listen [::]:80 default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
-
- ## Redirects all traffic to the HTTPS host
- root /nowhere; ## root doesn't have to be a valid path since we are redirecting
- rewrite ^ https://$server_name$request_uri? permanent;
+ return 301 https://$server_name$request_uri;
+ access_log /var/log/nginx/gitlab_access.log;
+ error_log /var/log/nginx/gitlab_error.log;
}
+
## HTTPS host
server {
- listen 443 ssl;
+ listen 0.0.0.0:443 ssl;
+ listen [::]:443 ssl default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
- server_tokens off;
+ server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public;
## Increase this if you want to upload large attachments
@@ -60,21 +61,19 @@ server {
client_max_body_size 20m;
## Strong SSL Security
- ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+ ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
ssl on;
ssl_certificate /etc/nginx/ssl/gitlab.crt;
ssl_certificate_key /etc/nginx/ssl/gitlab.key;
- ssl_ciphers 'AES256+EECDH:AES256+EDH';
-
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- ssl_session_cache builtin:1000 shared:SSL:10m;
-
- ssl_prefer_server_ciphers on;
+ # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
+ ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_prefer_server_ciphers on;
+ ssl_session_cache shared:SSL:10m;
+ ssl_session_timeout 5m;
- add_header Strict-Transport-Security max-age=63072000;
- add_header X-Frame-Options SAMEORIGIN;
- add_header X-Content-Type-Options nosniff;
+ ## See app/controllers/application_controller.rb for headers set
## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL.
## Replace with your ssl_trusted_certificate. For more info see:
@@ -85,11 +84,10 @@ server {
# ssl_stapling_verify on;
# ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt;
# resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired
- # resolver_timeout 10s;
+ # resolver_timeout 5s;
## [Optional] Generate a stronger DHE parameter:
- ## cd /etc/ssl/certs
- ## sudo openssl dhparam -out dhparam.pem 4096
+ ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
##
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 9ec368254ac..7ff23a7600a 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -574,20 +574,16 @@ namespace :gitlab do
Gitlab::Shell.new.version
end
- def required_gitlab_shell_version
- File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip
- end
-
def gitlab_shell_major_version
- required_gitlab_shell_version.split(".")[0].to_i
+ Gitlab::Shell.version_required.split('.')[0].to_i
end
def gitlab_shell_minor_version
- required_gitlab_shell_version.split(".")[1].to_i
+ Gitlab::Shell.version_required.split('.')[1].to_i
end
def gitlab_shell_patch_version
- required_gitlab_shell_version.split(".")[2].to_i
+ Gitlab::Shell.version_required.split('.')[2].to_i
end
def has_gitlab_shell3?
@@ -664,7 +660,7 @@ namespace :gitlab do
warn_user_is_not_gitlab
start_checking "LDAP"
- if ldap_config.enabled
+ if Gitlab::LDAP::Config.enabled?
print_users(args.limit)
else
puts 'LDAP is disabled in config/gitlab.yml'
@@ -675,39 +671,19 @@ namespace :gitlab do
def print_users(limit)
puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)"
- ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry|
- puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}"
- end
- end
- def attributes
- [ldap_config.uid]
- end
+ servers = Gitlab::LDAP::Config.providers
- def filter
- uid_filter = Net::LDAP::Filter.present?(ldap_config.uid)
- if user_filter
- Net::LDAP::Filter.join(uid_filter, user_filter)
- else
- uid_filter
- end
- end
-
- def user_filter
- if ldap_config['user_filter'] && ldap_config.user_filter.present?
- Net::LDAP::Filter.construct(ldap_config.user_filter)
- else
- nil
+ servers.each do |server|
+ puts "Server: #{server}"
+ Gitlab::LDAP::Adapter.open(server) do |adapter|
+ users = adapter.users(adapter.config.uid, '*', 100)
+ users.each do |user|
+ puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}"
+ end
+ end
end
end
-
- def ldap
- @ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection
- end
-
- def ldap_config
- @ldap_config ||= Gitlab.config.ldap
- end
end
# Helper methods
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index cbfa736c84c..3c693546c09 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -15,23 +15,17 @@ namespace :gitlab do
git_base_path = Gitlab.config.gitlab_shell.repos_path
repos_to_import = Dir.glob(git_base_path + '/**/*.git')
- namespaces = Namespace.pluck(:path)
-
repos_to_import.each do |repo_path|
# strip repo base path
repo_path[0..git_base_path.length] = ''
path = repo_path.sub(/\.git$/, '')
- name = File.basename path
- group_name = File.dirname path
+ group_name, name = File.split(path)
group_name = nil if group_name == '.'
- # Skip if group or user
- next if namespaces.include?(name)
-
puts "Processing #{repo_path}".yellow
- if path =~ /.wiki\Z/
+ if path =~ /\.wiki\Z/
puts " * Skipping wiki repo"
next
end
@@ -50,9 +44,9 @@ namespace :gitlab do
# find group namespace
if group_name
- group = Group.find_by(path: group_name)
+ group = Namespace.find_by(path: group_name)
# create group namespace
- if !group
+ unless group
group = Group.new(:name => group_name)
group.path = group_name
group.owner = user
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index a8f26a7c029..202e55c89ad 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -4,20 +4,20 @@ namespace :gitlab do
task :install, [:tag, :repo] => :environment do |t, args|
warn_user_is_not_gitlab
- default_version = File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip
+ default_version = Gitlab::Shell.version_required
args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git")
- user = Settings.gitlab.user
- home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home
- gitlab_url = Settings.gitlab.url
+ user = Gitlab.config.gitlab.user
+ home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
+ gitlab_url = Gitlab.config.gitlab.url
# gitlab-shell requires a / at the end of the url
- gitlab_url += "/" unless gitlab_url.match(/\/$/)
+ gitlab_url += '/' unless gitlab_url.end_with?('/')
repos_path = Gitlab.config.gitlab_shell.repos_path
target_dir = Gitlab.config.gitlab_shell.path
# Clone if needed
unless File.directory?(target_dir)
- sh "git clone '#{args.repo}' '#{target_dir}'"
+ sh(*%W(git clone #{args.repo} #{target_dir}))
end
# Make sure we're on the right tag
@@ -76,7 +76,7 @@ namespace :gitlab do
desc "GITLAB | Build missing projects"
task build_missing_projects: :environment do
Project.find_each(batch_size: 1000) do |project|
- path_to_repo = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
+ path_to_repo = project.repository.path_to_repo
if File.exists?(path_to_repo)
print '-'
else
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
index f19da1bb437..583f4a876da 100644
--- a/lib/tasks/test.rake
+++ b/lib/tasks/test.rake
@@ -4,3 +4,10 @@ desc "GITLAB | Run all tests"
task :test do
Rake::Task["gitlab:test"].invoke
end
+
+unless Rails.env.production?
+ require 'coveralls/rake/task'
+ Coveralls::RakeTask.new
+ desc "GITLAB | Run all tests on CI with simplecov"
+ task :test_ci => [:spinach, :spec, 'coveralls:push']
+end
diff --git a/spec/factories.rb b/spec/factories.rb
index a960571206c..15899d8c3c4 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -24,6 +24,11 @@ FactoryGirl.define do
admin true
end
+ trait :ldap do
+ provider 'ldapmain'
+ extern_uid 'my-ldap-id'
+ end
+
factory :admin, traits: [:admin]
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 5324654c48c..23314b3b1a4 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -47,16 +47,6 @@ FactoryGirl.define do
end
end
- # Generates a test repository from the repository stored under `spec/seed_project.tar.gz`.
- # Once you run `rake gitlab:setup`, you can see what the repository looks like under `tmp/repositories/gitlabhq`.
- # In order to modify files in the repository, you must untar the seed, modify and remake the tar.
- # Before recompressing, do not forget to `git checkout master`.
- # After recompressing, you need to run `RAILS_ENV=test bundle exec rake gitlab:setup` to regenerate the seeds under tmp.
- #
- # If you want to modify the repository only for an specific type of tests, e.g., markdown tests,
- # consider using a feature branch to reduce the chances of collision with other tests.
- # Create a new commit, and use the same commit message that you will use for the change in the main repo.
- # Changing the commig message and SHA of branch `master` may break tests.
factory :project, parent: :empty_project do
path { 'gitlabhq' }
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 524c4d5fa21..d291621935b 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Projects", feature: true do
+describe "Projects", feature: true, js: true do
before { login_as :user }
describe "DELETE /projects/:id" do
@@ -10,8 +10,23 @@ describe "Projects", feature: true do
visit edit_project_path(@project)
end
- it "should be correct path" do
- expect { click_link "Remove project" }.to change {Project.count}.by(-1)
+ it "should remove project" do
+ expect { remove_project }.to change {Project.count}.by(-1)
end
+
+ it 'should delete the project from disk' do
+ expect(GitlabShellWorker).to(
+ receive(:perform_async).with(:remove_repository,
+ /#{@project.path_with_namespace}/)
+ ).twice
+
+ remove_project
+ end
+ end
+
+ def remove_project
+ click_link "Remove project"
+ fill_in 'confirm_name_input', with: @project.path
+ click_button 'Confirm'
end
end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index 7b831c48611..a1206989d39 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -11,7 +11,7 @@ describe 'Users', feature: true do
fill_in "user_name", with: "Name Surname"
fill_in "user_username", with: "Great"
fill_in "user_email", with: "name@mail.com"
- fill_in "user_password", with: "password1234"
+ fill_in "user_password_sign_up", with: "password1234"
fill_in "user_password_confirmation", with: "password1234"
expect { click_button "Sign up" }.to change {User.count}.by(1)
end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
new file mode 100644
index 00000000000..c645cbc964c
--- /dev/null
+++ b/spec/finders/snippets_finder_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+
+describe SnippetsFinder do
+ let(:user) { create :user }
+ let(:user1) { create :user }
+ let(:group) { create :group }
+
+ let(:project1) { create(:empty_project, :public, group: group) }
+ let(:project2) { create(:empty_project, :private, group: group) }
+
+
+ context ':all filter' do
+ before do
+ @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE)
+ @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL)
+ @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC)
+ end
+
+ it "returns all private and internal snippets" do
+ snippets = SnippetsFinder.new.execute(user, filter: :all)
+ snippets.should include(@snippet2, @snippet3)
+ snippets.should_not include(@snippet1)
+ end
+
+ it "returns all public snippets" do
+ snippets = SnippetsFinder.new.execute(nil, filter: :all)
+ snippets.should include(@snippet3)
+ snippets.should_not include(@snippet1, @snippet2)
+ end
+ end
+
+ context ':by_user filter' do
+ before do
+ @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user)
+ @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user)
+ @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user)
+ end
+
+ it "returns all public and internal snippets" do
+ snippets = SnippetsFinder.new.execute(user1, filter: :by_user, user: user)
+ snippets.should include(@snippet2, @snippet3)
+ snippets.should_not include(@snippet1)
+ end
+
+ it "returns internal snippets" do
+ snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_internal")
+ snippets.should include(@snippet2)
+ snippets.should_not include(@snippet1, @snippet3)
+ end
+
+ it "returns private snippets" do
+ snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_private")
+ snippets.should include(@snippet1)
+ snippets.should_not include(@snippet2, @snippet3)
+ end
+
+ it "returns public snippets" do
+ snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_public")
+ snippets.should include(@snippet3)
+ snippets.should_not include(@snippet1, @snippet2)
+ end
+
+ it "returns all snippets" do
+ snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user)
+ snippets.should include(@snippet1, @snippet2, @snippet3)
+ end
+
+ it "returns only public snippets if unauthenticated user" do
+ snippets = SnippetsFinder.new.execute(nil, filter: :by_user, user: user)
+ snippets.should include(@snippet3)
+ snippets.should_not include(@snippet2, @snippet1)
+ end
+
+ end
+
+ context 'by_project filter' do
+ before do
+ @snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1)
+ @snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1)
+ @snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1)
+ end
+
+ it "returns public snippets for unauthorized user" do
+ snippets = SnippetsFinder.new.execute(nil, filter: :by_project, project: project1)
+ snippets.should include(@snippet3)
+ snippets.should_not include(@snippet1, @snippet2)
+ end
+
+ it "returns public and internal snippets for none project members" do
+ snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
+ snippets.should include(@snippet2, @snippet3)
+ snippets.should_not include(@snippet1)
+ end
+
+ it "returns all snippets for project members" do
+ project1.team << [user, :developer]
+ snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
+ snippets.should include(@snippet1, @snippet2, @snippet3)
+ end
+ end
+end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
new file mode 100644
index 00000000000..4de54d291f2
--- /dev/null
+++ b/spec/helpers/events_helper_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe EventsHelper do
+ include ApplicationHelper
+ include GitlabMarkdownHelper
+
+ it 'should display one line of plain text without alteration' do
+ input = 'A short, plain note'
+ expect(event_note(input)).to match(input)
+ expect(event_note(input)).not_to match(/\.\.\.\z/)
+ end
+
+ it 'should display inline code' do
+ input = 'A note with `inline code`'
+ expected = 'A note with <code>inline code</code>'
+
+ expect(event_note(input)).to match(expected)
+ end
+
+ it 'should truncate a note with multiple paragraphs' do
+ input = "Paragraph 1\n\nParagraph 2"
+ expected = 'Paragraph 1...'
+
+ expect(event_note(input)).to match(expected)
+ end
+
+ it 'should display the first line of a code block' do
+ input = "```\nCode block\nwith two lines\n```"
+ expected = '<pre><code class="">Code block...</code></pre>'
+
+ expect(event_note(input)).to match(expected)
+ end
+
+ it 'should truncate a single long line of text' do
+ text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
+ input = "#{text}#{text}#{text}#{text}" # 200 chars
+ expected = "#{text}#{text}".sub(/.{3}/, '...')
+
+ expect(event_note(input)).to match(expected)
+ end
+
+ it 'should preserve a link href when link text is truncated' do
+ text = 'The quick brown fox jumped over the lazy dog' # 44 chars
+ input = "#{text}#{text}#{text} " # 133 chars
+ link_url = 'http://example.com/foo/bar/baz' # 30 chars
+ input << link_url
+ expected_link_text = 'http://example...</a>'
+
+ expect(event_note(input)).to match(link_url)
+ expect(event_note(input)).to match(expected_link_text)
+ end
+end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 46337f8bafd..3c636b747d1 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -14,6 +14,10 @@ describe GitlabMarkdownHelper do
let(:snippet) { create(:project_snippet, project: project) }
let(:member) { project.project_members.where(user_id: user).first }
+ def url_helper(image_name)
+ File.join(root_url, 'assets', image_name)
+ end
+
before do
# Helper expects a @project instance variable
@project = project
@@ -38,8 +42,8 @@ describe GitlabMarkdownHelper do
it "should not touch HTML entities" do
@project.issues.stub(:where).with(id: '39').and_return([issue])
- actual = expected = "We&#39;ll accept good pull requests."
- gfm(actual).should == expected
+ actual = 'We&#39;ll accept good pull requests.'
+ gfm(actual).should == "We'll accept good pull requests."
end
it "should forward HTML options to links" do
@@ -56,7 +60,7 @@ describe GitlabMarkdownHelper do
end
it "should link using a short id" do
- actual = "Backported from #{commit.short_id(6)}"
+ actual = "Backported from #{commit.short_id}"
gfm(actual).should match(expected)
end
@@ -177,6 +181,76 @@ describe GitlabMarkdownHelper do
end
end
+ # Shared examples for referencing an object in a different project
+ #
+ # Expects the following attributes to be available in the example group:
+ #
+ # - object - The object itself
+ # - reference - The object reference string (e.g., #1234, $1234, !1234)
+ # - other_project - The project that owns the target object
+ #
+ # Currently limited to Snippets, Issues and MergeRequests
+ shared_examples 'cross-project referenced object' do
+ let(:project_path) { @other_project.path_with_namespace }
+ let(:full_reference) { "#{project_path}#{reference}" }
+ let(:actual) { "Reference to #{full_reference}" }
+ let(:expected) do
+ if object.is_a?(Commit)
+ project_commit_path(@other_project, object)
+ else
+ polymorphic_path([@other_project, object])
+ end
+ end
+
+ it 'should link using a valid id' do
+ gfm(actual).should match(
+ /#{expected}.*#{Regexp.escape(full_reference)}/
+ )
+ end
+
+ it 'should link with adjacent text' do
+ # Wrap the reference in parenthesis
+ gfm(actual.gsub(full_reference, "(#{full_reference})")).should(
+ match(expected)
+ )
+
+ # Append some text to the end of the reference
+ gfm(actual.gsub(full_reference, "#{full_reference}, right?")).should(
+ match(expected)
+ )
+ end
+
+ it 'should keep whitespace intact' do
+ actual = "Referenced #{full_reference} already."
+ expected = /Referenced <a.+>[^\s]+<\/a> already/
+ gfm(actual).should match(expected)
+ end
+
+ it 'should not link with an invalid id' do
+ # Modify the reference string so it's still parsed, but is invalid
+ if object.is_a?(Commit)
+ reference.gsub!(/^(.).+$/, '\1' + '12345abcd')
+ else
+ reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
+ end
+ gfm(actual).should == actual
+ end
+
+ it 'should include a title attribute' do
+ if object.is_a?(Commit)
+ title = object.link_title
+ else
+ title = "#{object.class.to_s.titlecase}: #{object.title}"
+ end
+ gfm(actual).should match(/title="#{title}"/)
+ end
+
+ it 'should include standard gfm classes' do
+ css = object.class.to_s.underscore
+ gfm(actual).should match(/class="\s?gfm gfm-#{css}\s?"/)
+ end
+ end
+
describe "referencing an issue" do
let(:object) { issue }
let(:reference) { "##{issue.iid}" }
@@ -184,6 +258,38 @@ describe GitlabMarkdownHelper do
include_examples 'referenced object'
end
+ context 'cross-repo references' do
+ before(:all) do
+ @other_project = create(:project, :public)
+ @commit2 = @other_project.repository.commit
+ @issue2 = create(:issue, project: @other_project)
+ @merge_request2 = create(:merge_request,
+ source_project: @other_project,
+ target_project: @other_project)
+ end
+
+ describe 'referencing an issue in another project' do
+ let(:object) { @issue2 }
+ let(:reference) { "##{@issue2.iid}" }
+
+ include_examples 'cross-project referenced object'
+ end
+
+ describe 'referencing an merge request in another project' do
+ let(:object) { @merge_request2 }
+ let(:reference) { "!#{@merge_request2.iid}" }
+
+ include_examples 'cross-project referenced object'
+ end
+
+ describe 'referencing a commit in another project' do
+ let(:object) { @commit2 }
+ let(:reference) { "@#{@commit2.id}" }
+
+ include_examples 'cross-project referenced object'
+ end
+ end
+
describe "referencing a Jira issue" do
let(:actual) { "Reference to JIRA-#{issue.iid}" }
let(:expected) { "http://jira.example/browse/JIRA-#{issue.iid}" }
@@ -330,7 +436,8 @@ describe GitlabMarkdownHelper do
end
it "keeps whitespace intact" do
- gfm("This deserves a :+1: big time.").should match(/deserves a <img.+\/> big time/)
+ gfm('This deserves a :+1: big time.').
+ should match(/deserves a <img.+> big time/)
end
it "ignores invalid emoji" do
@@ -423,6 +530,24 @@ describe GitlabMarkdownHelper do
markdown(actual).should match(%r{<li>light by <a.+>@#{member.user.username}</a></li>})
end
+ it "should not link the apostrophe to issue 39" do
+ project.team << [user, :master]
+ project.issues.stub(:where).with(iid: '39').and_return([issue])
+
+ actual = "Yes, it is @#{member.user.username}'s task."
+ expected = /Yes, it is <a.+>@#{member.user.username}<\/a>'s task/
+ markdown(actual).should match(expected)
+ end
+
+ it "should not link the apostrophe to issue 39 in code blocks" do
+ project.team << [user, :master]
+ project.issues.stub(:where).with(iid: '39').and_return([issue])
+
+ actual = "Yes, `it is @#{member.user.username}'s task.`"
+ expected = /Yes, <code>it is @gfm\'s task.<\/code>/
+ markdown(actual).should match(expected)
+ end
+
it "should handle references in <em>" do
actual = "Apply _!#{merge_request.iid}_ ASAP"
@@ -448,7 +573,8 @@ describe GitlabMarkdownHelper do
end
it "should leave inline code untouched" do
- markdown("\nDon't use `$#{snippet.id}` here.\n").should == "<p>Don&#39;t use <code>$#{snippet.id}</code> here.</p>\n"
+ markdown("\nDon't use `$#{snippet.id}` here.\n").should ==
+ "<p>Don't use <code>$#{snippet.id}</code> here.</p>\n"
end
it "should leave ref-like autolinks untouched" do
@@ -468,9 +594,23 @@ describe GitlabMarkdownHelper do
end
it "should generate absolute urls for emoji" do
- markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}")
+ markdown(':smile:').should(
+ include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/smile.png))
+ )
end
+ it "should generate absolute urls for emoji if relative url is present" do
+ Gitlab.config.gitlab.stub(:url).and_return('http://localhost/gitlab/root')
+ markdown(":smile:").should include("src=\"http://localhost/gitlab/root/assets/emoji/smile.png")
+ end
+
+ it "should generate absolute urls for emoji if asset_host is present" do
+ Gitlab::Application.config.stub(:asset_host).and_return("https://cdn.example.com")
+ ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com")
+ markdown(":smile:").should include("src=\"https://cdn.example.com/assets/emoji/smile.png")
+ end
+
+
it "should handle relative urls for a file in master" do
actual = "[GitLab API doc](doc/api/README.md)\n"
expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
@@ -508,7 +648,7 @@ describe GitlabMarkdownHelper do
end
end
- describe "markdwon for empty repository" do
+ describe 'markdown for empty repository' do
before do
@project = empty_project
@repository = empty_project.repository
@@ -544,4 +684,103 @@ describe GitlabMarkdownHelper do
helper.render_wiki_content(@wiki)
end
end
+
+ describe '#gfm_with_tasks' do
+ before(:all) do
+ @source_text_asterisk = <<EOT.gsub(/^\s{8}/, '')
+ * [ ] valid unchecked task
+ * [x] valid lowercase checked task
+ * [X] valid uppercase checked task
+ * [ ] valid unchecked nested task
+ * [x] valid checked nested task
+
+ [ ] not an unchecked task - no list item
+ [x] not a checked task - no list item
+
+ * [ ] not an unchecked task - too many spaces
+ * [x ] not a checked task - too many spaces
+ * [] not an unchecked task - no spaces
+ * Not a task [ ] - not at beginning
+EOT
+
+ @source_text_dash = <<EOT.gsub(/^\s{8}/, '')
+ - [ ] valid unchecked task
+ - [x] valid lowercase checked task
+ - [X] valid uppercase checked task
+ - [ ] valid unchecked nested task
+ - [x] valid checked nested task
+EOT
+ end
+
+ it 'should render checkboxes at beginning of asterisk list items' do
+ rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
+
+ expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid lowercase checked task/
+ )
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid uppercase checked task/
+ )
+ end
+
+ it 'should render checkboxes at beginning of dash list items' do
+ rendered_text = markdown(@source_text_dash, parse_tasks: true)
+
+ expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid lowercase checked task/
+ )
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid uppercase checked task/
+ )
+ end
+
+ it 'should not be confused by whitespace before bullets' do
+ rendered_text_asterisk = markdown(@source_text_asterisk,
+ parse_tasks: true)
+ rendered_text_dash = markdown(@source_text_dash, parse_tasks: true)
+
+ expect(rendered_text_asterisk).to match(
+ /<input.*checkbox.*valid unchecked nested task/
+ )
+ expect(rendered_text_asterisk).to match(
+ /<input.*checkbox.*valid checked nested task/
+ )
+ expect(rendered_text_dash).to match(
+ /<input.*checkbox.*valid unchecked nested task/
+ )
+ expect(rendered_text_dash).to match(
+ /<input.*checkbox.*valid checked nested task/
+ )
+ end
+
+ it 'should not render checkboxes outside of list items' do
+ rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
+
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not an unchecked task - no list item/
+ )
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not a checked task - no list item/
+ )
+ end
+
+ it 'should not render checkboxes with invalid formatting' do
+ rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
+
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not an unchecked task - too many spaces/
+ )
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not a checked task - too many spaces/
+ )
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not an unchecked task - no spaces/
+ )
+ expect(rendered_text).not_to match(
+ /Not a task.*<input.*checkbox.*not at beginning/
+ )
+ end
+ end
end
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index 3ecabbaf045..31ecdacf28e 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -8,7 +8,7 @@ describe NotificationsHelper do
before { notification.stub(disabled?: true) }
it "has a red icon" do
- notification_icon(notification).should match('class="icon-volume-off ns-mute"')
+ notification_icon(notification).should match('class="fa fa-volume-off ns-mute"')
end
end
@@ -16,7 +16,7 @@ describe NotificationsHelper do
before { notification.stub(participating?: true) }
it "has a blue icon" do
- notification_icon(notification).should match('class="icon-volume-down ns-part"')
+ notification_icon(notification).should match('class="fa fa-volume-down ns-part"')
end
end
@@ -24,12 +24,12 @@ describe NotificationsHelper do
before { notification.stub(watch?: true) }
it "has a green icon" do
- notification_icon(notification).should match('class="icon-volume-up ns-watch"')
+ notification_icon(notification).should match('class="fa fa-volume-up ns-watch"')
end
end
it "has a blue icon" do
- notification_icon(notification).should match('class="icon-circle-blank ns-default"')
+ notification_icon(notification).should match('class="fa fa-circle-o ns-default"')
end
end
end
diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb
new file mode 100644
index 00000000000..8bf6ee2ed50
--- /dev/null
+++ b/spec/lib/disable_email_interceptor_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe DisableEmailInterceptor do
+ before do
+ ActionMailer::Base.register_interceptor(DisableEmailInterceptor)
+ end
+
+ it 'should not send emails' do
+ Gitlab.config.gitlab.stub(:email_enabled).and_return(false)
+ expect {
+ deliver_mail
+ }.not_to change(ActionMailer::Base.deliveries, :count)
+ end
+
+ after do
+ # Removing interceptor from the list because unregister_interceptor is
+ # implemented in later version of mail gem
+ # See: https://github.com/mikel/mail/pull/705
+ Mail.class_variable_set(:@@delivery_interceptors, [])
+ end
+
+ def deliver_mail
+ key = create :personal_key
+ Notify.new_ssh_key_email(key.id)
+ end
+end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 551fb3fb5f6..95fc7e16a11 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -10,13 +10,21 @@ describe Gitlab::Auth do
password: password,
password_confirmation: password)
end
- let(:username) { 'john' }
+ let(:username) { 'John' } # username isn't lowercase, test this
let(:password) { 'my-secret' }
it "should find user by valid login/password" do
expect( gl_auth.find(username, password) ).to eql user
end
+ it 'should find user by valid email/password with case-insensitive email' do
+ expect(gl_auth.find(user.email.upcase, password)).to eql user
+ end
+
+ it 'should find user by valid username/password with case-insensitive username' do
+ expect(gl_auth.find(username.upcase, password)).to eql user
+ end
+
it "should not find user with invalid password" do
password = 'wrong'
expect( gl_auth.find(username, password) ).to_not eql user
@@ -28,17 +36,16 @@ describe Gitlab::Auth do
end
context "with ldap enabled" do
- before { Gitlab.config.ldap['enabled'] = true }
- after { Gitlab.config.ldap['enabled'] = false }
+ before { Gitlab::LDAP::Config.stub(enabled?: true) }
it "tries to autheticate with db before ldap" do
- expect(Gitlab::LDAP::User).not_to receive(:authenticate)
+ expect(Gitlab::LDAP::Authentication).not_to receive(:login)
gl_auth.find(username, password)
end
it "uses ldap as fallback to for authentication" do
- expect(Gitlab::LDAP::User).to receive(:authenticate)
+ expect(Gitlab::LDAP::Authentication).to receive(:login)
gl_auth.find('ldap_user', 'password')
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 570b03827a8..fe0a6bbdabb 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -55,12 +55,13 @@ describe Gitlab::GitAccess do
def changes
{
- push_new_branch: '000000000 570e7b2ab refs/heads/wow',
+ push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
push_master: '6f6d7e7ed 570e7b2ab refs/heads/master',
push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature',
- push_remove_protected_branch: '570e7b2ab 000000000 refs/heads/feature',
+ push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\
+ 'refs/heads/feature',
push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0',
- push_new_tag: '000000000 570e7b2ab refs/tags/v7.8.9',
+ push_new_tag: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/tags/v7.8.9",
push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature']
}
end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
new file mode 100644
index 00000000000..ed5785b31e6
--- /dev/null
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::GitAccessWiki do
+ let(:access) { Gitlab::GitAccessWiki.new }
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ describe 'push_allowed?' do
+ before do
+ create(:protected_branch, name: 'master', project: project)
+ project.team << [user, :developer]
+ end
+
+ subject { access.push_allowed?(user, project, changes) }
+
+ it { should be_true }
+ end
+
+ def changes
+ ['6f6d7e7ed 570e7b2ab refs/heads/master']
+ end
+end
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index 2307a03f656..f4d5a927396 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
describe Gitlab::LDAP::Access do
- let(:access) { Gitlab::LDAP::Access.new }
- let(:user) { create(:user) }
+ let(:access) { Gitlab::LDAP::Access.new user }
+ let(:user) { create(:user, :ldap) }
describe :allowed? do
- subject { access.allowed?(user) }
+ subject { access.allowed? }
context 'when the user cannot be found' do
before { Gitlab::LDAP::Person.stub(find_by_dn: nil) }
@@ -27,6 +27,15 @@ describe Gitlab::LDAP::Access do
it { should be_true }
end
+
+ context 'without ActiveDirectory enabled' do
+ before do
+ Gitlab::LDAP::Config.stub(enabled?: true)
+ Gitlab::LDAP::Config.any_instance.stub(active_directory: false)
+ end
+
+ it { should be_true }
+ end
end
end
-end
+end \ No newline at end of file
diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb
index c3f07334431..19347e47378 100644
--- a/spec/lib/gitlab/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/ldap/adapter_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::LDAP::Adapter do
- let(:adapter) { Gitlab::LDAP::Adapter.new }
+ let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' }
describe :dn_matches_filter? do
let(:ldap) { double(:ldap) }
diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb
new file mode 100644
index 00000000000..0eb7c443b8b
--- /dev/null
+++ b/spec/lib/gitlab/ldap/authentication_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Gitlab::LDAP::Authentication do
+ let(:klass) { Gitlab::LDAP::Authentication }
+ let(:user) { create(:user, :ldap, extern_uid: dn) }
+ let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
+ let(:login) { 'john' }
+ let(:password) { 'password' }
+
+ describe :login do
+ let(:adapter) { double :adapter }
+ before do
+ Gitlab::LDAP::Config.stub(enabled?: true)
+ end
+
+ it "finds the user if authentication is successful" do
+ user
+ # try only to fake the LDAP call
+ klass.any_instance.stub(adapter: double(:adapter,
+ bind_as: double(:ldap_user, dn: dn)
+ ))
+ expect(klass.login(login, password)).to be_true
+ end
+
+ it "is false if the user does not exist" do
+ # try only to fake the LDAP call
+ klass.any_instance.stub(adapter: double(:adapter,
+ bind_as: double(:ldap_user, dn: dn)
+ ))
+ expect(klass.login(login, password)).to be_false
+ end
+
+ it "is false if authentication fails" do
+ user
+ # try only to fake the LDAP call
+ klass.any_instance.stub(adapter: double(:adapter, bind_as: nil))
+ expect(klass.login(login, password)).to be_false
+ end
+
+ it "fails if ldap is disabled" do
+ Gitlab::LDAP::Config.stub(enabled?: false)
+ expect(klass.login(login, password)).to be_false
+ end
+
+ it "fails if no login is supplied" do
+ expect(klass.login('', password)).to be_false
+ end
+
+ it "fails if no password is supplied" do
+ expect(klass.login(login, '')).to be_false
+ end
+ end
+end \ No newline at end of file
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
new file mode 100644
index 00000000000..3ebb8aae243
--- /dev/null
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Gitlab::LDAP::Config do
+ let(:config) { Gitlab::LDAP::Config.new provider }
+ let(:provider) { 'ldapmain' }
+
+ describe :initalize do
+ it 'requires a provider' do
+ expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError
+ end
+
+ it "works" do
+ expect(config).to be_a described_class
+ end
+
+ it "raises an error if a unknow provider is used" do
+ expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error
+ end
+
+ context "if 'ldap' is the provider name" do
+ let(:provider) { 'ldap' }
+
+ context "and 'ldap' is not in defined as a provider" do
+ before { Gitlab::LDAP::Config.stub(providers: %w{ldapmain}) }
+
+ it "uses the first provider" do
+ # Fetch the provider_name attribute from 'options' so that we know
+ # that the 'options' Hash is not empty/nil.
+ expect(config.options['provider_name']).to eq('ldapmain')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index d232cb20759..726c9764e3d 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -1,54 +1,36 @@
require 'spec_helper'
describe Gitlab::LDAP::User do
- let(:gl_user) { Gitlab::LDAP::User }
+ let(:gl_user) { Gitlab::LDAP::User.new(auth_hash) }
let(:info) do
- double(
+ {
name: 'John',
email: 'john@example.com',
nickname: 'john'
- )
+ }
+ end
+ let(:auth_hash) do
+ double(uid: 'my-uid', provider: 'ldapmain', info: double(info))
end
- before { Gitlab.config.stub(omniauth: {}) }
describe :find_or_create do
- let(:auth) do
- double(info: info, provider: 'ldap', uid: 'my-uid')
- end
-
it "finds the user if already existing" do
- existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldap')
+ existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain')
- expect{ gl_user.find_or_create(auth) }.to_not change{ User.count }
+ expect{ gl_user.save }.to_not change{ User.count }
end
it "connects to existing non-ldap user if the email matches" do
existing_user = create(:user, email: 'john@example.com')
- expect{ gl_user.find_or_create(auth) }.to_not change{ User.count }
+ expect{ gl_user.save }.to_not change{ User.count }
existing_user.reload
expect(existing_user.extern_uid).to eql 'my-uid'
- expect(existing_user.provider).to eql 'ldap'
+ expect(existing_user.provider).to eql 'ldapmain'
end
it "creates a new user if not found" do
- expect{ gl_user.find_or_create(auth) }.to change{ User.count }.by(1)
- end
- end
-
- describe "authenticate" do
- let(:login) { 'john' }
- let(:password) { 'my-secret' }
-
- before {
- Gitlab.config.ldap['enabled'] = true
- Gitlab.config.ldap['user_filter'] = 'employeeType=developer'
- }
- after { Gitlab.config.ldap['enabled'] = false }
-
- it "send an authentication request to ldap" do
- expect( Gitlab::LDAP::User.adapter ).to receive(:bind_as)
- Gitlab::LDAP::User.authenticate(login, password)
+ expect{ gl_user.save }.to change{ User.count }.by(1)
end
end
end
diff --git a/spec/lib/gitlab/oauth/auth_hash_spec.rb b/spec/lib/gitlab/oauth/auth_hash_spec.rb
new file mode 100644
index 00000000000..5eb77b492b2
--- /dev/null
+++ b/spec/lib/gitlab/oauth/auth_hash_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Gitlab::OAuth::AuthHash do
+ let(:auth_hash) do
+ Gitlab::OAuth::AuthHash.new(double({
+ provider: 'twitter',
+ uid: uid,
+ info: double(info_hash)
+ }))
+ end
+ let(:uid) { 'my-uid' }
+ let(:email) { 'my-email@example.com' }
+ let(:nickname) { 'my-nickname' }
+ let(:info_hash) {
+ {
+ email: email,
+ nickname: nickname,
+ name: 'John',
+ first_name: "John",
+ last_name: "Who"
+ }
+ }
+
+ context "defaults" do
+ it { expect(auth_hash.provider).to eql 'twitter' }
+ it { expect(auth_hash.uid).to eql uid }
+ it { expect(auth_hash.email).to eql email }
+ it { expect(auth_hash.username).to eql nickname }
+ it { expect(auth_hash.name).to eql "John" }
+ it { expect(auth_hash.password).to_not be_empty }
+ end
+
+ context "email not provided" do
+ before { info_hash.delete(:email) }
+ it "generates a temp email" do
+ expect( auth_hash.email).to start_with('temp-email-for-oauth')
+ end
+ end
+
+ context "username not provided" do
+ before { info_hash.delete(:nickname) }
+
+ it "takes the first part of the email as username" do
+ expect( auth_hash.username ).to eql "my-email"
+ end
+ end
+
+ context "name not provided" do
+ before { info_hash.delete(:name) }
+
+ it "concats first and lastname as the name" do
+ expect( auth_hash.name ).to eql "John Who"
+ end
+ end
+end \ No newline at end of file
diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb
index c241e198609..8a83a1b2588 100644
--- a/spec/lib/gitlab/oauth/user_spec.rb
+++ b/spec/lib/gitlab/oauth/user_spec.rb
@@ -1,83 +1,108 @@
require 'spec_helper'
describe Gitlab::OAuth::User do
- let(:gl_auth) { Gitlab::OAuth::User }
- let(:info) do
- double(
+ let(:oauth_user) { Gitlab::OAuth::User.new(auth_hash) }
+ let(:gl_user) { oauth_user.gl_user }
+ let(:uid) { 'my-uid' }
+ let(:provider) { 'my-provider' }
+ let(:auth_hash) { double(uid: uid, provider: provider, info: double(info_hash)) }
+ let(:info_hash) do
+ {
nickname: 'john',
name: 'John',
email: 'john@mail.com'
- )
+ }
end
- before do
- Gitlab.config.stub(omniauth: {})
- end
-
- describe :find do
+ describe :persisted? do
let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') }
it "finds an existing user based on uid and provider (facebook)" do
auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider')
- assert gl_auth.find(auth)
+ expect( oauth_user.persisted? ).to be_true
end
- it "finds an existing user based on nested uid and provider" do
- auth = double(info: info, uid: 'my-uid', provider: 'my-provider')
- assert gl_auth.find(auth)
+ it "returns false if use is not found in database" do
+ auth_hash.stub(uid: 'non-existing')
+ expect( oauth_user.persisted? ).to be_false
end
end
- describe :create do
- it "should create user from LDAP" do
- auth = double(info: info, uid: 'my-uid', provider: 'ldap')
- user = gl_auth.create(auth)
+ describe :save do
+ let(:provider) { 'twitter' }
- user.should be_valid
- user.extern_uid.should == auth.uid
- user.provider.should == 'ldap'
- end
+ describe 'signup' do
+ context "with allow_single_sign_on enabled" do
+ before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
- it "should create user from Omniauth" do
- auth = double(info: info, uid: 'my-uid', provider: 'twitter')
- user = gl_auth.create(auth)
+ it "creates a user from Omniauth" do
+ oauth_user.save
- user.should be_valid
- user.extern_uid.should == auth.uid
- user.provider.should == 'twitter'
+ expect(gl_user).to be_valid
+ expect(gl_user.extern_uid).to eql uid
+ expect(gl_user.provider).to eql 'twitter'
+ end
+ end
+
+ context "with allow_single_sign_on disabled (Default)" do
+ it "throws an error" do
+ expect{ oauth_user.save }.to raise_error StandardError
+ end
+ end
end
- it "should apply defaults to user" do
- auth = double(info: info, uid: 'my-uid', provider: 'ldap')
- user = gl_auth.create(auth)
+ describe 'blocking' do
+ let(:provider) { 'twitter' }
+ before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
- user.should be_valid
- user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
- user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
- end
+ context 'signup' do
+ context 'dont block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: false }
- it "Set a temp email address if not provided (like twitter does)" do
- info = double(
- uid: 'my-uid',
- nickname: 'john',
- name: 'John'
- )
- auth = double(info: info, uid: 'my-uid', provider: 'my-provider')
+ it do
+ oauth_user.save
+ gl_user.should be_valid
+ gl_user.should_not be_blocked
+ end
+ end
- user = gl_auth.create(auth)
- expect(user.email).to_not be_empty
- end
+ context 'block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: true }
+
+ it do
+ oauth_user.save
+ gl_user.should be_valid
+ gl_user.should be_blocked
+ end
+ end
+ end
+
+ context 'sign-in' do
+ before do
+ oauth_user.save
+ oauth_user.gl_user.activate
+ end
+
+ context 'dont block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: false }
+
+ it do
+ oauth_user.save
+ gl_user.should be_valid
+ gl_user.should_not be_blocked
+ end
+ end
- it 'generates a username if non provided (google)' do
- info = double(
- uid: 'my-uid',
- name: 'John',
- email: 'john@example.com'
- )
- auth = double(info: info, uid: 'my-uid', provider: 'my-provider')
+ context 'block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: true }
- user = gl_auth.create(auth)
- expect(user.username).to eql 'john'
+ it do
+ oauth_user.save
+ gl_user.should be_valid
+ gl_user.should_not be_blocked
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 99fed27c796..23867df39dd 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -2,45 +2,48 @@ require 'spec_helper'
describe Gitlab::ReferenceExtractor do
it 'extracts username references' do
- subject.analyze "this contains a @user reference"
- subject.users.should == ["user"]
+ subject.analyze('this contains a @user reference', nil)
+ subject.users.should == [{ project: nil, id: 'user' }]
end
it 'extracts issue references' do
- subject.analyze "this one talks about issue #1234"
- subject.issues.should == ["1234"]
+ subject.analyze('this one talks about issue #1234', nil)
+ subject.issues.should == [{ project: nil, id: '1234' }]
end
it 'extracts JIRA issue references' do
- Gitlab.config.gitlab.stub(:issues_tracker).and_return("jira")
- subject.analyze "this one talks about issue JIRA-1234"
- subject.issues.should == ["JIRA-1234"]
+ Gitlab.config.gitlab.stub(:issues_tracker).and_return('jira')
+ subject.analyze('this one talks about issue JIRA-1234', nil)
+ subject.issues.should == [{ project: nil, id: 'JIRA-1234' }]
end
it 'extracts merge request references' do
- subject.analyze "and here's !43, a merge request"
- subject.merge_requests.should == ["43"]
+ subject.analyze("and here's !43, a merge request", nil)
+ subject.merge_requests.should == [{ project: nil, id: '43' }]
end
it 'extracts snippet ids' do
- subject.analyze "snippets like $12 get extracted as well"
- subject.snippets.should == ["12"]
+ subject.analyze('snippets like $12 get extracted as well', nil)
+ subject.snippets.should == [{ project: nil, id: '12' }]
end
it 'extracts commit shas' do
- subject.analyze "commit shas 98cf0ae3 are pulled out as Strings"
- subject.commits.should == ["98cf0ae3"]
+ subject.analyze('commit shas 98cf0ae3 are pulled out as Strings', nil)
+ subject.commits.should == [{ project: nil, id: '98cf0ae3' }]
end
it 'extracts multiple references and preserves their order' do
- subject.analyze "@me and @you both care about this"
- subject.users.should == ["me", "you"]
+ subject.analyze('@me and @you both care about this', nil)
+ subject.users.should == [
+ { project: nil, id: 'me' },
+ { project: nil, id: 'you' }
+ ]
end
it 'leaves the original note unmodified' do
- text = "issue #123 is just the worst, @user"
- subject.analyze text
- text.should == "issue #123 is just the worst, @user"
+ text = 'issue #123 is just the worst, @user'
+ subject.analyze(text, nil)
+ text.should == 'issue #123 is just the worst, @user'
end
it 'handles all possible kinds of references' do
@@ -59,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_foo, :reporter]
project.team << [@u_bar, :guest]
- subject.analyze "@foo, @baduser, @bar, and @offteam"
+ subject.analyze('@foo, @baduser, @bar, and @offteam', project)
subject.users_for(project).should == [@u_foo, @u_bar]
end
@@ -67,7 +70,7 @@ describe Gitlab::ReferenceExtractor do
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
- subject.analyze "##{@i0.iid}, ##{@i1.iid}, and #999."
+ subject.analyze("##{@i0.iid}, ##{@i1.iid}, and #999.", project)
subject.issues_for(project).should == [@i0, @i1]
end
@@ -75,7 +78,7 @@ describe Gitlab::ReferenceExtractor do
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa')
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
- subject.analyze "!999, !#{@m1.iid}, and !#{@m0.iid}."
+ subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.", project)
subject.merge_requests_for(project).should == [@m1, @m0]
end
@@ -84,14 +87,15 @@ describe Gitlab::ReferenceExtractor do
@s1 = create(:project_snippet, project: project)
@s2 = create(:project_snippet)
- subject.analyze "$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}"
+ subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}", project)
subject.snippets_for(project).should == [@s0, @s1]
end
it 'accesses valid commits' do
- commit = project.repository.commit("master")
+ commit = project.repository.commit('master')
- subject.analyze "this references commits #{commit.sha[0..6]} and 012345"
+ subject.analyze("this references commits #{commit.sha[0..6]} and 012345",
+ project)
extracted = subject.commits_for(project)
extracted.should have(1).item
extracted[0].sha.should == commit.sha
diff --git a/spec/lib/gitlab/satellite/action_spec.rb b/spec/lib/gitlab/satellite/action_spec.rb
index 2af1e9e32f9..3eb1258d67e 100644
--- a/spec/lib/gitlab/satellite/action_spec.rb
+++ b/spec/lib/gitlab/satellite/action_spec.rb
@@ -97,7 +97,7 @@ describe 'Gitlab::Satellite::Action' do
end
class FileLockStatusChecker < File
- def flocked? &block
+ def flocked?(&block)
status = flock LOCK_EX|LOCK_NB
case status
when false
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 799dce442cc..e06e8826e5c 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -44,7 +44,9 @@ describe Notify do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) }
- subject { Notify.new_user_email(new_user.id, new_user.password, 'kETLwRaayvigPq_x3SNM') }
+ token = 'kETLwRaayvigPq_x3SNM'
+
+ subject { Notify.new_user_email(new_user.id, new_user.password, token) }
it_behaves_like 'an email sent from GitLab'
@@ -65,7 +67,10 @@ describe Notify do
end
it 'includes a link for user to set password' do
- should have_body_text 'http://localhost/users/password/edit?reset_password_token=kETLwRaayvigPq_x3SNM'
+ params = "reset_password_token=#{token}"
+ should have_body_text(
+ %r{http://localhost(:\d+)?/users/password/edit\?#{params}}
+ )
end
it 'includes a link to the site' do
diff --git a/spec/models/assembla_service_spec.rb b/spec/models/assembla_service_spec.rb
index 0ef475b87c3..4300090eb13 100644
--- a/spec/models/assembla_service_spec.rb
+++ b/spec/models/assembla_service_spec.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
require 'spec_helper'
diff --git a/spec/models/buildbox_service_spec.rb b/spec/models/buildbox_service_spec.rb
new file mode 100644
index 00000000000..1d9ca51be16
--- /dev/null
+++ b/spec/models/buildbox_service_spec.rb
@@ -0,0 +1,73 @@
+# == Schema Information
+#
+# Table name: 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
+#
+
+require 'spec_helper'
+
+describe BuildboxService do
+ describe 'Associations' do
+ it { should belong_to :project }
+ it { should have_one :service_hook }
+ end
+
+ describe 'commits methods' do
+ before do
+ @project = Project.new
+ @project.stub(
+ default_branch: 'default-brancho'
+ )
+
+ @service = BuildboxService.new
+ @service.stub(
+ project: @project,
+ service_hook: true,
+ project_url: 'https://buildbox.io/account-name/example-project',
+ token: 'secret-sauce-webhook-token:secret-sauce-status-token'
+ )
+ end
+
+ describe :webhook_url do
+ it 'returns the webhook url' do
+ @service.webhook_url.should ==
+ 'https://webhook.buildbox.io/deliver/secret-sauce-webhook-token'
+ end
+ end
+
+ describe :commit_status_path do
+ it 'returns the correct status page' do
+ @service.commit_status_path('2ab7834c').should ==
+ 'https://gitlab.buildbox.io/status/secret-sauce-status-token.json?commit=2ab7834c'
+ end
+ end
+
+ describe :build_page do
+ it 'returns the correct build page' do
+ @service.build_page('2ab7834c').should ==
+ 'https://buildbox.io/account-name/example-project/builds?commit=2ab7834c'
+ end
+ end
+
+ describe :builds_page do
+ it 'returns the correct path to the builds page' do
+ @service.builds_path.should ==
+ 'https://buildbox.io/account-name/example-project/builds?branch=default-brancho'
+ end
+ end
+
+ describe :status_img_path do
+ it 'returns the correct path to the status image' do
+ @service.status_img_path.should == 'https://badge.buildbox.io/secret-sauce-status-token.svg'
+ end
+ end
+ end
+end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 1673184cbe4..a6ec44da4be 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -53,17 +53,29 @@ eos
describe '#closes_issues' do
let(:issue) { create :issue, project: project }
+ let(:other_project) { create :project, :public }
+ let(:other_issue) { create :issue, project: other_project }
it 'detects issues that this commit is marked as closing' do
- commit.stub(issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/, safe_message: "Fixes ##{issue.iid}")
+ stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX',
+ /Fixes #\d+/)
+ commit.stub(safe_message: "Fixes ##{issue.iid}")
commit.closes_issues(project).should == [issue]
end
+
+ it 'does not detect issues from other projects' do
+ ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
+ stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX',
+ /^([Cc]loses|[Ff]ixes)/)
+ commit.stub(safe_message: "Fixes #{ext_ref}")
+ commit.closes_issues(project).should be_empty
+ end
end
it_behaves_like 'a mentionable' do
let(:subject) { commit }
let(:mauthor) { create :user, email: commit.author_email }
- let(:backref_text) { "commit #{subject.sha[0..5]}" }
+ let(:backref_text) { "commit #{subject.id}" }
let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } }
# Include the subject in the repository stub.
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
new file mode 100644
index 00000000000..ca6f11b2a4d
--- /dev/null
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Issue, "Mentionable" do
+ describe :mentioned_users do
+ let!(:user) { create(:user, username: 'stranger') }
+ let!(:user2) { create(:user, username: 'john') }
+ let!(:issue) { create(:issue, description: '@stranger mentioned') }
+
+ subject { issue.mentioned_users }
+
+ it { should include(user) }
+ it { should_not include(user2) }
+ end
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 1fdd959da9d..204ae9da704 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -36,7 +36,7 @@ describe Event do
@user = project.owner
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/master",
user_id: @user.id,
@@ -60,7 +60,6 @@ describe Event do
it { @event.push?.should be_true }
it { @event.proper?.should be_true }
- it { @event.new_branch?.should be_true }
it { @event.tag?.should be_false }
it { @event.branch_name.should == "master" }
it { @event.author.should == @user }
diff --git a/spec/models/flowdock_service_spec.rb b/spec/models/flowdock_service_spec.rb
index 710b8cba502..5540f0fa988 100644
--- a/spec/models/flowdock_service_spec.rb
+++ b/spec/models/flowdock_service_spec.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
require 'spec_helper'
diff --git a/spec/models/gemnasium_service_spec.rb b/spec/models/gemnasium_service_spec.rb
index 5de645cdf33..60ffa6f8b05 100644
--- a/spec/models/gemnasium_service_spec.rb
+++ b/spec/models/gemnasium_service_spec.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
require 'spec_helper'
diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb
index e4cd8bb90c3..83277058fbb 100644
--- a/spec/models/gitlab_ci_service_spec.rb
+++ b/spec/models/gitlab_ci_service_spec.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
require 'spec_helper'
@@ -34,11 +34,11 @@ describe GitlabCiService do
end
describe :commit_status_path do
- it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c/status.json?token=verySecret"}
+ it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c/status.json?token=verySecret"}
end
describe :build_page do
- it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c"}
+ it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c"}
end
end
end
diff --git a/spec/models/group_member_spec.rb b/spec/models/group_member_spec.rb
index 6acbc9bb4ae..38657de6793 100644
--- a/spec/models/group_member_spec.rb
+++ b/spec/models/group_member_spec.rb
@@ -1,14 +1,16 @@
# == Schema Information
#
-# Table name: group_members
+# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
-# group_id :integer not null
+# source_id :integer not null
+# source_type :string(255) not null
# user_id :integer not null
+# notification_level :integer not null
+# type :string(255)
# created_at :datetime
# updated_at :datetime
-# notification_level :integer default(3), not null
#
require 'spec_helper'
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 8b299cea67c..6b6efe832e5 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -60,4 +60,8 @@ describe Issue do
let(:backref_text) { "issue ##{subject.iid}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
end
+
+ it_behaves_like 'a Taskable' do
+ let(:subject) { create :issue }
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c40f75290ed..7b0d261d72f 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -119,4 +119,8 @@ describe MergeRequest do
let(:backref_text) { "merge request !#{subject.iid}" }
let(:set_mentionable_text) { ->(txt){ subject.title = txt } }
end
+
+ it_behaves_like 'a Taskable' do
+ let(:subject) { create :merge_request, :simple }
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index da51100e0d7..6ab7162c15c 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -228,7 +228,7 @@ describe Note do
it { should be_valid }
its(:noteable) { should == issue }
- its(:note) { should == "_mentioned in commit #{commit.sha[0..5]}_" }
+ its(:note) { should == "_mentioned in commit #{commit.sha}_" }
end
context 'merge request from an issue' do
@@ -249,6 +249,12 @@ describe Note do
its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" }
end
+ context 'commit contained in a merge request' do
+ subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author, project) }
+
+ it { should be_nil }
+ end
+
context 'commit from issue' do
subject { Note.create_cross_reference_note(commit, issue, author, project) }
@@ -258,14 +264,25 @@ describe Note do
its(:commit_id) { should == commit.id }
its(:note) { should == "_mentioned in issue ##{issue.iid}_" }
end
+
+ context 'commit from commit' do
+ let(:parent_commit) { commit.parents.first }
+ subject { Note.create_cross_reference_note(commit, parent_commit, author, project) }
+
+ it { should be_valid }
+ its(:noteable_type) { should == "Commit" }
+ its(:noteable_id) { should be_nil }
+ its(:commit_id) { should == commit.id }
+ its(:note) { should == "_mentioned in commit #{parent_commit.id}_" }
+ end
end
describe '#cross_reference_exists?' do
let(:project) { create :project }
let(:author) { create :user }
let(:issue) { create :issue }
- let(:commit0) { double 'commit0', gfm_reference: 'commit 123456' }
- let(:commit1) { double 'commit1', gfm_reference: 'commit 654321' }
+ let(:commit0) { project.repository.commit }
+ let(:commit1) { project.repository.commit('HEAD~2') }
before do
Note.create_cross_reference_note(issue, commit0, author, project)
@@ -278,6 +295,15 @@ describe Note do
it 'detects if a mentionable has not already been mentioned' do
Note.cross_reference_exists?(issue, commit1).should be_false
end
+
+ context 'commit on commit' do
+ before do
+ Note.create_cross_reference_note(commit0, commit1, author, project)
+ end
+
+ it { Note.cross_reference_exists?(commit0, commit1).should be_true }
+ it { Note.cross_reference_exists?(commit1, commit0).should be_false }
+ end
end
describe '#system?' do
diff --git a/spec/models/project_member_spec.rb b/spec/models/project_member_spec.rb
index 0178d065e57..9b5f89b6d7d 100644
--- a/spec/models/project_member_spec.rb
+++ b/spec/models/project_member_spec.rb
@@ -1,14 +1,16 @@
# == Schema Information
#
-# Table name: project_members
+# Table name: members
#
# id :integer not null, primary key
+# access_level :integer not null
+# source_id :integer not null
+# source_type :string(255) not null
# user_id :integer not null
-# project_id :integer not null
+# notification_level :integer not null
+# type :string(255)
# created_at :datetime
# updated_at :datetime
-# project_access :integer default(0), not null
-# notification_level :integer default(3), not null
#
require 'spec_helper'
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index e4df934460b..a6e1d9eef50 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -2,17 +2,17 @@
#
# Table name: snippets
#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# expires_at :datetime
-# private :boolean default(TRUE), not null
-# type :string(255)
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# file_name :string(255)
+# expires_at :datetime
+# type :string(255)
+# visibility_level :integer default(0), not null
#
require 'spec_helper'
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 524cab2b925..70a15cac1a8 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -47,6 +47,7 @@ describe Project do
it { should have_many(:protected_branches).dependent(:destroy) }
it { should have_one(:forked_project_link).dependent(:destroy) }
it { should have_one(:slack_service).dependent(:destroy) }
+ it { should have_one(:pushover_service).dependent(:destroy) }
end
describe "Mass assignment" do
@@ -144,63 +145,6 @@ describe Project do
end
end
- describe 'comment merge requests with commits' do
- before do
- @user = create(:user)
- group = create(:group)
- group.add_owner(@user)
-
- @project = create(:project, namespace: group)
- @fork_project = Projects::ForkService.new(@project, @user).execute
- @merge_request = create(:merge_request, source_project: @project,
- source_branch: 'master',
- target_branch: 'feature',
- target_project: @project)
- @fork_merge_request = create(:merge_request, source_project: @fork_project,
- source_branch: 'master',
- target_branch: 'feature',
- target_project: @project)
-
- @commits = @merge_request.commits
- end
-
- context 'push to origin repo source branch' do
- before do
- @project.comment_mr_with_commits('master', @commits, @user)
- end
-
- it { @merge_request.notes.should_not be_empty }
- it { @fork_merge_request.notes.should be_empty }
- end
-
- context 'push to origin repo target branch' do
- before do
- @project.comment_mr_with_commits('feature', @commits, @user)
- end
-
- it { @merge_request.notes.should be_empty }
- it { @fork_merge_request.notes.should be_empty }
- end
-
- context 'push to fork repo source branch' do
- before do
- @fork_project.comment_mr_with_commits('master', @commits, @user)
- end
-
- it { @merge_request.notes.should be_empty }
- it { @fork_merge_request.notes.should_not be_empty }
- end
-
- context 'push to fork repo target branch' do
- before do
- @fork_project.comment_mr_with_commits('feature', @commits, @user)
- end
-
- it { @merge_request.notes.should be_empty }
- it { @fork_merge_request.notes.should be_empty }
- end
- end
-
describe :find_with_namespace do
context 'with namespace' do
before do
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 34c1a686c96..bbf50b654f4 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -27,6 +27,8 @@ describe ProjectTeam do
it { project.team.master?(guest).should be_false }
it { project.team.master?(reporter).should be_false }
it { project.team.master?(nonmember).should be_false }
+ it { project.team.member?(nonmember).should be_false }
+ it { project.team.member?(guest).should be_true }
end
end
@@ -60,6 +62,8 @@ describe ProjectTeam do
it { project.team.master?(guest).should be_true }
it { project.team.master?(reporter).should be_false }
it { project.team.master?(nonmember).should be_false }
+ it { project.team.member?(nonmember).should be_false }
+ it { project.team.member?(guest).should be_true }
end
end
end
diff --git a/spec/models/pushover_service_spec.rb b/spec/models/pushover_service_spec.rb
new file mode 100644
index 00000000000..59db69d7572
--- /dev/null
+++ b/spec/models/pushover_service_spec.rb
@@ -0,0 +1,69 @@
+# == Schema Information
+#
+# Table name: 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
+#
+
+require 'spec_helper'
+
+describe PushoverService do
+ describe 'Associations' do
+ it { should belong_to :project }
+ it { should have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'active' do
+ before do
+ subject.active = true
+ end
+
+ it { should validate_presence_of :api_key }
+ it { should validate_presence_of :user_key }
+ it { should validate_presence_of :priority }
+ end
+ end
+
+ describe 'Execute' do
+ let(:pushover) { PushoverService.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:sample_data) { GitPushService.new.sample_data(project, user) }
+
+ let(:api_key) { 'verySecret' }
+ let(:user_key) { 'verySecret' }
+ let(:device) { 'myDevice' }
+ let(:priority) { 0 }
+ let(:sound) { 'bike' }
+ let(:api_url) { 'https://api.pushover.net/1/messages.json' }
+
+ before do
+ pushover.stub(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ api_key: api_key,
+ user_key: user_key,
+ device: device,
+ priority: priority,
+ sound: sound
+ )
+
+ WebMock.stub_request(:post, api_url)
+ end
+
+ it 'should call Pushover API' do
+ pushover.execute(sample_data)
+
+ WebMock.should have_requested(:post, api_url).once
+ end
+ end
+end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
new file mode 100644
index 00000000000..6c3e221f343
--- /dev/null
+++ b/spec/models/repository_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Repository do
+ include RepoHelpers
+
+ let(:repository) { create(:project).repository }
+
+ describe :branch_names_contains do
+ subject { repository.branch_names_contains(sample_commit.id) }
+
+ it { should include('master') }
+ it { should_not include('feature') }
+ it { should_not include('fix') }
+ end
+
+ describe :last_commit_for_path do
+ subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
+
+ it { should eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
+ end
+end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 480aeabf67f..c96f2b20529 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
require 'spec_helper'
diff --git a/spec/models/slack_message_spec.rb b/spec/models/slack_message_spec.rb
index 1cd58534702..c530fad619b 100644
--- a/spec/models/slack_message_spec.rb
+++ b/spec/models/slack_message_spec.rb
@@ -1,4 +1,4 @@
-require_relative '../../app/models/project_services/slack_message'
+require 'spec_helper'
describe SlackMessage do
subject { SlackMessage.new(args) }
@@ -26,11 +26,11 @@ describe SlackMessage do
it 'returns a message regarding pushes' do
subject.pretext.should ==
- 'user_name pushed to branch <url/commits/master|master> of ' <<
+ 'user_name pushed to branch <url/commits/master|master> of '\
'<url|project_name> (<url/compare/before...after|Compare changes>)'
subject.attachments.should == [
{
- text: "<url1|abcdefghi>: message1 - author1\n" <<
+ text: "<url1|abcdefghi>: message1 - author1\n"\
"<url2|123456789>: message2 - author2",
color: color,
}
@@ -45,7 +45,7 @@ describe SlackMessage do
it 'returns a message regarding a new branch' do
subject.pretext.should ==
- 'user_name pushed new branch <url/commits/master|master> to ' <<
+ 'user_name pushed new branch <url/commits/master|master> to '\
'<url|project_name>'
subject.attachments.should be_empty
end
diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb
index 4576913b473..d4840391967 100644
--- a/spec/models/slack_service_spec.rb
+++ b/spec/models/slack_service_spec.rb
@@ -2,14 +2,14 @@
#
# Table name: 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
+# 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
#
require 'spec_helper'
@@ -26,40 +26,32 @@ describe SlackService do
subject.active = true
end
- it { should validate_presence_of :room }
- it { should validate_presence_of :subdomain }
- it { should validate_presence_of :token }
+ it { should validate_presence_of :webhook }
end
end
describe "Execute" do
- let(:slack) { SlackService.new }
- let(:user) { create(:user) }
+ let(:slack) { SlackService.new }
+ let(:user) { create(:user) }
let(:project) { create(:project) }
let(:sample_data) { GitPushService.new.sample_data(project, user) }
- let(:subdomain) { 'gitlab' }
- let(:token) { 'verySecret' }
- let(:api_url) {
- "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}"
- }
+ let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
before do
slack.stub(
project: project,
project_id: project.id,
- room: '#gitlab',
service_hook: true,
- subdomain: subdomain,
- token: token
+ webhook: webhook_url
)
- WebMock.stub_request(:post, api_url)
+ WebMock.stub_request(:post, webhook_url)
end
it "should call Slack API" do
slack.execute(sample_data)
- WebMock.should have_requested(:post, api_url).once
+ WebMock.should have_requested(:post, webhook_url).once
end
end
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index d179e9516e2..1ef2c512c1f 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -2,17 +2,17 @@
#
# Table name: snippets
#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# expires_at :datetime
-# private :boolean default(TRUE), not null
-# type :string(255)
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# file_name :string(255)
+# expires_at :datetime
+# type :string(255)
+# visibility_level :integer default(0), not null
#
require 'spec_helper'
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0250014bc21..6d865cfc691 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -287,6 +287,20 @@ describe User do
end
end
+ describe '.by_login' do
+ let(:username) { 'John' }
+ let!(:user) { create(:user, username: username) }
+
+ it 'should get the correct user' do
+ expect(User.by_login(user.email.upcase)).to eq user
+ expect(User.by_login(user.email)).to eq user
+ expect(User.by_login(username.downcase)).to eq user
+ expect(User.by_login(username)).to eq user
+ expect(User.by_login(nil)).to be_nil
+ expect(User.by_login('')).to be_nil
+ end
+ end
+
describe 'all_ssh_keys' do
it { should have_many(:keys).dependent(:destroy) }
@@ -346,6 +360,25 @@ describe User do
end
end
+ describe :ldap_user? do
+ let(:user) { build(:user, :ldap) }
+
+ it "is true if provider name starts with ldap" do
+ user.provider = 'ldapmain'
+ expect( user.ldap_user? ).to be_true
+ end
+
+ it "is false for other providers" do
+ user.provider = 'other-provider'
+ expect( user.ldap_user? ).to be_false
+ end
+
+ it "is false if no extern_uid is provided" do
+ user.extern_uid = nil
+ expect( user.ldap_user? ).to be_false
+ end
+ end
+
describe '#full_website_url' do
let(:user) { create(:user) }
@@ -429,4 +462,32 @@ describe User do
expect(user.starred?(project)).to be_false
end
end
+
+ describe "#sort" do
+ before do
+ User.delete_all
+ @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha'
+ @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega'
+ end
+
+ it "sorts users as recently_signed_in" do
+ User.sort('recent_sign_in').first.should == @user
+ end
+
+ it "sorts users as late_signed_in" do
+ User.sort('oldest_sign_in').first.should == @user1
+ end
+
+ it "sorts users as recently_created" do
+ User.sort('recently_created').first.should == @user
+ end
+
+ it "sorts users as late_created" do
+ User.sort('late_created').first.should == @user1
+ end
+
+ it "sorts users by name when nil is passed" do
+ User.sort(nil).first.should == @user
+ end
+ end
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 8834a6cfa83..b45572c39fd 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -146,6 +146,7 @@ describe API::API, api: true do
it "should remove branch" do
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
response.status.should == 200
+ json_response['branch_name'].should == branch_name
end
it 'should return 404 if branch not exists' do
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
new file mode 100644
index 00000000000..cbbd1e7de5a
--- /dev/null
+++ b/spec/requests/api/fork_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:user3) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:project) {
+ create(:project, creator_id: user.id,
+ namespace: user.namespace)
+ }
+ let(:project_user2) {
+ create(:project_member, user: user2,
+ project: project,
+ access_level: ProjectMember::GUEST)
+ }
+
+ describe 'POST /projects/fork/:id' do
+ before { project_user2 }
+ before { user3 }
+
+ context 'when authenticated' do
+ it 'should fork if user has sufficient access to project' do
+ post api("/projects/fork/#{project.id}", user2)
+ response.status.should == 201
+ json_response['name'].should == project.name
+ json_response['path'].should == project.path
+ json_response['owner']['id'].should == user2.id
+ json_response['namespace']['id'].should == user2.namespace.id
+ json_response['forked_from_project']['id'].should == project.id
+ end
+
+ it 'should fork if user is admin' do
+ post api("/projects/fork/#{project.id}", admin)
+ response.status.should == 201
+ json_response['name'].should == project.name
+ json_response['path'].should == project.path
+ json_response['owner']['id'].should == admin.id
+ json_response['namespace']['id'].should == admin.namespace.id
+ json_response['forked_from_project']['id'].should == project.id
+ end
+
+ it 'should fail on missing project access for the project to fork' do
+ post api("/projects/fork/#{project.id}", user3)
+ response.status.should == 404
+ json_response['message'].should == '404 Not Found'
+ end
+
+ it 'should fail if forked project exists in the user namespace' do
+ post api("/projects/fork/#{project.id}", user)
+ response.status.should == 409
+ json_response['message']['base'].should == ['Invalid fork destination']
+ json_response['message']['name'].should == ['has already been taken']
+ json_response['message']['path'].should == ['has already been taken']
+ end
+
+ it 'should fail if project to fork from does not exist' do
+ post api('/projects/fork/424242', user)
+ response.status.should == 404
+ json_response['message'].should == '404 Not Found'
+ end
+ end
+
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
+ post api("/projects/fork/#{project.id}")
+ response.status.should == 401
+ json_response['message'].should == '401 Unauthorized'
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb
new file mode 100644
index 00000000000..4957186f605
--- /dev/null
+++ b/spec/requests/api/group_members_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let(:owner) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:master) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:stranger) { create(:user) }
+
+ let!(:group_with_members) do
+ group = create(:group)
+ group.add_users([reporter.id], GroupMember::REPORTER)
+ group.add_users([developer.id], GroupMember::DEVELOPER)
+ group.add_users([master.id], GroupMember::MASTER)
+ group.add_users([guest.id], GroupMember::GUEST)
+ group
+ end
+
+ let!(:group_no_members) { create(:group) }
+
+ before do
+ group_with_members.add_owner owner
+ group_no_members.add_owner owner
+ end
+
+ describe "GET /groups/:id/members" do
+ context "when authenticated as user that is part or the group" do
+ it "each user: should return an array of members groups of group3" do
+ [owner, master, developer, reporter, guest].each do |user|
+ get api("/groups/#{group_with_members.id}/members", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.size.should == 5
+ json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER
+ json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER
+ json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER
+ json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER
+ json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST
+ end
+ end
+
+ it "users not part of the group should get access error" do
+ get api("/groups/#{group_with_members.id}/members", stranger)
+ response.status.should == 403
+ end
+ end
+ end
+
+ describe "POST /groups/:id/members" do
+ context "when not a member of the group" do
+ it "should not add guest as member of group_no_members when adding being done by person outside the group" do
+ post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER
+ response.status.should == 403
+ end
+ end
+
+ context "when a member of the group" do
+ it "should return ok and add new member" do
+ new_user = create(:user)
+
+ expect {
+ post api("/groups/#{group_no_members.id}/members", owner),
+ user_id: new_user.id, access_level: GroupMember::MASTER
+ }.to change { group_no_members.members.count }.by(1)
+
+ response.status.should == 201
+ json_response['name'].should == new_user.name
+ json_response['access_level'].should == GroupMember::MASTER
+ end
+
+ it "should not allow guest to modify group members" do
+ new_user = create(:user)
+
+ expect {
+ post api("/groups/#{group_with_members.id}/members", guest),
+ user_id: new_user.id, access_level: GroupMember::MASTER
+ }.not_to change { group_with_members.members.count }
+
+ response.status.should == 403
+ end
+
+ it "should return error if member already exists" do
+ post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER
+ response.status.should == 409
+ end
+
+ it "should return a 400 error when user id is not given" do
+ post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER
+ response.status.should == 400
+ end
+
+ it "should return a 400 error when access level is not given" do
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
+ response.status.should == 400
+ end
+
+ it "should return a 422 error when access level is not known" do
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
+ response.status.should == 422
+ end
+ end
+ end
+
+ describe "DELETE /groups/:id/members/:user_id" do
+ context "when not a member of the group" do
+ it "should not delete guest's membership of group_with_members" do
+ random_user = create(:user)
+ delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
+ response.status.should == 403
+ end
+ end
+
+ context "when a member of the group" do
+ it "should delete guest's membership of group" do
+ expect {
+ delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
+ }.to change { group_with_members.members.count }.by(-1)
+
+ response.status.should == 200
+ end
+
+ it "should return a 404 error when user id is not known" do
+ delete api("/groups/#{group_with_members.id}/members/1328", owner)
+ response.status.should == 404
+ end
+
+ it "should not allow guest to modify group members" do
+ delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest)
+ response.status.should == 403
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 42ccad71aaf..8dfd2cd650e 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -165,114 +165,4 @@ describe API::API, api: true do
end
end
end
-
- describe "members" do
- let(:owner) { create(:user) }
- let(:reporter) { create(:user) }
- let(:developer) { create(:user) }
- let(:master) { create(:user) }
- let(:guest) { create(:user) }
- let!(:group_with_members) do
- group = create(:group)
- group.add_users([reporter.id], GroupMember::REPORTER)
- group.add_users([developer.id], GroupMember::DEVELOPER)
- group.add_users([master.id], GroupMember::MASTER)
- group.add_users([guest.id], GroupMember::GUEST)
- group
- end
- let!(:group_no_members) { create(:group) }
-
- before do
- group_with_members.add_owner owner
- group_no_members.add_owner owner
- end
-
- describe "GET /groups/:id/members" do
- context "when authenticated as user that is part or the group" do
- it "each user: should return an array of members groups of group3" do
- [owner, master, developer, reporter, guest].each do |user|
- get api("/groups/#{group_with_members.id}/members", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.size.should == 5
- json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER
- json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER
- json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER
- json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER
- json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST
- end
- end
-
- it "users not part of the group should get access error" do
- get api("/groups/#{group_with_members.id}/members", user1)
- response.status.should == 403
- end
- end
- end
-
- describe "POST /groups/:id/members" do
- context "when not a member of the group" do
- it "should not add guest as member of group_no_members when adding being done by person outside the group" do
- post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER
- response.status.should == 403
- end
- end
-
- context "when a member of the group" do
- it "should return ok and add new member" do
- count_before=group_no_members.group_members.count
- new_user = create(:user)
- post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER
- response.status.should == 201
- json_response['name'].should == new_user.name
- json_response['access_level'].should == GroupMember::MASTER
- group_no_members.group_members.count.should == count_before + 1
- end
-
- it "should return error if member already exists" do
- post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER
- response.status.should == 409
- end
-
- it "should return a 400 error when user id is not given" do
- post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER
- response.status.should == 400
- end
-
- it "should return a 400 error when access level is not given" do
- post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
- response.status.should == 400
- end
-
- it "should return a 422 error when access level is not known" do
- post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
- response.status.should == 422
- end
- end
- end
-
- describe "DELETE /groups/:id/members/:user_id" do
- context "when not a member of the group" do
- it "should not delete guest's membership of group_with_members" do
- random_user = create(:user)
- delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
- response.status.should == 403
- end
- end
-
- context "when a member of the group" do
- it "should delete guest's membership of group" do
- count_before=group_with_members.group_members.count
- delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
- response.status.should == 200
- group_with_members.group_members.count.should == count_before - 1
- end
-
- it "should return a 404 error when user id is not known" do
- delete api("/groups/#{group_with_members.id}/members/1328", owner)
- response.status.should == 404
- end
- end
- end
- end
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 6df5ef38961..677b1494041 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -5,10 +5,11 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:key) { create(:key, user: user) }
let(:project) { create(:project) }
+ let(:secret_token) { File.read Rails.root.join('.gitlab_shell_secret') }
describe "GET /internal/check", no_db: true do
it do
- get api("/internal/check")
+ get api("/internal/check"), secret_token: secret_token
response.status.should == 200
json_response['api_version'].should == API::API.version
@@ -17,7 +18,7 @@ describe API::API, api: true do
describe "GET /internal/discover" do
it do
- get(api("/internal/discover"), key_id: key.id)
+ get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
response.status.should == 200
@@ -159,7 +160,8 @@ describe API::API, api: true do
api("/internal/allowed"),
key_id: key.id,
project: project.path_with_namespace,
- action: 'git-upload-pack'
+ action: 'git-upload-pack',
+ secret_token: secret_token
)
end
@@ -169,7 +171,8 @@ describe API::API, api: true do
changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master',
key_id: key.id,
project: project.path_with_namespace,
- action: 'git-receive-pack'
+ action: 'git-receive-pack',
+ secret_token: secret_token
)
end
@@ -179,7 +182,8 @@ describe API::API, api: true do
ref: 'master',
key_id: key.id,
project: project.path_with_namespace,
- action: 'git-upload-archive'
+ action: 'git-upload-archive',
+ secret_token: secret_token
)
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 9876452f81d..775d7b4e18d 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -4,12 +4,29 @@ describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
- let!(:closed_issue) { create(:closed_issue, author: user, assignee: user, project: project, state: :closed) }
- let!(:issue) { create(:issue, author: user, assignee: user, project: project) }
+ let!(:closed_issue) do
+ create :closed_issue,
+ author: user,
+ assignee: user,
+ project: project,
+ state: :closed,
+ milestone: milestone
+ end
+ let!(:issue) do
+ create :issue,
+ author: user,
+ assignee: user,
+ project: project,
+ milestone: milestone
+ end
let!(:label) do
create(:label, title: 'label', color: '#FFAABB', project: project)
end
let!(:label_link) { create(:label_link, label: label, target: issue) }
+ let!(:milestone) { create(:milestone, title: '1.0.0', project: project) }
+ let!(:empty_milestone) do
+ create(:milestone, title: '2.0.0', project: project)
+ end
before { project.team << [user, :reporter] }
@@ -102,15 +119,18 @@ describe API::API, api: true do
end
describe "GET /projects/:id/issues" do
+ let(:base_url) { "/projects/#{project.id}" }
+ let(:title) { milestone.title }
+
it "should return project issues" do
- get api("/projects/#{project.id}/issues", user)
+ get api("#{base_url}/issues", user)
response.status.should == 200
json_response.should be_an Array
json_response.first['title'].should == issue.title
end
it 'should return an array of labeled project issues' do
- get api("/projects/#{project.id}/issues?labels=#{label.title}", user)
+ get api("#{base_url}/issues?labels=#{label.title}", user)
response.status.should == 200
json_response.should be_an Array
json_response.length.should == 1
@@ -118,7 +138,7 @@ describe API::API, api: true do
end
it 'should return an array of labeled project issues when at least one label matches' do
- get api("/projects/#{project.id}/issues?labels=#{label.title},foo,bar", user)
+ get api("#{base_url}/issues?labels=#{label.title},foo,bar", user)
response.status.should == 200
json_response.should be_an Array
json_response.length.should == 1
@@ -126,11 +146,43 @@ describe API::API, api: true do
end
it 'should return an empty array if no project issue matches labels' do
- get api("/projects/#{project.id}/issues?labels=foo,bar", user)
+ get api("#{base_url}/issues?labels=foo,bar", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.length.should == 0
+ end
+
+ it 'should return an empty array if no issue matches milestone' do
+ get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user)
response.status.should == 200
json_response.should be_an Array
json_response.length.should == 0
end
+
+ it 'should return an empty array if milestone does not exist' do
+ get api("#{base_url}/issues?milestone=foo", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.length.should == 0
+ end
+
+ it 'should return an array of issues in given milestone' do
+ get api("#{base_url}/issues?milestone=#{title}", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.length.should == 2
+ json_response.first['id'].should == issue.id
+ json_response.second['id'].should == closed_issue.id
+ end
+
+ it 'should return an array of issues matching state in milestone' do
+ get api("#{base_url}/issues?milestone=#{milestone.title}"\
+ '&state=closed', user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.length.should == 1
+ json_response.first['id'].should == closed_issue.id
+ end
end
describe "GET /projects/:id/issues/:issue_id" do
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index d9d52468b15..b8943ea0762 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -20,8 +20,7 @@ describe API::API, api: true do
response.status.should == 200
json_response.should be_an Array
- # Admin namespace + 2 group namespaces
- json_response.length.should == 3
+ json_response.length.should == Namespace.count
end
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 571d8506277..2c4b68c10b6 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -54,8 +54,15 @@ describe API::API, api: true do
get api("/projects/all", admin)
response.status.should == 200
json_response.should be_an Array
- json_response.first['name'].should == project.name
- json_response.first['owner']['username'].should == user.username
+ project_name = project.name
+
+ json_response.detect {
+ |project| project['name'] == project_name
+ }['name'].should == project_name
+
+ json_response.detect {
+ |project| project['owner']['username'] == user.username
+ }['owner']['username'].should == user.username
end
end
end
@@ -196,15 +203,12 @@ describe API::API, api: true do
json_response['message']['name'].should == [
'can\'t be blank',
'is too short (minimum is 0 characters)',
- 'can contain only letters, digits, \'_\', \'-\' and \'.\' and '\
- 'space. It must start with letter, digit or \'_\'.'
+ Gitlab::Regex.project_regex_message
]
json_response['message']['path'].should == [
'can\'t be blank',
'is too short (minimum is 0 characters)',
- 'can contain only letters, digits, \'_\', \'-\' and \'.\'. It must '\
- 'start with letter, digit or \'_\', optionally preceeded by \'.\'. '\
- 'It must not end in \'.git\'.'
+ Gitlab::Regex.send(:default_regex_message)
]
end
@@ -332,6 +336,7 @@ describe API::API, api: true do
json_event['action_name'].should == 'joined'
json_event['project_id'].to_i.should == project.id
+ json_event['author_username'].should == user.username
end
it "should return a 404 error if not found" do
@@ -625,6 +630,11 @@ describe API::API, api: true do
describe "DELETE /projects/:id" do
context "when authenticated as user" do
it "should remove project" do
+ expect(GitlabShellWorker).to(
+ receive(:perform_async).with(:remove_repository,
+ /#{project.path_with_namespace}/)
+ ).twice
+
delete api("/projects/#{project.id}", user)
response.status.should == 200
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index a339dbfe9db..beae71c02d9 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -34,21 +34,23 @@ describe API::API, api: true do
end
end
- # TODO: fix this test for CI
- #context 'annotated tag' do
- #it 'should create a new annotated tag' do
- #post api("/projects/#{project.id}/repository/tags", user),
- #tag_name: 'v7.1.0',
- #ref: 'master',
- #message: 'tag message'
-
- #response.status.should == 201
- #json_response['name'].should == 'v7.1.0'
- # The message is not part of the JSON response.
- # Additional changes to the gitlab_git gem may be required.
- # json_response['message'].should == 'tag message'
- #end
- #end
+ context 'annotated tag' do
+ it 'should create a new annotated tag' do
+ # Identity must be set in .gitconfig to create annotated tag.
+ repo_path = project.repository.path_to_repo
+ system(*%W(git --git-dir=#{repo_path} config user.name #{user.name}))
+ system(*%W(git --git-dir=#{repo_path} config user.email #{user.email}))
+
+ post api("/projects/#{project.id}/repository/tags", user),
+ tag_name: 'v7.1.0',
+ ref: 'master',
+ message: 'Release 7.1.0'
+
+ response.status.should == 201
+ json_response['name'].should == 'v7.1.0'
+ json_response['message'].should == 'Release 7.1.0'
+ end
+ end
it 'should deny for user without push access' do
post api("/projects/#{project.id}/repository/tags", user2),
@@ -224,8 +226,8 @@ describe API::API, api: true do
contributor['email'].should == 'dmitriy.zaporozhets@gmail.com'
contributor['name'].should == 'Dmitriy Zaporozhets'
contributor['commits'].should == 13
- contributor['additions'].should == 4081
- contributor['deletions'].should == 29
+ contributor['additions'].should == 0
+ contributor['deletions'].should == 0
end
end
end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index f883c9e028a..d8282d0696b 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -27,4 +27,30 @@ describe API::API, api: true do
project.gitlab_ci_service.should be_nil
end
end
+
+ describe 'PUT /projects/:id/services/hipchat' do
+ it 'should update hipchat settings' do
+ put api("/projects/#{project.id}/services/hipchat", user),
+ token: 'secret-token', room: 'test'
+
+ response.status.should == 200
+ project.hipchat_service.should_not be_nil
+ end
+
+ it 'should return if required fields missing' do
+ put api("/projects/#{project.id}/services/gitlab-ci", user),
+ token: 'secret-token', active: true
+
+ response.status.should == 400
+ end
+ end
+
+ describe 'DELETE /projects/:id/services/hipchat' do
+ it 'should delete hipchat settings' do
+ delete api("/projects/#{project.id}/services/hipchat", user)
+
+ response.status.should == 200
+ project.hipchat_service.should be_nil
+ end
+ end
end
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index 013f425d6ce..57b2e6cbd6a 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -19,6 +19,32 @@ describe API::API, api: true do
end
end
+ context 'when email has case-typo and password is valid' do
+ it 'should return private token' do
+ post api('/session'), email: user.email.upcase, password: '12345678'
+ expect(response.status).to eq 201
+
+ expect(json_response['email']).to eq user.email
+ expect(json_response['private_token']).to eq user.private_token
+ expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['can_create_project']).to eq user.can_create_project?
+ expect(json_response['can_create_group']).to eq user.can_create_group?
+ end
+ end
+
+ context 'when login has case-typo and password is valid' do
+ it 'should return private token' do
+ post api('/session'), login: user.username.upcase, password: '12345678'
+ expect(response.status).to eq 201
+
+ expect(json_response['email']).to eq user.email
+ expect(json_response['private_token']).to eq user.private_token
+ expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['can_create_project']).to eq user.can_create_project?
+ expect(json_response['can_create_group']).to eq user.can_create_group?
+ end
+ end
+
context "when invalid password" do
it "should return authentication error" do
post api("/session"), email: user.email, password: '123'
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b0752ebe43c..113a39b870e 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -20,7 +20,10 @@ describe API::API, api: true do
get api("/users", user)
response.status.should == 200
json_response.should be_an Array
- json_response.first['username'].should == user.username
+ username = user.username
+ json_response.detect {
+ |user| user['username'] == username
+ }['username'].should == username
end
end
@@ -137,9 +140,7 @@ describe API::API, api: true do
json_response['message']['projects_limit'].
should == ['must be greater than or equal to 0']
json_response['message']['username'].
- should == ['can contain only letters, digits, '\
- '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\
- '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.']
+ should == [Gitlab::Regex.send(:default_regex_message)]
end
it "shouldn't available for non admin users" do
@@ -281,9 +282,7 @@ describe API::API, api: true do
json_response['message']['projects_limit'].
should == ['must be greater than or equal to 0']
json_response['message']['username'].
- should == ['can contain only letters, digits, '\
- '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\
- '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.']
+ should == [Gitlab::Regex.send(:default_regex_message)]
end
context "with existing user" do
@@ -430,6 +429,7 @@ describe API::API, api: true do
json_response['is_admin'].should == user.is_admin?
json_response['can_create_project'].should == user.can_create_project?
json_response['can_create_group'].should == user.can_create_group?
+ json_response['projects_limit'].should == user.projects_limit
end
it "should return 401 error if user is unauthenticated" do
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index fa99acabc78..19b442573f4 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -8,12 +8,38 @@ describe GitPushService do
let (:service) { GitPushService.new }
before do
- @blankrev = '0000000000000000000000000000000000000000'
+ @blankrev = Gitlab::Git::BLANK_SHA
@oldrev = sample_commit.parent_id
@newrev = sample_commit.id
@ref = 'refs/heads/master'
end
+ describe 'Push branches' do
+ context 'new branch' do
+ subject do
+ service.execute(project, user, @blankrev, @newrev, @ref)
+ end
+
+ it { should be_true }
+ end
+
+ context 'existing branch' do
+ subject do
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ end
+
+ it { should be_true }
+ end
+
+ context 'rm branch' do
+ subject do
+ service.execute(project, user, @oldrev, @blankrev, @ref)
+ end
+
+ it { should be_true }
+ end
+ end
+
describe "Git Push Data" do
before do
service.execute(project, user, @oldrev, @newrev, @ref)
@@ -79,6 +105,8 @@ describe GitPushService do
context "execute web hooks" do
it "when pushing a branch for the first time" do
project.should_receive(:execute_hooks)
+ project.default_branch.should == "master"
+ project.protected_branches.should_receive(:create).with({ name: "master" })
service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
new file mode 100644
index 00000000000..9f294152053
--- /dev/null
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+
+describe MergeRequests::RefreshService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:service) { MergeRequests::RefreshService }
+
+ describe :execute do
+ before do
+ @user = create(:user)
+ group = create(:group)
+ group.add_owner(@user)
+
+ @project = create(:project, namespace: group)
+ @fork_project = Projects::ForkService.new(@project, @user).execute
+ @merge_request = create(:merge_request, source_project: @project,
+ source_branch: 'master',
+ target_branch: 'feature',
+ target_project: @project)
+
+ @fork_merge_request = create(:merge_request, source_project: @fork_project,
+ source_branch: 'master',
+ target_branch: 'feature',
+ target_project: @project)
+
+ @commits = @merge_request.commits
+
+ @oldrev = @commits.last.id
+ @newrev = @commits.first.id
+ end
+
+ context 'push to origin repo source branch' do
+ before do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should_not be_empty }
+ it { @merge_request.should be_open }
+ it { @fork_merge_request.should be_open }
+ it { @fork_merge_request.notes.should be_empty }
+ end
+
+ context 'push to origin repo target branch' do
+ before do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should be_empty }
+ it { @merge_request.should be_merged }
+ it { @fork_merge_request.should be_merged }
+ it { @fork_merge_request.notes.should be_empty }
+ end
+
+ context 'push to fork repo source branch' do
+ before do
+ service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should be_empty }
+ it { @merge_request.should be_open }
+ it { @fork_merge_request.notes.should_not be_empty }
+ it { @fork_merge_request.should be_open }
+ end
+
+ context 'push to fork repo target branch' do
+ before do
+ service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should be_empty }
+ it { @merge_request.should be_open }
+ it { @fork_merge_request.notes.should be_empty }
+ it { @fork_merge_request.should be_open }
+ end
+
+ context 'push to origin repo target branch after fork project was removed' do
+ before do
+ @fork_project.destroy
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should be_empty }
+ it { @merge_request.should be_merged }
+ it { @fork_merge_request.should be_open }
+ it { @fork_merge_request.notes.should be_empty }
+ end
+
+ def reload_mrs
+ @merge_request.reload
+ @fork_merge_request.reload
+ end
+ end
+end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 2508dfc4565..79d0526ff89 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -3,15 +3,12 @@ require 'spec_helper'
describe Projects::TransferService do
let(:user) { create(:user) }
let(:group) { create(:group) }
- let(:group2) { create(:group) }
let(:project) { create(:project, namespace: user.namespace) }
context 'namespace -> namespace' do
before do
group.add_owner(user)
- @service = Projects::TransferService.new(project, user, namespace_id: group.id)
- @service.gitlab_shell.stub(mv_repository: true)
- @result = @service.execute
+ @result = transfer_project(project, user, namespace_id: group.id)
end
it { @result.should be_true }
@@ -20,24 +17,25 @@ describe Projects::TransferService do
context 'namespace -> no namespace' do
before do
- group.add_owner(user)
- @service = Projects::TransferService.new(project, user, namespace_id: nil)
- @service.gitlab_shell.stub(mv_repository: true)
- @result = @service.execute
+ @result = transfer_project(project, user, namespace_id: nil)
end
+ it { @result.should_not be_nil } # { result.should be_false } passes on nil
it { @result.should be_false }
it { project.namespace.should == user.namespace }
end
context 'namespace -> not allowed namespace' do
before do
- @service = Projects::TransferService.new(project, user, namespace_id: group2.id)
- @service.gitlab_shell.stub(mv_repository: true)
- @result = @service.execute
+ @result = transfer_project(project, user, namespace_id: group.id)
end
+ it { @result.should_not be_nil } # { result.should be_false } passes on nil
it { @result.should be_false }
it { project.namespace.should == user.namespace }
end
+
+ def transfer_project(project, user, params)
+ Projects::TransferService.new(project, user, params).execute
+ end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 60322b67a79..773de6628b1 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -4,7 +4,7 @@ end
if ENV['COVERALLS']
require 'coveralls'
- Coveralls.wear_merged!('rails')
+ Coveralls.wear_merged!
end
ENV["RAILS_ENV"] ||= 'test'
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index cd8a076318c..791d2a1fd64 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -21,6 +21,6 @@ module LoginHelpers
# Requires Javascript driver.
def logout
- find(:css, ".icon-signout").click
+ find(:css, ".fa.fa-sign-out").click
end
end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 0d67e7ee4e6..ebd74206699 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -14,21 +14,31 @@ def common_mentionable_setup
let(:mentioned_mr) { create :merge_request, :simple, source_project: mproject }
let(:mentioned_commit) { double('commit', sha: '1234567890abcdef').as_null_object }
+ let(:ext_proj) { create :project, :public }
+ let(:ext_issue) { create :issue, project: ext_proj }
+ let(:other_ext_issue) { create :issue, project: ext_proj }
+ let(:ext_mr) { create :merge_request, :simple, source_project: ext_proj }
+ let(:ext_commit) { ext_proj.repository.commit }
+
# Override to add known commits to the repository stub.
let(:extra_commits) { [] }
# A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference
# to this string and place it in their +mentionable_text+.
let(:ref_string) do
- "mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, !#{mentioned_mr.iid}, " +
- "#{mentioned_commit.sha[0..5]} and itself as #{backref_text}"
+ "mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, " +
+ "!#{mentioned_mr.iid}, " +
+ "#{ext_proj.path_with_namespace}##{ext_issue.iid}, " +
+ "#{ext_proj.path_with_namespace}!#{ext_mr.iid}, " +
+ "#{ext_proj.path_with_namespace}@#{ext_commit.short_id}, " +
+ "#{mentioned_commit.sha[0..10]} and itself as #{backref_text}"
end
before do
# Wire the project's repository to return the mentioned commit, and +nil+ for any
# unrecognized commits.
- commitmap = { '123456' => mentioned_commit }
- extra_commits.each { |c| commitmap[c.sha[0..5]] = c }
+ commitmap = { '1234567890a' => mentioned_commit }
+ extra_commits.each { |c| commitmap[c.short_id] = c }
mproject.repository.stub(:commit) { |sha| commitmap[sha] }
set_mentionable_text.call(ref_string)
end
@@ -44,15 +54,20 @@ shared_examples 'a mentionable' do
it "extracts references from its reference property" do
# De-duplicate and omit itself
refs = subject.references(mproject)
-
- refs.should have(3).items
+ refs.should have(6).items
refs.should include(mentioned_issue)
refs.should include(mentioned_mr)
refs.should include(mentioned_commit)
+ refs.should include(ext_issue)
+ refs.should include(ext_mr)
+ refs.should include(ext_commit)
end
it 'creates cross-reference notes' do
- [mentioned_issue, mentioned_mr, mentioned_commit].each do |referenced|
+ mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit,
+ ext_issue, ext_mr, ext_commit]
+
+ mentioned_objects.each do |referenced|
Note.should_receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject)
end
@@ -73,15 +88,25 @@ shared_examples 'an editable mentionable' do
it_behaves_like 'a mentionable'
it 'creates new cross-reference notes when the mentionable text is edited' do
- new_text = "this text still mentions ##{mentioned_issue.iid} and #{mentioned_commit.sha[0..5]}, " +
- "but now it mentions ##{other_issue.iid}, too."
+ new_text = "still mentions ##{mentioned_issue.iid}, " +
+ "#{mentioned_commit.sha[0..10]}, " +
+ "#{ext_issue.iid}, " +
+ "new refs: ##{other_issue.iid}, " +
+ "#{ext_proj.path_with_namespace}##{other_ext_issue.iid}"
- [mentioned_issue, mentioned_commit].each do |oldref|
+ [mentioned_issue, mentioned_commit, ext_issue].each do |oldref|
Note.should_not_receive(:create_cross_reference_note).with(oldref, subject.local_reference,
mauthor, mproject)
end
- Note.should_receive(:create_cross_reference_note).with(other_issue, subject.local_reference, mauthor, mproject)
+ [other_issue, other_ext_issue].each do |newref|
+ Note.should_receive(:create_cross_reference_note).with(
+ newref,
+ subject.local_reference,
+ mauthor,
+ mproject
+ )
+ end
subject.save
set_mentionable_text.call(new_text)
diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb
new file mode 100644
index 00000000000..42252675683
--- /dev/null
+++ b/spec/support/taskable_shared_examples.rb
@@ -0,0 +1,42 @@
+# Specs for task state functionality for issues and merge requests.
+#
+# Requires a context containing:
+# let(:subject) { Issue or MergeRequest }
+shared_examples 'a Taskable' do
+ before do
+ subject.description = <<EOT.gsub(/ {6}/, '')
+ * [ ] Task 1
+ * [x] Task 2
+ * [x] Task 3
+ * [ ] Task 4
+ * [ ] Task 5
+EOT
+ end
+
+ it 'updates the Nth task correctly' do
+ subject.update_nth_task(1, true)
+ expect(subject.description).to match(/\[x\] Task 1/)
+
+ subject.update_nth_task(2, true)
+ expect(subject.description).to match('\[x\] Task 2')
+
+ subject.update_nth_task(3, false)
+ expect(subject.description).to match('\[ \] Task 3')
+
+ subject.update_nth_task(4, false)
+ expect(subject.description).to match('\[ \] Task 4')
+ end
+
+ it 'returns the correct task status' do
+ expect(subject.task_status).to match('5 tasks')
+ expect(subject.task_status).to match('2 done')
+ expect(subject.task_status).to match('3 unfinished')
+ end
+
+ it 'knows if it has tasks' do
+ expect(subject.tasks?).to be_true
+
+ subject.description = 'Now I have no tasks'
+ expect(subject.tasks?).to be_false
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 4b0a3856f89..e6db410fb1c 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -3,6 +3,16 @@ require 'rspec/mocks'
module TestEnv
extend self
+ # When developing the seed repository, comment out the branch you will modify.
+ BRANCH_SHA = {
+ 'feature' => '0b4bc9a',
+ 'feature_conflict' => 'bb5206f',
+ 'fix' => '12d65c8',
+ 'improve/awesome' => '5937ac0',
+ 'markdown' => '0ed8c6c',
+ 'master' => '5937ac0'
+ }
+
# Test environment
#
# See gitlab.yml.example test section for paths
@@ -18,13 +28,13 @@ module TestEnv
if File.directory?(tmp_test_path)
Dir.entries(tmp_test_path).each do |entry|
- unless ['.', '..', 'gitlab-shell'].include?(entry)
+ unless ['.', '..', 'gitlab-shell', factory_repo_name].include?(entry)
FileUtils.rm_r(File.join(tmp_test_path, entry))
end
end
end
- FileUtils.mkdir_p(tmp_test_path)
+ FileUtils.mkdir_p(repos_path)
# Setup GitLab shell for test instance
setup_gitlab_shell
@@ -46,17 +56,35 @@ module TestEnv
end
def setup_factory_repo
- repo_path = repos_path + "/root/testme.git"
- clone_url = 'https://gitlab.com/gitlab-org/gitlab-test.git'
+ clone_url = "https://gitlab.com/gitlab-org/#{factory_repo_name}.git"
- unless File.directory?(repo_path)
- git_cmd = %W(git clone --bare #{clone_url} #{repo_path})
- system(*git_cmd)
+ unless File.directory?(factory_repo_path)
+ system(*%W(git clone #{clone_url} #{factory_repo_path}))
end
+
+ Dir.chdir(factory_repo_path) do
+ BRANCH_SHA.each do |branch, sha|
+ # Try to reset without fetching to avoid using the network.
+ reset = %W(git update-ref refs/heads/#{branch} #{sha})
+ unless system(*reset)
+ if system(*%w(git fetch origin))
+ unless system(*reset)
+ raise 'The fetched test seed '\
+ 'does not contain the required revision.'
+ end
+ else
+ raise 'Could not fetch test seed repository.'
+ end
+ end
+ end
+ end
+
+ # We must copy bare repositories because we will push to them.
+ system(*%W(git clone --bare #{factory_repo_path} #{factory_repo_path_bare}))
end
def copy_repo(project)
- base_repo_path = File.expand_path(repos_path + "/root/testme.git")
+ base_repo_path = File.expand_path(factory_repo_path_bare)
target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git")
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
@@ -66,4 +94,18 @@ module TestEnv
def repos_path
Gitlab.config.gitlab_shell.repos_path
end
+
+ private
+
+ def factory_repo_path
+ @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
+ end
+
+ def factory_repo_path_bare
+ factory_repo_path.to_s + '_bare'
+ end
+
+ def factory_repo_name
+ 'gitlab-test'
+ end
end
diff --git a/vendor/assets/javascripts/chart-lib.min.js b/vendor/assets/javascripts/chart-lib.min.js
new file mode 100755
index 00000000000..626e6c3cdb9
--- /dev/null
+++ b/vendor/assets/javascripts/chart-lib.min.js
@@ -0,0 +1,11 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 1.0.1-beta.4
+ *
+ * Copyright 2014 Nick Downie
+ * Released under the MIT license
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
+ */
+(function(){"use strict";var t=this,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;this.width=t.canvas.width,this.height=t.canvas.height;return this.aspectRatio=this.width/this.height,s.retinaScale(this),this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},e.types={};var s=e.helpers={},n=s.each=function(t,i,e){var s=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n<t.length;n++)i.apply(e,[t[n],n].concat(s))}else for(var o in t)i.apply(e,[t[o],o].concat(s))},o=s.clone=function(t){var i={};return n(t,function(e,s){t.hasOwnProperty(s)&&(i[s]=e)}),i},a=s.extend=function(t){return n(Array.prototype.slice.call(arguments,1),function(i){n(i,function(e,s){i.hasOwnProperty(s)&&(t[s]=e)})}),t},h=s.merge=function(){var t=Array.prototype.slice.call(arguments,0);return t.unshift({}),a.apply(null,t)},l=s.indexOf=function(t,i){if(Array.prototype.indexOf)return t.indexOf(i);for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1},r=(s.where=function(t,i){var e=[];return s.each(t,function(t){i(t)&&e.push(t)}),e},s.findNextWhere=function(t,i,e){e||(e=-1);for(var s=e+1;s<t.length;s++){var n=t[s];if(i(n))return n}},s.findPreviousWhere=function(t,i,e){e||(e=t.length);for(var s=e-1;s>=0;s--){var n=t[s];if(i(n))return n}},s.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},s=function(){this.constructor=e};return s.prototype=i.prototype,e.prototype=new s,e.extend=r,t&&a(e.prototype,t),e.__super__=i.prototype,e}),c=s.noop=function(){},u=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=s.amd="function"==typeof t.define&&t.define.amd,f=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},m=s.min=function(t){return Math.min.apply(Math,t)},v=(s.cap=function(t,i,e){if(f(i)){if(t>i)return i}else if(f(e)&&e>t)return e;return t},s.getDecimalPlaces=function(t){return t%1!==0&&f(t)?t.toString().split(".")[1].length:0}),x=s.radians=function(t){return t*(Math.PI/180)},S=(s.getAngleFromPoint=function(t,i){var e=i.x-t.x,s=i.y-t.y,n=Math.sqrt(e*e+s*s),o=2*Math.PI+Math.atan2(s,e);return 0>e&&0>s&&(o+=2*Math.PI),{angle:o,distance:n}},s.aliasPixel=function(t){return t%2===0?0:.5}),y=(s.splineCurve=function(t,i,e,s){var n=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),o=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),a=s*n/(n+o),h=s*o/(n+o);return{inner:{x:i.x-a*(e.x-t.x),y:i.y-a*(e.y-t.y)},outer:{x:i.x+h*(e.x-t.x),y:i.y+h*(e.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),C=(s.calculateScaleRange=function(t,i,e,s,n){var o=2,a=Math.floor(i/(1.5*e)),h=o>=a,l=g(t),r=m(t);l===r&&(l+=.5,r>=.5&&!s?r-=.5:l+=.5);for(var c=Math.abs(l-r),u=y(c),d=Math.ceil(l/(1*Math.pow(10,u)))*Math.pow(10,u),p=s?0:Math.floor(r/(1*Math.pow(10,u)))*Math.pow(10,u),f=d-p,v=Math.pow(10,u),x=Math.round(f/v);(x>a||a>2*x)&&!h;)if(x>a)v*=2,x=Math.round(f/v),x%1!==0&&(h=!0);else if(n&&u>=0){if(v/2%1!==0)break;v/=2,x=Math.round(f/v)}else v/=2,x=Math.round(f/v);return h&&(x=o,v=f/x),{steps:x,stepValue:v,min:p,max:p+x*v}},s.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):s[t]=s[t];return i?e(i):e}if(t instanceof Function)return t(i);var s={};return e(t,i)}),b=(s.generateLabels=function(t,i,e,s){var o=new Array(i);return labelTemplateString&&n(o,function(i,n){o[n]=C(t,{value:e+s*(n+1)})}),o},s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),-(s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)))},easeOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),s*Math.pow(2,-10*t)*Math.sin(2*(1*t-i)*Math.PI/e)+1)},easeInOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:2==(t/=.5)?1:(e||(e=.3*1.5),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),1>t?-.5*s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):s*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+2)},easeInBounce:function(t){return 1-b.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*b.easeInBounce(2*t):.5*b.easeOutBounce(2*t-1)+.5}}),w=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),P=(s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),s.animationLoop=function(t,i,e,s,n,o){var a=0,h=b[e]||b.linear,l=function(){a++;var e=a/i,r=h(e);t.call(o,r,e,a),s.call(o,r,e),i>a?o.animationFrame=w(l):n.apply(o)};w(l)},s.getRelativePosition=function(t){var i,e,s=t.originalEvent||t,n=t.currentTarget||t.srcElement,o=n.getBoundingClientRect();return s.touches?(i=s.touches[0].clientX-o.left,e=s.touches[0].clientY-o.top):(i=s.clientX-o.left,e=s.clientY-o.top),{x:i,y:e}},s.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),L=s.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=c},k=(s.bindEvents=function(t,i,e){t.events||(t.events={}),n(i,function(i){t.events[i]=function(){e.apply(t,arguments)},P(t.chart.canvas,i,t.events[i])})},s.unbindEvents=function(t,i){n(i,function(i,e){L(t.chart.canvas,e,i)})}),F=s.getMaximumWidth=function(t){var i=t.parentNode;return i.clientWidth},R=s.getMaximumHeight=function(t){var i=t.parentNode;return i.clientHeight},A=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,s=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=s+"px",i.canvas.height=s*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),T=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},M=s.fontString=function(t,i,e){return i+" "+t+"px "+e},W=s.longestText=function(t,i,e){t.font=i;var s=0;return n(e,function(i){var e=t.measureText(i).width;s=e>s?e:s}),s},z=s.drawRoundedRectangle=function(t,i,e,s,n,o){t.beginPath(),t.moveTo(i+o,e),t.lineTo(i+s-o,e),t.quadraticCurveTo(i+s,e,i+s,e+o),t.lineTo(i+s,e+n-o),t.quadraticCurveTo(i+s,e+n,i+s-o,e+n),t.lineTo(i+o,e+n),t.quadraticCurveTo(i,e+n,i,e+n-o),t.lineTo(i,e+o),t.quadraticCurveTo(i,e,i+o,e),t.closePath()};e.instances={},e.Type=function(t,i,s){this.options=i,this.chart=s,this.id=u(),e.instances[this.id]=this,i.responsive&&this.resize(),this.initialize.call(this,t)},a(e.Type.prototype,{initialize:function(){return this},clear:function(){return T(this.chart),this},stop:function(){return s.cancelAnimFrame.call(t,this.animationFrame),this},resize:function(t){this.stop();var i=this.chart.canvas,e=F(this.chart.canvas),s=this.options.maintainAspectRatio?e/this.chart.aspectRatio:R(this.chart.canvas);return i.width=this.chart.width=e,i.height=this.chart.height=s,A(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t){return t&&this.reflow(),this.options.animation&&!t?s.animationLoop(this.draw,this.options.animationSteps,this.options.animationEasing,this.options.onAnimationProgress,this.options.onAnimationComplete,this):(this.draw(),this.options.onAnimationComplete.call(this)),this},generateLegend:function(){return C(this.options.legendTemplate,this)},destroy:function(){this.clear(),k(this,this.events),delete e.instances[this.id]},showTooltip:function(t,i){"undefined"==typeof this.activeElements&&(this.activeElements=[]);var o=function(t){var i=!1;return t.length!==this.activeElements.length?i=!0:(n(t,function(t,e){t!==this.activeElements[e]&&(i=!0)},this),i)}.call(this,t);if(o||i){if(this.activeElements=t,this.draw(),t.length>0)if(this.datasets&&this.datasets.length>1){for(var a,h,r=this.datasets.length-1;r>=0&&(a=this.datasets[r].points||this.datasets[r].bars||this.datasets[r].segments,h=l(a,t[0]),-1===h);r--);var c=[],u=[],d=function(){var t,i,e,n,o,a=[],l=[],r=[];return s.each(this.datasets,function(i){t=i.points||i.bars||i.segments,t[h]&&t[h].hasValue()&&a.push(t[h])}),s.each(a,function(t){l.push(t.x),r.push(t.y),c.push(s.template(this.options.multiTooltipTemplate,t)),u.push({fill:t._saved.fillColor||t.fillColor,stroke:t._saved.strokeColor||t.strokeColor})},this),o=m(r),e=g(r),n=m(l),i=g(l),{x:n>this.chart.width/2?n:i,y:(o+e)/2}}.call(this,h);new e.MultiTooltip({x:d.x,y:d.y,xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,xOffset:this.options.tooltipXOffset,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,titleTextColor:this.options.tooltipTitleFontColor,titleFontFamily:this.options.tooltipTitleFontFamily,titleFontStyle:this.options.tooltipTitleFontStyle,titleFontSize:this.options.tooltipTitleFontSize,cornerRadius:this.options.tooltipCornerRadius,labels:c,legendColors:u,legendColorBackground:this.options.multiTooltipKeyBackground,title:t[0].label,chart:this.chart,ctx:this.chart.ctx}).draw()}else n(t,function(t){var i=t.tooltipPosition();new e.Tooltip({x:Math.round(i.x),y:Math.round(i.y),xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,caretHeight:this.options.tooltipCaretSize,cornerRadius:this.options.tooltipCornerRadius,text:C(this.options.tooltipTemplate,t),chart:this.chart}).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),e.Type.extend=function(t){var i=this,s=function(){return i.apply(this,arguments)};if(s.prototype=o(i.prototype),a(s.prototype,t),s.extend=e.Type.extend,t.name||i.prototype.name){var n=t.name||i.prototype.name,l=e.defaults[i.prototype.name]?o(e.defaults[i.prototype.name]):{};e.defaults[n]=a(l,t.defaults),e.types[n]=s,e.prototype[n]=function(t,i){var o=h(e.defaults.global,e.defaults[n],i||{});return new s(t,o,this)}}else d("Name not provided for this chart, so it hasn't been registered");return i},e.Element=function(t){a(this,t),this.initialize.apply(this,arguments),this.save()},a(e.Element.prototype,{initialize:function(){},restore:function(t){return t?n(t,function(t){this[t]=this._saved[t]},this):a(this,this._saved),this},save:function(){return this._saved=o(this),delete this._saved._saved,this},update:function(t){return n(t,function(t,i){this._saved[i]=this[i],this[i]=t},this),this},transition:function(t,i){return n(t,function(t,e){this[e]=(t-this._saved[e])*i+this._saved[e]},this),this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return f(this.value)}}),e.Element.extend=r,e.Point=e.Element.extend({display:!0,inRange:function(t,i){var e=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(i-this.y,2)<Math.pow(e,2)},draw:function(){if(this.display){var t=this.ctx;t.beginPath(),t.arc(this.x,this.y,this.radius,0,2*Math.PI),t.closePath(),t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.fillStyle=this.fillColor,t.fill(),t.stroke()}}}),e.Arc=e.Element.extend({inRange:function(t,i){var e=s.getAngleFromPoint(this,{x:t,y:i}),n=e.angle>=this.startAngle&&e.angle<=this.endAngle,o=e.distance>=this.innerRadius&&e.distance<=this.outerRadius;return n&&o},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,i=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*i,y:this.y+Math.sin(t)*i}},draw:function(t){var i=this.ctx;i.beginPath(),i.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),i.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),i.closePath(),i.strokeStyle=this.strokeColor,i.lineWidth=this.strokeWidth,i.fillStyle=this.fillColor,i.fill(),i.lineJoin="bevel",this.showStroke&&i.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this.ctx,i=this.width/2,e=this.x-i,s=this.x+i,n=this.base-(this.base-this.y),o=this.strokeWidth/2;this.showStroke&&(e+=o,s-=o,n+=o),t.beginPath(),t.fillStyle=this.fillColor,t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.moveTo(e,this.base),t.lineTo(e,n),t.lineTo(s,n),t.lineTo(s,this.base),t.fill(),this.showStroke&&t.stroke()},height:function(){return this.base-this.y},inRange:function(t,i){return t>=this.x-this.width/2&&t<=this.x+this.width/2&&i>=this.y&&i<=this.base}}),e.Tooltip=e.Element.extend({draw:function(){var t=this.chart.ctx;t.font=M(this.fontSize,this.fontStyle,this.fontFamily),this.xAlign="center",this.yAlign="above";var i=2,e=t.measureText(this.text).width+2*this.xPadding,s=this.fontSize+2*this.yPadding,n=s+this.caretHeight+i;this.x+e/2>this.chart.width?this.xAlign="left":this.x-e/2<0&&(this.xAlign="right"),this.y-n<0&&(this.yAlign="below");var o=this.x-e/2,a=this.y-n;switch(t.fillStyle=this.fillColor,this.yAlign){case"above":t.beginPath(),t.moveTo(this.x,this.y-i),t.lineTo(this.x+this.caretHeight,this.y-(i+this.caretHeight)),t.lineTo(this.x-this.caretHeight,this.y-(i+this.caretHeight)),t.closePath(),t.fill();break;case"below":a=this.y+i+this.caretHeight,t.beginPath(),t.moveTo(this.x,this.y+i),t.lineTo(this.x+this.caretHeight,this.y+i+this.caretHeight),t.lineTo(this.x-this.caretHeight,this.y+i+this.caretHeight),t.closePath(),t.fill()}switch(this.xAlign){case"left":o=this.x-e+(this.cornerRadius+this.caretHeight);break;case"right":o=this.x-(this.cornerRadius+this.caretHeight)}z(t,o,a,e,s,this.cornerRadius),t.fill(),t.fillStyle=this.textColor,t.textAlign="center",t.textBaseline="middle",t.fillText(this.text,o+e/2,a+s/2)}}),e.MultiTooltip=e.Element.extend({initialize:function(){this.font=M(this.fontSize,this.fontStyle,this.fontFamily),this.titleFont=M(this.titleFontSize,this.titleFontStyle,this.titleFontFamily),this.height=this.labels.length*this.fontSize+(this.labels.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize,this.ctx.font=this.titleFont;var t=this.ctx.measureText(this.title).width,i=W(this.ctx,this.font,this.labels)+this.fontSize+3,e=g([i,t]);this.width=e+2*this.xPadding;var s=this.height/2;this.y-s<0?this.y=s:this.y+s>this.chart.height&&(this.y=this.chart.height-s),this.x>this.chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset},getLineHeight:function(t){var i=this.y-this.height/2+this.yPadding,e=t-1;return 0===t?i+this.titleFontSize/2:i+(1.5*this.fontSize*e+this.fontSize/2)+1.5*this.titleFontSize},draw:function(){z(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var t=this.ctx;t.fillStyle=this.fillColor,t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=this.titleTextColor,t.font=this.titleFont,t.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0)),t.font=this.font,s.each(this.labels,function(i,e){t.fillStyle=this.textColor,t.fillText(i,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(e+1)),t.fillStyle=this.legendColorBackground,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize),t.fillStyle=this.legendColors[e].fill,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}),e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?W(this.ctx,this.font,this.yLabels):0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),t<this.yLabelWidth&&this.calculateXLabelRotation()},calculateXLabelRotation:function(){this.ctx.font=this.font;var t,i,e=this.ctx.measureText(this.xLabels[0]).width,s=this.ctx.measureText(this.xLabels[this.xLabels.length-1]).width;if(this.xScalePaddingRight=s/2+3,this.xScalePaddingLeft=e/2>this.yLabelWidth+10?e/2:this.yLabelWidth+10,this.xLabelRotation=0,this.display){var n,o=W(this.ctx,this.font,this.xLabels);this.xLabelWidth=o;for(var a=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>a&&0===this.xLabelRotation||this.xLabelWidth>a&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(x(this.xLabelRotation)),t=n*e,i=n*s,t+this.fontSize/2>this.yLabelWidth+8&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*o;this.xLabelRotation>0&&(this.endPoint-=Math.sin(x(this.xLabelRotation))*o+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var i=this.drawingArea()/(this.min-this.max);return this.endPoint-i*(t-this.min)},calculateX:function(t){var i=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),e=i/(this.valuesCount-(this.offsetGridLines?0:1)),s=e*t+this.xScalePaddingLeft;return this.offsetGridLines&&(s+=e/2),Math.round(s)},update:function(t){s.extend(this,t),this.fit()},draw:function(){var t=this.ctx,i=(this.endPoint-this.startPoint)/this.steps,e=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,o){var a=this.endPoint-i*o,h=Math.round(a);t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,e-10,a),t.beginPath(),o>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),h+=s.aliasPixel(t.lineWidth),t.moveTo(e,h),t.lineTo(this.width,h),t.stroke(),t.closePath(),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(e-5,h),t.lineTo(e,h),t.stroke(),t.closePath()},this),n(this.xLabels,function(i,e){var s=this.calculateX(e)+S(this.lineWidth),n=this.calculateX(e-(this.offsetGridLines?.5:0))+S(this.lineWidth),o=this.xLabelRotation>0;t.beginPath(),e>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath(),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(s,o?this.endPoint+12:this.endPoint+8),t.rotate(-1*x(this.xLabelRotation)),t.font=this.font,t.textAlign=o?"right":"center",t.textBaseline=o?"middle":"top",t.fillText(i,0,0),t.restore()},this))}}),e.RadialScale=e.Element.extend({initialize:function(){this.size=m([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,i,e,s,n,o,a,h,l,r,c,u,d=m([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,g=0;for(this.ctx.font=M(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),i=0;i<this.valuesCount;i++)t=this.getPointPosition(i,d),e=this.ctx.measureText(C(this.templateString,{value:this.labels[i]})).width+5,0===i||i===this.valuesCount/2?(s=e/2,t.x+s>p&&(p=t.x+s,n=i),t.x-s<g&&(g=t.x-s,a=i)):i<this.valuesCount/2?t.x+e>p&&(p=t.x+e,n=i):i>this.valuesCount/2&&t.x-e<g&&(g=t.x-e,a=i);l=g,r=Math.ceil(p-this.width),o=this.getIndexAngle(n),h=this.getIndexAngle(a),c=r/Math.sin(o+Math.PI/2),u=l/Math.sin(h+Math.PI/2),c=f(c)?c:0,u=f(u)?u:0,this.drawingArea=d-(u+c)/2,this.setCenterPoint(u,c)},setCenterPoint:function(t,i){var e=this.width-i-this.drawingArea,s=t+this.drawingArea;this.xCenter=(s+e)/2,this.yCenter=this.height/2},getIndexAngle:function(t){var i=2*Math.PI/this.valuesCount;return t*i-Math.PI/2},getPointPosition:function(t,i){var e=this.getIndexAngle(t);return{x:Math.cos(e)*i+this.xCenter,y:Math.sin(e)*i+this.yCenter}},draw:function(){if(this.display){var t=this.ctx;if(n(this.yLabels,function(i,e){if(e>0){var s,n=e*(this.drawingArea/this.steps),o=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var a=0;a<this.valuesCount;a++)s=this.getPointPosition(a,this.calculateCenterOffset(this.min+e*this.stepValue)),0===a?t.moveTo(s.x,s.y):t.lineTo(s.x,s.y);t.closePath(),t.stroke()}if(this.showLabels){if(t.font=M(this.fontSize,this.fontStyle,this.fontFamily),this.showLabelBackdrop){var h=t.measureText(i).width;t.fillStyle=this.backdropColor,t.fillRect(this.xCenter-h/2-this.backdropPaddingX,o-this.fontSize/2-this.backdropPaddingY,h+2*this.backdropPaddingX,this.fontSize+2*this.backdropPaddingY)}t.textAlign="center",t.textBaseline="middle",t.fillStyle=this.fontColor,t.fillText(i,this.xCenter,o)}}},this),!this.lineArc){t.lineWidth=this.angleLineWidth,t.strokeStyle=this.angleLineColor;for(var i=this.valuesCount-1;i>=0;i--){if(this.angleLineWidth>0){var e=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(e.x,e.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=M(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var o=this.labels.length,a=this.labels.length/2,h=a/2,l=h>i||i>o-h,r=i===h||i===o-h;t.textAlign=0===i?"center":i===a?"center":a>i?"left":"right",t.textBaseline=r?"middle":l?"bottom":"top",t.fillText(this.labels[i],s.x,s.y)}}}}}),s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(e.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Bar",defaults:s,initialize:function(t){var s=this.options;this.ScaleClass=i.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,i,e){var n=this.calculateBaseWidth(),o=this.calculateX(e)-n/2,a=this.calculateBarWidth(t);return o+a*i+i*s.barDatasetSpacing+a/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*s.barValueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*s.barDatasetSpacing;return i/t}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getBarsAtEvent(t):[];this.eachBars(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),this.BarClass=i.Rectangle.extend({strokeWidth:this.options.barStrokeWidth,showStroke:this.options.barShowStroke,ctx:this.chart.ctx}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,bars:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.bars.push(new this.BarClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.strokeColor,fillColor:i.fillColor,highlightFill:i.highlightFill||i.fillColor,highlightStroke:i.highlightStroke||i.strokeColor}))},this)},this),this.buildScale(t.labels),this.BarClass.prototype.base=this.scale.endPoint,this.eachBars(function(t,i,s){e.extend(t,{width:this.scale.calculateBarWidth(this.datasets.length),x:this.scale.calculateBarX(this.datasets.length,s,i),y:this.scale.endPoint}),t.save()},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachBars(function(t){t.save()}),this.render()},eachBars:function(t){e.each(this.datasets,function(i,s){e.each(i.bars,t,this,s)},this)},getBarsAtEvent:function(t){for(var i,s=[],n=e.getRelativePosition(t),o=function(t){s.push(t.bars[i])},a=0;a<this.datasets.length;a++)for(i=0;i<this.datasets[a].bars.length;i++)if(this.datasets[a].bars[i].inRange(n.x,n.y))return e.each(this.datasets,o),s;return s},buildScale:function(t){var i=this,s=function(){var t=[];return i.eachBars(function(i){t.push(i.value)}),t},n={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(s(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.barShowStroke?this.options.barStrokeWidth:0,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(n,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new this.ScaleClass(n)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].bars.push(new this.BarClass({value:t,label:i,x:this.scale.calculateBarX(this.datasets.length,e,this.scale.valuesCount+1),y:this.scale.endPoint,width:this.scale.calculateBarWidth(this.datasets.length),base:this.scale.endPoint,strokeColor:this.datasets[e].strokeColor,fillColor:this.datasets[e].fillColor}))},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.bars.shift()},this),this.update()},reflow:function(){e.extend(this.BarClass.prototype,{y:this.scale.endPoint,base:this.scale.endPoint});var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();this.chart.ctx;this.scale.draw(i),e.each(this.datasets,function(t,s){e.each(t.bars,function(t,e){t.hasValue()&&(t.base=this.scale.endPoint,t.transition({x:this.scale.calculateBarX(this.datasets.length,s,e),y:this.scale.calculateY(t.value),width:this.scale.calculateBarWidth(this.datasets.length)},i).draw())},this)},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};
+i.Type.extend({name:"Doughnut",defaults:s,initialize:function(t){this.segments=[],this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=i.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.calculateTotal(t),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),e||(this.reflow(),this.update())},calculateCircumference:function(t){return 2*Math.PI*(t/this.total)},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this)},update:function(){this.calculateTotal(this.segments),e.each(this.activeElements,function(t){t.restore(["fillColor"])}),e.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,e.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var i=t?t:1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},i),t.endAngle=t.startAngle+t.circumference,t.draw(),0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle)},this)}}),i.types.Doughnut.extend({name:"Pie",defaults:e.merge(s,{percentageInnerCutout:0})})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0,bezierCurveTension:.4,pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Line",defaults:s,initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)<Math.pow(this.radius+this.hitDetectionRadius,2)}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this),this.buildScale(t.labels),this.eachPoints(function(t,i){e.extend(t,{x:this.scale.calculateX(i),y:this.scale.endPoint}),t.save()},this)},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachPoints(function(t){t.save()}),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.datasets,function(t){e.each(t.points,function(t){t.inRange(s.x,s.y)&&i.push(t)})},this),i},buildScale:function(t){var s=this,n=function(){var t=[];return s.eachPoints(function(i){t.push(i.value)}),t},o={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(n(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.pointDotRadius+this.options.pointDotStrokeWidth,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(o,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new i.Scale(o)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:this.scale.calculateX(this.scale.valuesCount+1),y:this.scale.endPoint,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.points.shift()},this),this.update()},reflow:function(){var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();var s=this.chart.ctx,n=function(t){return null!==t.value},o=function(t,i,s){return e.findNextWhere(i,n,s)||t},a=function(t,i,s){return e.findPreviousWhere(i,n,s)||t};this.scale.draw(i),e.each(this.datasets,function(t){var h=e.where(t.points,n);e.each(t.points,function(t,e){t.hasValue()&&t.transition({y:this.scale.calculateY(t.value),x:this.scale.calculateX(e)},i)},this),this.options.bezierCurve&&e.each(h,function(t,i){var s=i>0&&i<h.length-1?this.options.bezierCurveTension:0;t.controlPoints=e.splineCurve(a(t,h,i),t,o(t,h,i),s),t.controlPoints.outer.y>this.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.y<this.scale.startPoint&&(t.controlPoints.outer.y=this.scale.startPoint),t.controlPoints.inner.y>this.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y<this.scale.startPoint&&(t.controlPoints.inner.y=this.scale.startPoint)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(h,function(t,i){if(0===i)s.moveTo(t.x,t.y);else if(this.options.bezierCurve){var e=a(t,h,i);s.bezierCurveTo(e.controlPoints.outer.x,e.controlPoints.outer.y,t.controlPoints.inner.x,t.controlPoints.inner.y,t.x,t.y)}else s.lineTo(t.x,t.y)},this),s.stroke(),this.options.datasetFill&&h.length>0&&(s.lineTo(h[h.length-1].x,this.scale.endPoint),s.lineTo(h[0].x,this.scale.endPoint),s.fillStyle=t.fillColor,s.closePath(),s.fill()),e.each(h,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"PolarArea",defaults:s,initialize:function(t){this.segments=[],this.SegmentArc=i.Arc.extend({showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,ctx:this.chart.ctx,innerRadius:0,x:this.chart.width/2,y:this.chart.height/2}),this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,lineArc:!0,width:this.chart.width,height:this.chart.height,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,valuesCount:t.length}),this.updateScaleRange(t),this.scale.update(),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),e||(this.reflow(),this.update())},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var i=[];e.each(t,function(t){i.push(t.value)});var s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.calculateTotal(this.segments),e.each(this.segments,function(t){t.save()}),this.render()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),e.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),e.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var i=t||1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},i),t.endAngle=t.startAngle+t.circumference,0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle),t.draw()},this),this.scale.draw()}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers;i.Type.extend({name:"Radar",defaults:{scaleShowLine:!0,angleShowLineOut:!0,scaleShowLabels:!1,scaleBeginAtZero:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:10,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'},initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx}),this.datasets=[],this.buildScale(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,pointLabelFontColor:this.options.pointLabelFontColor,pointLabelFontSize:this.options.pointLabelFontSize,pointLabelFontFamily:this.options.pointLabelFontFamily,pointLabelFontStyle:this.options.pointLabelFontStyle,height:this.chart.height,width:this.chart.width,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,labels:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.updateScaleRange(this.datasets),this.scale.setScaleSize(),this.scale.buildYLabels()},draw:function(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this); \ No newline at end of file
diff --git a/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js
new file mode 100644
index 00000000000..ee374a07fab
--- /dev/null
+++ b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js
@@ -0,0 +1,659 @@
+/*!
+ * jQuery Password Strength plugin for Twitter Bootstrap
+ *
+ * Copyright (c) 2008-2013 Tane Piper
+ * Copyright (c) 2013 Alejandro Blanco
+ * Dual licensed under the MIT and GPL licenses.
+ */
+
+(function (jQuery) {
+// Source: src/rules.js
+
+ var rulesEngine = {};
+
+ try {
+ if (!jQuery && module && module.exports) {
+ var jQuery = require("jquery"),
+ jsdom = require("jsdom").jsdom;
+ jQuery = jQuery(jsdom().parentWindow);
+ }
+ } catch (ignore) {}
+
+ (function ($, rulesEngine) {
+ "use strict";
+ var validation = {};
+
+ rulesEngine.forbiddenSequences = [
+ "0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl",
+ "zxcvbnm", "!@#$%^&*()_+"
+ ];
+
+ validation.wordNotEmail = function (options, word, score) {
+ if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) {
+ return score;
+ }
+ return 0;
+ };
+
+ validation.wordLength = function (options, word, score) {
+ var wordlen = word.length,
+ lenScore = Math.pow(wordlen, options.rules.raisePower);
+ if (wordlen < options.common.minChar) {
+ lenScore = (lenScore + score);
+ }
+ return lenScore;
+ };
+
+ validation.wordSimilarToUsername = function (options, word, score) {
+ var username = $(options.common.usernameField).val();
+ if (username && word.toLowerCase().match(username.toLowerCase())) {
+ return score;
+ }
+ return 0;
+ };
+
+ validation.wordTwoCharacterClasses = function (options, word, score) {
+ if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) ||
+ (word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) ||
+ (word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) {
+ return score;
+ }
+ return 0;
+ };
+
+ validation.wordRepetitions = function (options, word, score) {
+ if (word.match(/(.)\1\1/)) { return score; }
+ return 0;
+ };
+
+ validation.wordSequences = function (options, word, score) {
+ var found = false,
+ j;
+ if (word.length > 2) {
+ $.each(rulesEngine.forbiddenSequences, function (idx, seq) {
+ var sequences = [seq, seq.split('').reverse().join('')];
+ $.each(sequences, function (idx, sequence) {
+ for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3:
+ if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) {
+ found = true;
+ }
+ }
+ });
+ });
+ if (found) { return score; }
+ }
+ return 0;
+ };
+
+ validation.wordLowercase = function (options, word, score) {
+ return word.match(/[a-z]/) && score;
+ };
+
+ validation.wordUppercase = function (options, word, score) {
+ return word.match(/[A-Z]/) && score;
+ };
+
+ validation.wordOneNumber = function (options, word, score) {
+ return word.match(/\d+/) && score;
+ };
+
+ validation.wordThreeNumbers = function (options, word, score) {
+ return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score;
+ };
+
+ validation.wordOneSpecialChar = function (options, word, score) {
+ return word.match(/.[!,@,#,$,%,\^,&,*,?,_,~]/) && score;
+ };
+
+ validation.wordTwoSpecialChar = function (options, word, score) {
+ return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score;
+ };
+
+ validation.wordUpperLowerCombo = function (options, word, score) {
+ return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score;
+ };
+
+ validation.wordLetterNumberCombo = function (options, word, score) {
+ return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score;
+ };
+
+ validation.wordLetterNumberCharCombo = function (options, word, score) {
+ return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score;
+ };
+
+ rulesEngine.validation = validation;
+
+ rulesEngine.executeRules = function (options, word) {
+ var totalScore = 0;
+
+ $.each(options.rules.activated, function (rule, active) {
+ if (active) {
+ var score = options.rules.scores[rule],
+ funct = rulesEngine.validation[rule],
+ result,
+ errorMessage;
+
+ if (!$.isFunction(funct)) {
+ funct = options.rules.extra[rule];
+ }
+
+ if ($.isFunction(funct)) {
+ result = funct(options, word, score);
+ if (result) {
+ totalScore += result;
+ }
+ if (result < 0 || (!$.isNumeric(result) && !result)) {
+ errorMessage = options.ui.spanError(options, rule);
+ if (errorMessage.length > 0) {
+ options.instances.errors.push(errorMessage);
+ }
+ }
+ }
+ }
+ });
+
+ return totalScore;
+ };
+ }(jQuery, rulesEngine));
+
+ try {
+ if (module && module.exports) {
+ module.exports = rulesEngine;
+ }
+ } catch (ignore) {}
+
+// Source: src/options.js
+
+
+
+
+ var defaultOptions = {};
+
+ defaultOptions.common = {};
+ defaultOptions.common.minChar = 6;
+ defaultOptions.common.usernameField = "#username";
+ defaultOptions.common.userInputs = [
+ // Selectors for input fields with user input
+ ];
+ defaultOptions.common.onLoad = undefined;
+ defaultOptions.common.onKeyUp = undefined;
+ defaultOptions.common.zxcvbn = false;
+ defaultOptions.common.debug = false;
+
+ defaultOptions.rules = {};
+ defaultOptions.rules.extra = {};
+ defaultOptions.rules.scores = {
+ wordNotEmail: -100,
+ wordLength: -50,
+ wordSimilarToUsername: -100,
+ wordSequences: -50,
+ wordTwoCharacterClasses: 2,
+ wordRepetitions: -25,
+ wordLowercase: 1,
+ wordUppercase: 3,
+ wordOneNumber: 3,
+ wordThreeNumbers: 5,
+ wordOneSpecialChar: 3,
+ wordTwoSpecialChar: 5,
+ wordUpperLowerCombo: 2,
+ wordLetterNumberCombo: 2,
+ wordLetterNumberCharCombo: 2
+ };
+ defaultOptions.rules.activated = {
+ wordNotEmail: true,
+ wordLength: true,
+ wordSimilarToUsername: true,
+ wordSequences: true,
+ wordTwoCharacterClasses: false,
+ wordRepetitions: false,
+ wordLowercase: true,
+ wordUppercase: true,
+ wordOneNumber: true,
+ wordThreeNumbers: true,
+ wordOneSpecialChar: true,
+ wordTwoSpecialChar: true,
+ wordUpperLowerCombo: true,
+ wordLetterNumberCombo: true,
+ wordLetterNumberCharCombo: true
+ };
+ defaultOptions.rules.raisePower = 1.4;
+
+ defaultOptions.ui = {};
+ defaultOptions.ui.bootstrap2 = false;
+ defaultOptions.ui.showProgressBar = true;
+ defaultOptions.ui.showPopover = false;
+ defaultOptions.ui.showStatus = false;
+ defaultOptions.ui.spanError = function (options, key) {
+ "use strict";
+ var text = options.ui.errorMessages[key];
+ if (!text) { return ''; }
+ return '<span style="color: #d52929">' + text + '</span>';
+ };
+ defaultOptions.ui.errorMessages = {
+ wordLength: "Your password is too short",
+ wordNotEmail: "Do not use your email as your password",
+ wordSimilarToUsername: "Your password cannot contain your username",
+ wordTwoCharacterClasses: "Use different character classes",
+ wordRepetitions: "Too many repetitions",
+ wordSequences: "Your password contains sequences"
+ };
+ defaultOptions.ui.verdicts = ["Weak", "Normal", "Medium", "Strong", "Very Strong"];
+ defaultOptions.ui.showVerdicts = true;
+ defaultOptions.ui.showVerdictsInsideProgressBar = false;
+ defaultOptions.ui.showErrors = false;
+ defaultOptions.ui.container = undefined;
+ defaultOptions.ui.viewports = {
+ progress: undefined,
+ verdict: undefined,
+ errors: undefined
+ };
+ defaultOptions.ui.scores = [14, 26, 38, 50];
+
+// Source: src/ui.js
+
+
+
+
+ var ui = {};
+
+ (function ($, ui) {
+ "use strict";
+
+ var barClasses = ["danger", "warning", "success"],
+ statusClasses = ["error", "warning", "success"];
+
+ ui.getContainer = function (options, $el) {
+ var $container;
+
+ $container = $(options.ui.container);
+ if (!($container && $container.length === 1)) {
+ $container = $el.parent();
+ }
+ return $container;
+ };
+
+ ui.findElement = function ($container, viewport, cssSelector) {
+ if (viewport) {
+ return $container.find(viewport).find(cssSelector);
+ }
+ return $container.find(cssSelector);
+ };
+
+ ui.getUIElements = function (options, $el) {
+ var $container, result;
+
+ if (options.instances.viewports) {
+ return options.instances.viewports;
+ }
+
+ $container = ui.getContainer(options, $el);
+
+ result = {};
+ result.$progressbar = ui.findElement($container, options.ui.viewports.progress, "div.progress");
+ if (options.ui.showVerdictsInsideProgressBar) {
+ result.$verdict = result.$progressbar.find("span.password-verdict");
+ }
+
+ if (!options.ui.showPopover) {
+ if (!options.ui.showVerdictsInsideProgressBar) {
+ result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict");
+ }
+ result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list");
+ }
+
+ options.instances.viewports = result;
+ return result;
+ };
+
+ ui.initProgressBar = function (options, $el) {
+ var $container = ui.getContainer(options, $el),
+ progressbar = "<div class='progress'><div class='";
+
+ if (!options.ui.bootstrap2) {
+ progressbar += "progress-";
+ }
+ progressbar += "bar'>";
+ if (options.ui.showVerdictsInsideProgressBar) {
+ progressbar += "<span class='password-verdict'></span>";
+ }
+ progressbar += "</div></div>";
+
+ if (options.ui.viewports.progress) {
+ $container.find(options.ui.viewports.progress).append(progressbar);
+ } else {
+ $(progressbar).insertAfter($el);
+ }
+ };
+
+ ui.initHelper = function (options, $el, html, viewport) {
+ var $container = ui.getContainer(options, $el);
+ if (viewport) {
+ $container.find(viewport).append(html);
+ } else {
+ $(html).insertAfter($el);
+ }
+ };
+
+ ui.initVerdict = function (options, $el) {
+ ui.initHelper(options, $el, "<span class='password-verdict'></span>",
+ options.ui.viewports.verdict);
+ };
+
+ ui.initErrorList = function (options, $el) {
+ ui.initHelper(options, $el, "<ul class='error-list'></ul>",
+ options.ui.viewports.errors);
+ };
+
+ ui.initPopover = function (options, $el) {
+ $el.popover("destroy");
+ $el.popover({
+ html: true,
+ placement: "top",
+ trigger: "manual",
+ content: " "
+ });
+ };
+
+ ui.initUI = function (options, $el) {
+ if (options.ui.showPopover) {
+ ui.initPopover(options, $el);
+ } else {
+ if (options.ui.showErrors) { ui.initErrorList(options, $el); }
+ if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
+ ui.initVerdict(options, $el);
+ }
+ }
+ if (options.ui.showProgressBar) {
+ ui.initProgressBar(options, $el);
+ }
+ };
+
+ ui.possibleProgressBarClasses = ["danger", "warning", "success"];
+
+ ui.updateProgressBar = function (options, $el, cssClass, percentage) {
+ var $progressbar = ui.getUIElements(options, $el).$progressbar,
+ $bar = $progressbar.find(".progress-bar"),
+ cssPrefix = "progress-";
+
+ if (options.ui.bootstrap2) {
+ $bar = $progressbar.find(".bar");
+ cssPrefix = "";
+ }
+
+ $.each(ui.possibleProgressBarClasses, function (idx, value) {
+ $bar.removeClass(cssPrefix + "bar-" + value);
+ });
+ $bar.addClass(cssPrefix + "bar-" + barClasses[cssClass]);
+ $bar.css("width", percentage + '%');
+ };
+
+ ui.updateVerdict = function (options, $el, text) {
+ var $verdict = ui.getUIElements(options, $el).$verdict;
+ $verdict.text(text);
+ };
+
+ ui.updateErrors = function (options, $el) {
+ var $errors = ui.getUIElements(options, $el).$errors,
+ html = "";
+ $.each(options.instances.errors, function (idx, err) {
+ html += "<li>" + err + "</li>";
+ });
+ $errors.html(html);
+ };
+
+ ui.updatePopover = function (options, $el, verdictText) {
+ var popover = $el.data("bs.popover"),
+ html = "",
+ hide = true;
+
+ if (options.ui.showVerdicts &&
+ !options.ui.showVerdictsInsideProgressBar &&
+ verdictText.length > 0) {
+ html = "<h5><span class='password-verdict'>" + verdictText +
+ "</span></h5>";
+ hide = false;
+ }
+ if (options.ui.showErrors) {
+ html += "<div><ul class='error-list' style='margin-bottom: 0; margin-left: -20px'>";
+ $.each(options.instances.errors, function (idx, err) {
+ html += "<li>" + err + "</li>";
+ hide = false;
+ });
+ html += "</ul></div>";
+ }
+
+ if (hide) {
+ $el.popover("hide");
+ return;
+ }
+
+ if (options.ui.bootstrap2) { popover = $el.data("popover"); }
+
+ if (popover.$arrow && popover.$arrow.parents("body").length > 0) {
+ $el.find("+ .popover .popover-content").html(html);
+ } else {
+ // It's hidden
+ popover.options.content = html;
+ $el.popover("show");
+ }
+ };
+
+ ui.updateFieldStatus = function (options, $el, cssClass) {
+ var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group",
+ $container = $el.parents(targetClass).first();
+
+ $.each(statusClasses, function (idx, css) {
+ if (!options.ui.bootstrap2) { css = "has-" + css; }
+ $container.removeClass(css);
+ });
+
+ cssClass = statusClasses[cssClass];
+ if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; }
+ $container.addClass(cssClass);
+ };
+
+ ui.percentage = function (score, maximun) {
+ var result = Math.floor(100 * score / maximun);
+ result = result < 0 ? 0 : result;
+ result = result > 100 ? 100 : result;
+ return result;
+ };
+
+ ui.getVerdictAndCssClass = function (options, score) {
+ var cssClass, verdictText, level;
+
+ if (score <= 0) {
+ cssClass = 0;
+ level = -1;
+ verdictText = options.ui.verdicts[0];
+ } else if (score < options.ui.scores[0]) {
+ cssClass = 0;
+ level = 0;
+ verdictText = options.ui.verdicts[0];
+ } else if (score < options.ui.scores[1]) {
+ cssClass = 0;
+ level = 1;
+ verdictText = options.ui.verdicts[1];
+ } else if (score < options.ui.scores[2]) {
+ cssClass = 1;
+ level = 2;
+ verdictText = options.ui.verdicts[2];
+ } else if (score < options.ui.scores[3]) {
+ cssClass = 1;
+ level = 3;
+ verdictText = options.ui.verdicts[3];
+ } else {
+ cssClass = 2;
+ level = 4;
+ verdictText = options.ui.verdicts[4];
+ }
+
+ return [verdictText, cssClass, level];
+ };
+
+ ui.updateUI = function (options, $el, score) {
+ var cssClass, barPercentage, verdictText;
+
+ cssClass = ui.getVerdictAndCssClass(options, score);
+ verdictText = cssClass[0];
+ cssClass = cssClass[1];
+
+ if (options.ui.showProgressBar) {
+ barPercentage = ui.percentage(score, options.ui.scores[3]);
+ ui.updateProgressBar(options, $el, cssClass, barPercentage);
+ if (options.ui.showVerdictsInsideProgressBar) {
+ ui.updateVerdict(options, $el, verdictText);
+ }
+ }
+
+ if (options.ui.showStatus) {
+ ui.updateFieldStatus(options, $el, cssClass);
+ }
+
+ if (options.ui.showPopover) {
+ ui.updatePopover(options, $el, verdictText);
+ } else {
+ if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
+ ui.updateVerdict(options, $el, verdictText);
+ }
+ if (options.ui.showErrors) {
+ ui.updateErrors(options, $el);
+ }
+ }
+ };
+ }(jQuery, ui));
+
+// Source: src/methods.js
+
+
+
+
+ var methods = {};
+
+ (function ($, methods) {
+ "use strict";
+ var onKeyUp, applyToAll;
+
+ onKeyUp = function (event) {
+ var $el = $(event.target),
+ options = $el.data("pwstrength-bootstrap"),
+ word = $el.val(),
+ userInputs,
+ verdictText,
+ verdictLevel,
+ score;
+
+ if (options === undefined) { return; }
+
+ options.instances.errors = [];
+ if (options.common.zxcvbn) {
+ userInputs = [];
+ $.each(options.common.userInputs, function (idx, selector) {
+ userInputs.push($(selector).val());
+ });
+ userInputs.push($(options.common.usernameField).val());
+ score = zxcvbn(word, userInputs).entropy;
+ } else {
+ score = rulesEngine.executeRules(options, word);
+ }
+ ui.updateUI(options, $el, score);
+ verdictText = ui.getVerdictAndCssClass(options, score);
+ verdictLevel = verdictText[2];
+ verdictText = verdictText[0];
+
+ if (options.common.debug) { console.log(score + ' - ' + verdictText); }
+
+ if ($.isFunction(options.common.onKeyUp)) {
+ options.common.onKeyUp(event, {
+ score: score,
+ verdictText: verdictText,
+ verdictLevel: verdictLevel
+ });
+ }
+ };
+
+ methods.init = function (settings) {
+ this.each(function (idx, el) {
+ // Make it deep extend (first param) so it extends too the
+ // rules and other inside objects
+ var clonedDefaults = $.extend(true, {}, defaultOptions),
+ localOptions = $.extend(true, clonedDefaults, settings),
+ $el = $(el);
+
+ localOptions.instances = {};
+ $el.data("pwstrength-bootstrap", localOptions);
+ $el.on("keyup", onKeyUp);
+ $el.on("change", onKeyUp);
+ $el.on("onpaste", onKeyUp);
+
+ ui.initUI(localOptions, $el);
+ if ($.trim($el.val())) { // Not empty, calculate the strength
+ $el.trigger("keyup");
+ }
+
+ if ($.isFunction(localOptions.common.onLoad)) {
+ localOptions.common.onLoad();
+ }
+ });
+
+ return this;
+ };
+
+ methods.destroy = function () {
+ this.each(function (idx, el) {
+ var $el = $(el),
+ options = $el.data("pwstrength-bootstrap"),
+ elements = ui.getUIElements(options, $el);
+ elements.$progressbar.remove();
+ elements.$verdict.remove();
+ elements.$errors.remove();
+ $el.removeData("pwstrength-bootstrap");
+ });
+ };
+
+ methods.forceUpdate = function () {
+ this.each(function (idx, el) {
+ var event = { target: el };
+ onKeyUp(event);
+ });
+ };
+
+ methods.addRule = function (name, method, score, active) {
+ this.each(function (idx, el) {
+ var options = $(el).data("pwstrength-bootstrap");
+
+ options.rules.activated[name] = active;
+ options.rules.scores[name] = score;
+ options.rules.extra[name] = method;
+ });
+ };
+
+ applyToAll = function (rule, prop, value) {
+ this.each(function (idx, el) {
+ $(el).data("pwstrength-bootstrap").rules[prop][rule] = value;
+ });
+ };
+
+ methods.changeScore = function (rule, score) {
+ applyToAll.call(this, rule, "scores", score);
+ };
+
+ methods.ruleActive = function (rule, active) {
+ applyToAll.call(this, rule, "activated", active);
+ };
+
+ $.fn.pwstrength = function (method) {
+ var result;
+
+ if (methods[method]) {
+ result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else if (typeof method === "object" || !method) {
+ result = methods.init.apply(this, arguments);
+ } else {
+ $.error("Method " + method + " does not exist on jQuery.pwstrength-bootstrap");
+ }
+
+ return result;
+ };
+ }(jQuery, methods));
+}(jQuery)); \ No newline at end of file