[DRE-commits] [gitlab] 02/11: New upstream version 8.12.1+dfsg1

Praveen Arimbrathodiyil praveen at moszumanska.debian.org
Fri Sep 30 17:13:40 UTC 2016


This is an automated email from the git hooks/post-receive script.

praveen pushed a commit to branch master
in repository gitlab.

commit ab0093fc8a0ce1e1e418e613bf5191ddf9b70e19
Author: Praveen Arimbrathodiyil <praveen at debian.org>
Date:   Thu Sep 29 09:46:39 2016 +0530

    New upstream version 8.12.1+dfsg1
---
 .flayignore                                        |    1 +
 .gitignore                                         |    1 +
 .gitlab-ci.yml                                     |   27 +-
 .gitlab/issue_templates/Bug.md                     |   44 +
 .gitlab/issue_templates/Feature Proposal.md        |    7 +
 .gitlab/merge_request_templates/Documentation.md   |   14 +
 .haml-lint.yml                                     |  103 +
 .pkgr.yml                                          |   12 +
 .rubocop.yml                                       |    7 +-
 .rubocop_todo.yml                                  |  125 +-
 CHANGELOG                                          | 1964 +++-----------------
 CONTRIBUTING.md                                    |   84 +-
 GITLAB_SHELL_VERSION                               |    2 +-
 GITLAB_WORKHORSE_VERSION                           |    2 +-
 Gemfile                                            |   14 +-
 Gemfile.lock                                       |   42 +-
 PROCESS.md                                         |    2 +-
 README.md                                          |    3 +-
 VERSION                                            |    2 +-
 app/assets/images/icon-link.png                    |  Bin 729 -> 0 bytes
 app/assets/images/icon_anchor.svg                  |    1 +
 app/assets/javascripts/LabelManager.js             |    5 +
 app/assets/javascripts/abuse_reports.js.es6        |   38 +
 app/assets/javascripts/activities.js               |    4 +-
 app/assets/javascripts/api.js                      |   14 +-
 app/assets/javascripts/application.js              |   69 +-
 app/assets/javascripts/autosave.js                 |    6 +-
 app/assets/javascripts/awards_handler.js           |   70 +-
 app/assets/javascripts/behaviors/autosize.js       |    2 -
 .../javascripts/behaviors/details_behavior.js      |    6 +
 app/assets/javascripts/behaviors/quick_submit.js   |   20 +-
 app/assets/javascripts/behaviors/requires_input.js |   19 +-
 .../javascripts/behaviors/toggler_behavior.js      |    9 +-
 app/assets/javascripts/blob/blob_file_dropzone.js  |    3 +
 app/assets/javascripts/blob/template_selector.js   |   10 +
 app/assets/javascripts/blob_edit/edit_blob.js      |    2 +
 app/assets/javascripts/boards/boards_bundle.js.es6 |    7 +
 .../javascripts/boards/components/board.js.es6     |   15 -
 .../boards/components/board_list.js.es6            |   18 +-
 app/assets/javascripts/boards/models/list.js.es6   |   29 +-
 .../javascripts/boards/stores/boards_store.js.es6  |    3 +-
 .../javascripts/boards/test_utils/simulate_drag.js |    0
 .../boards/vue_resource_interceptor.js.es6         |    9 +-
 app/assets/javascripts/breakpoints.js              |    2 +
 app/assets/javascripts/build.js                    |   25 +-
 app/assets/javascripts/build_variables.js.es6      |    6 +
 app/assets/javascripts/commit/image-file.js        |    2 +
 app/assets/javascripts/commits.js                  |    1 +
 app/assets/javascripts/copy_to_clipboard.js        |    7 +-
 app/assets/javascripts/cycle-analytics.js.es6      |   93 +
 app/assets/javascripts/diff.js                     |    3 +
 app/assets/javascripts/dispatcher.js               |   13 +
 app/assets/javascripts/due_date_select.js          |    3 +
 app/assets/javascripts/extensions/jquery.js        |    2 +
 app/assets/javascripts/files_comment_button.js     |   15 +-
 app/assets/javascripts/gfm_auto_complete.js.es6    |   24 +
 app/assets/javascripts/gl_dropdown.js              |  121 +-
 app/assets/javascripts/gl_form.js                  |    6 +
 app/assets/javascripts/graphs/graphs_bundle.js     |    8 +-
 .../graphs/stat_graph_contributors_graph.js        |    1 +
 app/assets/javascripts/groups_select.js            |    1 +
 app/assets/javascripts/importer_status.js          |   17 +-
 app/assets/javascripts/issuable.js                 |   86 -
 app/assets/javascripts/issuable.js.es6             |  110 ++
 app/assets/javascripts/issuable_form.js            |   24 +-
 app/assets/javascripts/issue.js                    |   12 +-
 app/assets/javascripts/issues-bulk-assignment.js   |   10 +-
 app/assets/javascripts/labels.js                   |    3 +
 app/assets/javascripts/labels_select.js            |   11 +-
 app/assets/javascripts/layout_nav.js               |   25 +-
 app/assets/javascripts/lib/chart.js                |    1 -
 app/assets/javascripts/lib/cropper.js              |    1 -
 app/assets/javascripts/lib/d3.js                   |    1 -
 app/assets/javascripts/lib/raphael.js              |    5 -
 .../javascripts/lib/utils/datetime_utility.js      |    9 +
 .../lib/utils/emoji_aliases.js.coffee.erb          |    2 -
 .../javascripts/lib/utils/emoji_aliases.js.erb     |    6 +
 app/assets/javascripts/lib/utils/notify.js         |    5 +
 app/assets/javascripts/lib/utils/text_utility.js   |   10 +-
 app/assets/javascripts/lib/utils/url_utility.js    |    6 +
 app/assets/javascripts/line_highlighter.js         |   69 +-
 app/assets/javascripts/logo.js                     |   54 +-
 .../javascripts/merge_conflict_resolver.js.es6     |    4 +-
 app/assets/javascripts/merge_request.js            |   15 +-
 app/assets/javascripts/merge_request_tabs.js       |   88 +-
 app/assets/javascripts/merge_request_widget.js     |    8 +
 app/assets/javascripts/milestone.js                |    1 +
 app/assets/javascripts/milestone_select.js         |    1 +
 app/assets/javascripts/network/branch-graph.js     |   13 +
 app/assets/javascripts/network/network_bundle.js   |    7 +-
 app/assets/javascripts/notes.js                    |   81 +-
 app/assets/javascripts/preview_markdown.js         |   10 +
 app/assets/javascripts/profile/gl_crop.js          |   12 +-
 app/assets/javascripts/profile/profile.js          |    4 +
 app/assets/javascripts/profile/profile_bundle.js   |    1 -
 app/assets/javascripts/project.js                  |   19 +-
 app/assets/javascripts/project_find_file.js        |    9 +
 app/assets/javascripts/project_new.js              |   20 +-
 app/assets/javascripts/project_show.js             |    2 +
 app/assets/javascripts/projects_list.js            |    1 +
 .../javascripts/protected_branch_dropdown.js.es6   |    1 +
 app/assets/javascripts/right_sidebar.js            |    2 +-
 app/assets/javascripts/search_autocomplete.js      |   22 +
 app/assets/javascripts/shortcuts.js                |    1 +
 app/assets/javascripts/shortcuts_find_file.js      |    2 +
 app/assets/javascripts/shortcuts_issuable.js       |    6 +-
 app/assets/javascripts/shortcuts_navigation.js     |    3 +
 app/assets/javascripts/sidebar.js                  |   41 -
 app/assets/javascripts/sidebar.js.es6              |   93 +
 app/assets/javascripts/snippets_list.js.es6        |   11 +
 app/assets/javascripts/syntax_highlight.js         |   11 +
 app/assets/javascripts/todos.js                    |   34 +-
 app/assets/javascripts/tree.js                     |    3 +
 app/assets/javascripts/u2f/authenticate.js         |   18 +
 app/assets/javascripts/u2f/register.js             |    7 +
 app/assets/javascripts/user.js                     |   31 -
 app/assets/javascripts/user.js.es6                 |   34 +
 app/assets/javascripts/user_tabs.js                |   69 +
 app/assets/javascripts/users/calendar.js           |   74 +-
 app/assets/javascripts/users/users_bundle.js       |    1 -
 app/assets/javascripts/users_select.js             |    8 +
 app/assets/javascripts/zen_mode.js                 |   37 +-
 app/assets/stylesheets/framework.scss              |    1 +
 app/assets/stylesheets/framework/animations.scss   |   67 +-
 app/assets/stylesheets/framework/blocks.scss       |    4 +
 app/assets/stylesheets/framework/buttons.scss      |    6 +
 app/assets/stylesheets/framework/common.scss       |    6 +-
 app/assets/stylesheets/framework/dropdowns.scss    |   28 +
 app/assets/stylesheets/framework/files.scss        |   27 +-
 app/assets/stylesheets/framework/filters.scss      |    4 +
 app/assets/stylesheets/framework/forms.scss        |    1 -
 app/assets/stylesheets/framework/gfm.scss          |    2 +-
 app/assets/stylesheets/framework/header.scss       |   58 +-
 app/assets/stylesheets/framework/highlight.scss    |   15 +-
 app/assets/stylesheets/framework/lists.scss        |    4 +
 app/assets/stylesheets/framework/logo.scss         |  118 ++
 app/assets/stylesheets/framework/mixins.scss       |   66 +-
 app/assets/stylesheets/framework/mobile.scss       |    4 -
 app/assets/stylesheets/framework/modal.scss        |    1 -
 app/assets/stylesheets/framework/nav.scss          |   36 +-
 app/assets/stylesheets/framework/selects.scss      |    5 +-
 app/assets/stylesheets/framework/sidebar.scss      |   18 +-
 .../framework/tw_bootstrap_variables.scss          |    2 +-
 app/assets/stylesheets/framework/typography.scss   |   27 +-
 app/assets/stylesheets/framework/variables.scss    |  146 +-
 app/assets/stylesheets/pages/admin.scss            |   46 +-
 app/assets/stylesheets/pages/awards.scss           |    7 +-
 app/assets/stylesheets/pages/boards.scss           |  158 +-
 app/assets/stylesheets/pages/builds.scss           |   35 +-
 app/assets/stylesheets/pages/commits.scss          |   12 +-
 app/assets/stylesheets/pages/cycle_analytics.scss  |  144 ++
 app/assets/stylesheets/pages/diff.scss             |    9 +-
 app/assets/stylesheets/pages/environments.scss     |    5 +-
 app/assets/stylesheets/pages/events.scss           |    9 +-
 app/assets/stylesheets/pages/groups.scss           |   13 +
 app/assets/stylesheets/pages/import.scss           |   19 -
 app/assets/stylesheets/pages/issuable.scss         |   17 +-
 app/assets/stylesheets/pages/issues.scss           |   24 +-
 app/assets/stylesheets/pages/labels.scss           |    1 +
 app/assets/stylesheets/pages/merge_conflicts.scss  |    2 +-
 app/assets/stylesheets/pages/merge_requests.scss   |   45 +-
 app/assets/stylesheets/pages/notes.scss            |   16 +-
 app/assets/stylesheets/pages/pipelines.scss        |  194 +-
 app/assets/stylesheets/pages/projects.scss         |   63 +-
 app/assets/stylesheets/pages/search.scss           |    4 +-
 app/assets/stylesheets/pages/snippets.scss         |   42 +-
 app/assets/stylesheets/pages/status.scss           |    9 +
 app/assets/stylesheets/pages/todos.scss            |   49 +-
 app/assets/stylesheets/pages/tree.scss             |   25 +-
 app/assets/stylesheets/pages/xterm.scss            |    3 +
 app/controllers/admin/groups_controller.rb         |   10 +-
 app/controllers/application_controller.rb          |   37 +-
 app/controllers/ci/lints_controller.rb             |   11 +-
 .../concerns/authenticates_with_two_factor.rb      |    1 +
 app/controllers/concerns/creates_commit.rb         |    3 +-
 app/controllers/concerns/issuable_actions.rb       |   35 +-
 app/controllers/concerns/service_params.rb         |    2 +-
 app/controllers/concerns/toggle_award_emoji.rb     |   16 +-
 app/controllers/groups_controller.rb               |   12 +-
 app/controllers/import/base_controller.rb          |   17 +-
 app/controllers/import/bitbucket_controller.rb     |   23 +-
 app/controllers/import/github_controller.rb        |   18 +-
 app/controllers/import/gitlab_controller.rb        |   15 +-
 app/controllers/import/gitorious_controller.rb     |   47 -
 app/controllers/jwt_controller.rb                  |   36 +-
 app/controllers/namespaces_controller.rb           |    2 +-
 app/controllers/projects/application_controller.rb |    2 +-
 app/controllers/projects/artifacts_controller.rb   |   44 +-
 app/controllers/projects/avatars_controller.rb     |    2 +-
 app/controllers/projects/blob_controller.rb        |   10 +-
 .../projects/boards/issues_controller.rb           |   15 +-
 app/controllers/projects/branches_controller.rb    |    7 +
 app/controllers/projects/builds_controller.rb      |   12 +-
 .../projects/cycle_analytics_controller.rb         |   67 +
 app/controllers/projects/discussions_controller.rb |    2 +-
 .../projects/git_http_client_controller.rb         |   70 +-
 app/controllers/projects/git_http_controller.rb    |    5 +-
 app/controllers/projects/hooks_controller.rb       |    1 +
 app/controllers/projects/issues_controller.rb      |   45 +-
 app/controllers/projects/labels_controller.rb      |    2 +-
 app/controllers/projects/lfs_storage_controller.rb |   11 +-
 .../projects/merge_requests_controller.rb          |   60 +-
 app/controllers/projects/milestones_controller.rb  |    2 +-
 app/controllers/projects/pipelines_controller.rb   |    9 +-
 app/controllers/projects/services_controller.rb    |    5 +-
 app/controllers/projects/snippets_controller.rb    |    7 +-
 app/controllers/projects/tags_controller.rb        |    8 +-
 app/controllers/projects_controller.rb             |   27 +-
 app/controllers/search_controller.rb               |    4 +-
 app/controllers/sent_notifications_controller.rb   |    7 +
 app/controllers/snippets_controller.rb             |    3 +
 app/controllers/users_controller.rb                |    2 +-
 app/finders/issuable_finder.rb                     |   11 +-
 app/finders/issues_finder.rb                       |    4 +
 app/finders/merge_requests_finder.rb               |   10 +
 app/finders/move_to_project_finder.rb              |    6 +
 app/finders/pipelines_finder.rb                    |   32 +-
 app/finders/tags_finder.rb                         |   29 +
 app/finders/todos_finder.rb                        |    2 +-
 app/helpers/application_helper.rb                  |    4 +-
 app/helpers/avatars_helper.rb                      |    3 +-
 app/helpers/award_emoji_helper.rb                  |    9 +
 app/helpers/ci_status_helper.rb                    |   11 +-
 app/helpers/compare_helper.rb                      |    2 +-
 app/helpers/git_helper.rb                          |    4 +
 app/helpers/gitlab_routing_helper.rb               |   32 +
 app/helpers/groups_helper.rb                       |   25 +
 app/helpers/import_helper.rb                       |    5 +
 app/helpers/issuables_helper.rb                    |   13 +
 app/helpers/issues_helper.rb                       |   14 +-
 app/helpers/lfs_helper.rb                          |   16 +-
 app/helpers/merge_requests_helper.rb               |   12 +-
 app/helpers/namespaces_helper.rb                   |    5 +-
 app/helpers/nav_helper.rb                          |   19 +-
 app/helpers/notes_helper.rb                        |    4 +
 app/helpers/projects_helper.rb                     |   39 +-
 app/helpers/search_helper.rb                       |   37 +-
 app/helpers/sentry_helper.rb                       |    9 +
 app/helpers/services_helper.rb                     |    6 +-
 app/helpers/sidekiq_helper.rb                      |   19 +
 app/helpers/snippets_helper.rb                     |    6 +-
 app/helpers/tags_helper.rb                         |   10 +
 app/helpers/todos_helper.rb                        |   34 +-
 app/helpers/workhorse_helper.rb                    |    4 +
 app/mailers/base_mailer.rb                         |    2 +-
 app/mailers/notify.rb                              |    6 +
 app/models/ability.rb                              |  587 +-----
 app/models/application_setting.rb                  |    2 +-
 app/models/blob.rb                                 |   12 +
 app/models/ci/build.rb                             |   86 +-
 app/models/ci/pipeline.rb                          |   67 +-
 app/models/ci/runner.rb                            |    2 +-
 app/models/ci/variable.rb                          |    6 +-
 app/models/commit.rb                               |    9 -
 app/models/commit_range.rb                         |    7 -
 app/models/commit_status.rb                        |   18 +-
 app/models/concerns/awardable.rb                   |   20 +-
 app/models/concerns/has_status.rb                  |   94 +
 app/models/concerns/issuable.rb                    |   15 +
 app/models/concerns/note_on_diff.rb                |    4 +
 .../concerns/project_features_compatibility.rb     |   37 +
 app/models/concerns/statuseable.rb                 |   93 -
 app/models/concerns/taskable.rb                    |    4 +-
 app/models/cycle_analytics.rb                      |   97 +
 app/models/cycle_analytics/summary.rb              |   24 +
 app/models/deployment.rb                           |   34 +
 app/models/diff_note.rb                            |   22 +-
 app/models/discussion.rb                           |   39 +-
 app/models/environment.rb                          |   16 +
 app/models/event.rb                                |   29 +-
 app/models/group.rb                                |    7 +
 app/models/hooks/project_hook.rb                   |    1 +
 app/models/hooks/web_hook.rb                       |    1 +
 app/models/issue.rb                                |    4 +
 app/models/issue/metrics.rb                        |   21 +
 app/models/member.rb                               |   33 +-
 app/models/merge_request.rb                        |  162 +-
 app/models/merge_request/metrics.rb                |   11 +
 app/models/merge_request_diff.rb                   |  188 +-
 app/models/merge_requests_closing_issues.rb        |    7 +
 app/models/namespace.rb                            |    5 +
 app/models/project.rb                              |   91 +-
 app/models/project_feature.rb                      |   69 +
 app/models/project_services/hipchat_service.rb     |    2 +-
 app/models/project_services/slack_service.rb       |   72 +-
 .../slack_service/build_message.rb                 |    4 +-
 .../slack_service/pipeline_message.rb              |   79 +
 app/models/repository.rb                           |  225 ++-
 app/models/service.rb                              |    5 +-
 app/models/snippet.rb                              |    1 +
 app/models/user.rb                                 |    8 +-
 app/policies/base_policy.rb                        |  116 ++
 app/policies/ci/build_policy.rb                    |   13 +
 app/policies/ci/runner_policy.rb                   |   13 +
 app/policies/commit_status_policy.rb               |    5 +
 app/policies/deployment_policy.rb                  |    5 +
 app/policies/environment_policy.rb                 |    5 +
 app/policies/external_issue_policy.rb              |    5 +
 app/policies/global_policy.rb                      |    8 +
 app/policies/group_member_policy.rb                |   19 +
 app/policies/group_policy.rb                       |   45 +
 app/policies/issuable_policy.rb                    |   14 +
 app/policies/issue_policy.rb                       |   28 +
 app/policies/merge_request_policy.rb               |    3 +
 app/policies/namespace_policy.rb                   |   10 +
 app/policies/note_policy.rb                        |   19 +
 app/policies/personal_snippet_policy.rb            |   16 +
 app/policies/project_member_policy.rb              |   22 +
 app/policies/project_policy.rb                     |  235 +++
 app/policies/project_snippet_policy.rb             |   20 +
 app/policies/user_policy.rb                        |   11 +
 .../container_registry_authentication_service.rb   |   32 +-
 app/services/base_service.rb                       |    6 +-
 app/services/boards/lists/create_service.rb        |    9 +-
 app/services/ci/process_pipeline_service.rb        |   20 +-
 app/services/ci/register_build_service.rb          |    8 +-
 app/services/ci/web_hook_service.rb                |   35 -
 app/services/commits/change_service.rb             |   20 +-
 app/services/commits/cherry_pick_service.rb        |   14 +-
 app/services/commits/revert_service.rb             |   14 +-
 app/services/create_deployment_service.rb          |   44 +-
 app/services/files/base_service.rb                 |    2 +
 app/services/files/create_dir_service.rb           |    2 +-
 app/services/files/create_service.rb               |    2 +-
 app/services/files/delete_service.rb               |    2 +-
 app/services/files/update_service.rb               |    4 +-
 app/services/git_push_service.rb                   |   10 +-
 app/services/issuable/bulk_update_service.rb       |   26 +
 app/services/issuable_base_service.rb              |   25 +-
 app/services/issues/base_service.rb                |    7 +-
 app/services/issues/bulk_update_service.rb         |   25 -
 app/services/merge_requests/build_service.rb       |    2 +-
 app/services/merge_requests/create_service.rb      |    1 +
 app/services/merge_requests/get_urls_service.rb    |    2 +-
 app/services/merge_requests/refresh_service.rb     |    9 +
 app/services/merge_requests/resolve_service.rb     |   21 +-
 app/services/merge_requests/update_service.rb      |    8 +
 app/services/milestones/create_service.rb          |    2 +-
 app/services/notes/slash_commands_service.rb       |   21 +-
 app/services/projects/create_service.rb            |    4 +-
 app/services/projects/destroy_service.rb           |    2 +
 app/services/projects/fork_service.rb              |    4 +-
 app/services/projects/housekeeping_service.rb      |   12 +-
 app/services/system_note_service.rb                |   10 +-
 app/services/todo_service.rb                       |   23 +-
 .../admin/abuse_reports/_abuse_report.html.haml    |   17 +-
 app/views/admin/abuse_reports/index.html.haml      |   33 +-
 app/views/admin/appearances/_form.html.haml        |    2 +-
 .../admin/application_settings/_form.html.haml     |   54 +-
 app/views/admin/background_jobs/_head.html.haml    |   46 +-
 app/views/admin/background_jobs/show.html.haml     |    8 +-
 app/views/admin/builds/_build.html.haml            |   77 -
 app/views/admin/builds/index.html.haml             |   43 +-
 app/views/admin/dashboard/_head.html.haml          |   54 +-
 app/views/admin/groups/_form.html.haml             |    2 +
 app/views/admin/groups/show.html.haml              |    6 +
 app/views/admin/projects/show.html.haml            |    6 +
 app/views/award_emoji/_awards_block.html.haml      |    2 +-
 app/views/ci/lints/show.html.haml                  |    3 +
 app/views/dashboard/todos/_todo.html.haml          |   15 +-
 app/views/dashboard/todos/index.html.haml          |   38 +-
 app/views/devise/sessions/two_factor.html.haml     |    3 +-
 app/views/discussions/_jump_to_next.html.haml      |    2 +-
 app/views/discussions/_notes.html.haml             |    3 +-
 app/views/events/_event.html.haml                  |    2 +-
 app/views/explore/groups/index.html.haml           |    2 +-
 app/views/groups/_group_lfs_settings.html.haml     |   11 +
 app/views/groups/edit.html.haml                    |    2 +
 app/views/groups/group_members/index.html.haml     |    2 +-
 app/views/groups/group_members/update.js.haml      |    2 +-
 app/views/groups/show.html.haml                    |    2 +-
 app/views/help/_shortcuts.html.haml                |  553 +++---
 app/views/help/ui.html.haml                        |    2 +-
 app/views/import/base/create.js.haml               |   21 +-
 app/views/import/base/unauthorized.js.haml         |   14 +
 app/views/import/bitbucket/deploy_key.js.haml      |    3 +
 app/views/import/bitbucket/status.html.haml        |    2 +-
 app/views/import/github/status.html.haml           |   12 +-
 app/views/import/gitlab/status.html.haml           |    2 +-
 app/views/import/gitorious/status.html.haml        |   54 -
 app/views/layouts/_page.html.haml                  |    2 +-
 app/views/layouts/nav/_group.html.haml             |    2 +-
 app/views/layouts/nav/_group_settings.html.haml    |   38 +-
 app/views/layouts/nav/_project.html.haml           |    6 +-
 app/views/layouts/nav/_project_settings.html.haml  |    2 +-
 app/views/layouts/notify.html.haml                 |    4 +-
 app/views/layouts/project.html.haml                |    3 -
 app/views/profiles/keys/index.html.haml            |    1 +
 app/views/profiles/update_username.js.haml         |    2 +-
 .../projects/_merge_request_settings.html.haml     |   25 +-
 app/views/projects/_zen.html.haml                  |    3 +
 app/views/projects/blame/show.html.haml            |    3 +-
 app/views/projects/blob/_editor.html.haml          |    2 +-
 .../projects/boards/components/_board.html.haml    |   14 +-
 app/views/projects/branches/_branch.html.haml      |   11 +-
 app/views/projects/builds/_sidebar.html.haml       |   80 +-
 app/views/projects/builds/_table.html.haml         |   24 +
 app/views/projects/builds/index.html.haml          |   47 +-
 app/views/projects/buttons/_download.html.haml     |   46 +-
 app/views/projects/buttons/_fork.html.haml         |    4 +-
 app/views/projects/buttons/_star.html.haml         |    6 +-
 app/views/projects/ci/builds/_build.html.haml      |   40 +-
 .../projects/ci/builds/_build_pipeline.html.haml   |   24 +-
 .../projects/ci/pipelines/_pipeline.html.haml      |   16 +-
 app/views/projects/commit/_change.html.haml        |    4 +-
 app/views/projects/commit/_ci_stage.html.haml      |    4 +-
 app/views/projects/commit/_pipeline.html.haml      |    3 +-
 .../projects/commit/_pipeline_stage.html.haml      |   14 +
 .../commit/_pipeline_status_group.html.haml        |   11 +
 app/views/projects/commit/_pipelines_list.haml     |    5 +-
 app/views/projects/commits/_commit.html.haml       |    2 +-
 app/views/projects/commits/_head.html.haml         |    5 +-
 app/views/projects/cycle_analytics/show.html.haml  |   59 +
 app/views/projects/deployments/_actions.haml       |    4 +-
 app/views/projects/diffs/_file.html.haml           |    2 +-
 app/views/projects/edit.html.haml                  |   87 +-
 app/views/projects/forks/index.html.haml           |    4 +-
 .../_generic_commit_status_pipeline.html.haml      |   16 +-
 app/views/projects/graphs/_head.html.haml          |   32 +-
 app/views/projects/hooks/_project_hook.html.haml   |    2 +-
 app/views/projects/issues/_head.html.haml          |   54 +-
 app/views/projects/issues/_issue.html.haml         |    6 +-
 app/views/projects/issues/_issues.html.haml        |    2 +-
 .../projects/issues/_merge_requests.html.haml      |    2 +-
 app/views/projects/issues/_new_branch.html.haml    |   19 +-
 .../projects/issues/_related_branches.html.haml    |    4 +-
 app/views/projects/issues/index.html.haml          |    2 +
 app/views/projects/issues/show.html.haml           |    2 +-
 .../projects/merge_requests/_discussion.html.haml  |    2 +-
 .../merge_requests/_merge_request.html.haml        |    6 +-
 .../merge_requests/_merge_requests.html.haml       |    2 +-
 app/views/projects/merge_requests/_show.html.haml  |   33 +-
 app/views/projects/merge_requests/index.html.haml  |    2 +
 .../projects/merge_requests/show/_diffs.html.haml  |    1 +
 .../merge_requests/show/_how_to_merge.html.haml    |    7 +-
 .../merge_requests/show/_mr_title.html.haml        |    6 +-
 .../merge_requests/show/_versions.html.haml        |   74 +
 .../merge_requests/widget/_heading.html.haml       |   25 +-
 app/views/projects/new.html.haml                   |    7 +-
 app/views/projects/notes/_form.html.haml           |    6 +-
 app/views/projects/notes/_note.html.haml           |    7 +-
 app/views/projects/pipelines/_head.html.haml       |   42 +-
 app/views/projects/pipelines/_info.html.haml       |    2 +
 app/views/projects/pipelines/index.html.haml       |    5 +-
 app/views/projects/project_members/update.js.haml  |    2 +-
 app/views/projects/refs/logs_tree.js.haml          |    4 +-
 .../repositories/_download_archive.html.haml       |   37 -
 app/views/projects/show.html.haml                  |    2 +-
 app/views/projects/snippets/_actions.html.haml     |   14 +-
 app/views/projects/snippets/show.html.haml         |   12 +-
 app/views/projects/tags/_download.html.haml        |   14 -
 app/views/projects/tags/_tag.html.haml             |    3 +-
 app/views/projects/tags/index.html.haml            |   17 +-
 app/views/projects/tags/show.html.haml             |    3 +-
 app/views/projects/tree/_blob_item.html.haml       |    6 +-
 app/views/projects/tree/_readme.html.haml          |    2 +-
 app/views/projects/tree/_tree_content.html.haml    |    9 +-
 app/views/projects/tree/_tree_item.html.haml       |    6 +-
 app/views/projects/tree/show.html.haml             |    3 +-
 app/views/projects/triggers/index.html.haml        |  130 +-
 app/views/projects/variables/_table.html.haml      |    2 +-
 app/views/projects/wikis/_nav.html.haml            |   22 +-
 app/views/search/_results.html.haml                |   16 +-
 app/views/search/results/_blob.html.haml           |    2 +-
 app/views/search/results/_commit.html.haml         |    3 +-
 app/views/search/results/_wiki_blob.html.haml      |    2 +-
 app/views/sent_notifications/unsubscribe.html.haml |   19 +
 app/views/shared/_logo.svg                         |   16 +-
 app/views/shared/_nav_scroll.html.haml             |    4 +
 app/views/shared/_ref_switcher.html.haml           |    2 +-
 app/views/shared/_visibility_level.html.haml       |    2 +-
 app/views/shared/builds/_tabs.html.haml            |   24 +
 .../shared/icons/_icon_cycle_analytics_splash.svg  |    1 +
 app/views/shared/icons/_icon_fork.svg              |    2 +-
 app/views/shared/icons/_icon_play.svg              |    4 +-
 app/views/shared/icons/_icon_status_created.svg    |    1 +
 app/views/shared/issuable/_filter.html.haml        |   42 +-
 app/views/shared/issuable/_form.html.haml          |   17 +-
 app/views/shared/issuable/_search_form.html.haml   |    4 +-
 app/views/shared/issuable/_sidebar.html.haml       |    2 +-
 app/views/shared/projects/_project.html.haml       |    4 +-
 app/views/shared/snippets/_header.html.haml        |    8 +-
 app/views/shared/snippets/_snippet.html.haml       |   21 +-
 app/views/shared/web_hooks/_form.html.haml         |    7 +
 app/views/snippets/_actions.html.haml              |   14 +-
 app/views/snippets/_snippets.html.haml             |   16 +-
 app/views/snippets/show.html.haml                  |   21 +-
 app/views/u2f/_authenticate.html.haml              |    2 +
 app/views/users/calendar.html.haml                 |    4 +-
 app/views/users/show.html.haml                     |    2 +-
 app/workers/prune_old_events_worker.rb             |   17 +
 changelogs/archive.md                              | 1810 ++++++++++++++++++
 changelogs/unreleased/.gitkeep                     |    0
 config/initializers/1_settings.rb                  |    5 +-
 config/initializers/ar_monkey_patch.rb             |   57 +
 config/initializers/gitlab_workhorse_secret.rb     |    8 +
 config/initializers/metrics.rb                     |    3 +-
 config/initializers/mime_types.rb                  |    8 +-
 config/initializers/sentry.rb                      |    1 +
 config/routes.rb                                   |   35 +-
 db/fixtures/development/14_builds.rb               |  138 --
 db/fixtures/development/14_pipelines.rb            |  157 ++
 db/fixtures/development/17_cycle_analytics.rb      |  246 +++
 db/migrate/20140502125220_migrate_repo_size.rb     |    5 +-
 db/migrate/20160707104333_add_lock_to_issuables.rb |   18 +
 ...0160725104020_merge_request_diff_remove_uniq.rb |   35 +
 .../20160725104452_merge_request_diff_add_index.rb |   17 +
 db/migrate/20160808085531_add_token_to_build.rb    |   10 +
 .../20160808085602_add_index_for_build_token.rb    |   12 +
 .../20160823081327_change_merge_error_to_text.rb   |   10 +
 .../20160823213309_add_lfs_enabled_to_projects.rb  |   29 +
 db/migrate/20160824103857_drop_unused_ci_tables.rb |   11 +
 .../20160824124900_add_table_issue_metrics.rb      |   37 +
 ...160825052008_add_table_merge_request_metrics.rb |   38 +
 ...827011312_ensure_lock_version_has_no_default.rb |   16 +
 ..._add_confidential_issues_events_to_web_hooks.rb |   15 +
 ...2_add_confidential_issues_events_to_services.rb |   15 +
 .../20160830232601_change_lock_version_not_null.rb |   13 +
 .../20160831214002_create_project_features.rb      |   16 +
 .../20160831214543_migrate_project_features.rb     |   44 +
 ...223750_remove_features_enabled_from_projects.rb |   29 +
 ...3_set_confidential_issues_events_on_webhooks.rb |   15 +
 ...20160901213340_add_lfs_enabled_to_namespaces.rb |   12 +
 ...op_gitorious_field_from_application_settings.rb |   39 +
 ...7131111_add_environment_type_to_environments.rb |    9 +
 ...160913162434_remove_projects_pushes_since_gc.rb |   19 +
 .../20160913212128_change_artifacts_size_column.rb |   15 +
 ...5042921_create_merge_requests_closing_issues.rb |   34 +
 db/schema.rb                                       |  173 +-
 doc/README.md                                      |    5 +-
 doc/administration/container_registry.md           |    4 +-
 doc/administration/issue_closing_pattern.md        |   49 +
 doc/api/README.md                                  |   24 +-
 doc/api/award_emoji.md                             |   15 +-
 doc/api/broadcast_messages.md                      |  158 ++
 doc/api/ci/builds.md                               |    9 +
 doc/api/ci/lint.md                                 |   49 +
 doc/api/commits.md                                 |   14 +-
 doc/api/groups.md                                  |   18 +-
 doc/api/issues.md                                  |   51 +-
 doc/api/members.md                                 |    4 +-
 doc/api/merge_requests.md                          |  155 +-
 doc/api/notes.md                                   |    6 +-
 doc/api/notification_settings.md                   |  169 ++
 doc/api/project_snippets.md                        |    3 +-
 doc/api/projects.md                                |  126 +-
 doc/api/repository_files.md                        |   12 +-
 doc/api/settings.md                                |    2 +-
 doc/api/users.md                                   |    3 +-
 doc/ci/README.md                                   |    2 +
 doc/ci/examples/README.md                          |   14 +-
 doc/ci/pipelines.md                                |    2 +-
 doc/ci/quick_start/README.md                       |    3 +-
 doc/ci/ssh_keys/README.md                          |    3 +-
 doc/ci/triggers/README.md                          |    4 +
 doc/ci/triggers/img/builds_page.png                |  Bin 33324 -> 76181 bytes
 doc/ci/triggers/img/trigger_single_build.png       |  Bin 2387 -> 21152 bytes
 doc/ci/triggers/img/trigger_variables.png          |  Bin 4433 -> 9315 bytes
 doc/ci/triggers/img/triggers_page.png              |  Bin 12943 -> 12002 bytes
 doc/ci/variables/README.md                         |   10 +-
 doc/ci/yaml/README.md                              |  155 +-
 doc/container_registry/README.md                   |    6 +-
 doc/customization/issue_closing.md                 |   41 +-
 doc/development/README.md                          |    2 +
 doc/development/doc_styleguide.md                  |   55 +-
 doc/development/instrumentation.md                 |   15 +
 .../merge_request_performance_guidelines.md        |  171 ++
 doc/development/migration_style_guide.md           |   22 +
 doc/development/newlines_styleguide.md             |    2 +-
 doc/gitlab-basics/add-file.md                      |    4 -
 doc/gitlab-basics/create-issue.md                  |    4 +-
 doc/install/installation.md                        |    9 +-
 doc/install/requirements.md                        |   34 +-
 doc/integration/bitbucket.md                       |  215 ++-
 doc/integration/img/bitbucket_oauth_keys.png       |  Bin 0 -> 12073 bytes
 .../img/bitbucket_oauth_settings_page.png          |  Bin 0 -> 82818 bytes
 doc/integration/omniauth.md                        |    4 +-
 doc/intro/README.md                                |    8 +-
 .../corporate_contributor_license_agreement.md     |    4 +-
 doc/raketasks/backup_restore.md                    |    6 +
 doc/update/8.10-to-8.11.md                         |    2 +-
 doc/update/8.11-to-8.12.md                         |  199 ++
 doc/user/account/security.md                       |    3 +
 doc/user/account/two_factor_authentication.md      |   68 +
 doc/user/markdown.md                               |   20 +-
 doc/user/permissions.md                            |   41 +-
 doc/user/project/builds/artifacts.md               |   32 +
 .../builds/img/build_latest_artifacts_browser.png  |  Bin 0 -> 26617 bytes
 doc/user/project/cycle_analytics.md                |  114 ++
 .../project/img/cycle_analytics_landing_page.png   |  Bin 0 -> 58203 bytes
 doc/user/project/issues/automatic_issue_closing.md |   55 +
 doc/user/project/koding.md                         |    2 +-
 doc/user/project/merge_requests.md                 |  169 ++
 .../authorization_for_merge_requests.md            |   56 +
 .../project/merge_requests}/cherry_pick_changes.md |    0
 .../img/cherry_pick_changes_commit.png             |  Bin
 .../img/cherry_pick_changes_commit_modal.png       |  Bin
 .../merge_requests}/img/cherry_pick_changes_mr.png |  Bin
 .../img/cherry_pick_changes_mr_modal.png           |  Bin
 .../project/merge_requests/img}/commit_compare.png |  Bin
 .../merge_requests/img/merge_request_diff.png      |  Bin 0 -> 69394 bytes
 .../img/merge_when_build_succeeds_enable.png}      |  Bin
 ...ge_when_build_succeeds_only_if_succeeds_msg.png |  Bin 0 -> 11136 bytes
 ...n_build_succeeds_only_if_succeeds_settings.png} |  Bin
 .../img/merge_when_build_succeeds_status.png}      |  Bin
 .../merge_requests}/img/revert_changes_commit.png  |  Bin
 .../img/revert_changes_commit_modal.png            |  Bin
 .../merge_requests}/img/revert_changes_mr.png      |  Bin
 .../img/revert_changes_mr_modal.png                |  Bin
 .../merge_requests/img/versions-compare.png        |  Bin 0 -> 68722 bytes
 .../merge_requests/img/versions-dropdown.png       |  Bin 0 -> 60587 bytes
 doc/user/project/merge_requests/img/versions.png   |  Bin 0 -> 171413 bytes
 .../img/wip_blocked_accept_button.png}             |  Bin
 .../merge_requests/img/wip_mark_as_wip.png}        |  Bin
 .../merge_requests/img/wip_unmark_as_wip.png}      |  Bin
 .../merge_requests/merge_when_build_succeeds.md    |   46 +
 .../project/merge_requests}/revert_changes.md      |    0
 doc/user/project/merge_requests/versions.md        |   32 +
 .../work_in_progress_merge_requests.md             |   17 +
 doc/user/project/new_ci_build_permissions_model.md |  289 +++
 .../img/web_editor_new_branch_dropdown.png         |  Bin
 .../repository}/img/web_editor_new_branch_page.png |  Bin
 .../img/web_editor_new_directory_dialog.png        |  Bin
 .../img/web_editor_new_directory_dropdown.png      |  Bin
 .../img/web_editor_new_file_dropdown.png           |  Bin
 .../repository}/img/web_editor_new_file_editor.png |  Bin
 .../repository}/img/web_editor_new_push_widget.png |  Bin
 .../img/web_editor_new_tag_dropdown.png            |  Bin
 .../repository}/img/web_editor_new_tag_page.png    |  Bin
 .../img/web_editor_start_new_merge_request.png     |  Bin
 .../img/web_editor_template_dropdown_buttons.png   |  Bin 0 -> 14131 bytes
 .../web_editor_template_dropdown_first_file.png    |  Bin 0 -> 25748 bytes
 .../web_editor_template_dropdown_mit_license.png   |  Bin 0 -> 85413 bytes
 .../img/web_editor_upload_file_dialog.png          |  Bin
 .../img/web_editor_upload_file_dropdown.png        |  Bin
 doc/user/project/repository/web_editor.md          |  175 ++
 doc/user/project/settings/import_export.md         |   18 +-
 doc/user/project/slash_commands.md                 |    2 +-
 doc/workflow/README.md                             |   19 +-
 doc/workflow/authorization_for_merge_requests.md   |   41 +-
 doc/workflow/cherry_pick_changes.md                |   53 +-
 doc/workflow/gitlab_flow.md                        |    4 +-
 .../img/import_projects_from_github_importer.png   |  Bin 22711 -> 65288 bytes
 ...mport_projects_from_github_new_project_page.png |  Bin 13668 -> 24911 bytes
 ...ort_projects_from_github_select_auth_method.png |  Bin 0 -> 42043 bytes
 .../importing/import_projects_from_github.md       |  129 +-
 doc/workflow/lfs/lfs_administration.md             |    4 +-
 .../lfs/manage_large_binaries_with_git_lfs.md      |    8 +
 doc/workflow/merge_requests.md                     |   64 +-
 doc/workflow/merge_requests/merge_request_diff.png |  Bin 103239 -> 0 bytes
 .../merge_request_diff_without_whitespace.png      |  Bin 71896 -> 0 bytes
 doc/workflow/merge_when_build_succeeds.md          |   16 +-
 doc/workflow/project_features.md                   |   10 +-
 doc/workflow/revert_changes.md                     |   65 +-
 doc/workflow/web_editor.md                         |  152 +-
 doc/workflow/wip_merge_requests.md                 |   14 +-
 features/dashboard/todos.feature                   |   20 -
 features/project/merge_requests.feature            |    4 +-
 features/steps/admin/settings.rb                   |    1 +
 features/steps/dashboard/new_project.rb            |    1 -
 features/steps/dashboard/todos.rb                  |   24 +-
 features/steps/project/issues/award_emoji.rb       |    2 +-
 features/steps/project/issues/issues.rb            |    2 +-
 features/steps/project/merge_requests.rb           |   12 +-
 features/steps/project/project.rb                  |    2 +-
 features/steps/shared/issuable.rb                  |    2 +-
 features/steps/shared/project.rb                   |    6 +-
 lib/api/access_requests.rb                         |    2 +-
 lib/api/api.rb                                     |   16 +-
 lib/api/api_guard.rb                               |   56 +-
 lib/api/award_emoji.rb                             |   33 +-
 lib/api/broadcast_messages.rb                      |   99 +
 lib/api/commit_statuses.rb                         |   56 +-
 lib/api/entities.rb                                |   82 +-
 lib/api/files.rb                                   |   12 +-
 lib/api/groups.rb                                  |   28 +-
 lib/api/helpers.rb                                 |   71 +-
 lib/api/internal.rb                                |   54 +-
 lib/api/issues.rb                                  |   46 +-
 lib/api/lint.rb                                    |   21 +
 lib/api/members.rb                                 |    8 +-
 lib/api/merge_request_diffs.rb                     |   45 +
 lib/api/notes.rb                                   |    8 +-
 lib/api/notification_settings.rb                   |   97 +
 lib/api/pipelines.rb                               |    5 +-
 lib/api/project_hooks.rb                           |    2 +
 lib/api/projects.rb                                |  123 +-
 lib/api/users.rb                                   |    2 +-
 lib/backup/repository.rb                           |    2 +-
 lib/banzai/filter/abstract_reference_filter.rb     |   10 +-
 lib/banzai/filter/commit_range_reference_filter.rb |    2 +-
 lib/banzai/filter/commit_reference_filter.rb       |    4 -
 lib/banzai/filter/label_reference_filter.rb        |    5 +
 lib/banzai/filter/milestone_reference_filter.rb    |    4 +
 lib/banzai/filter/reference_filter.rb              |    2 +-
 lib/banzai/filter/sanitization_filter.rb           |   64 +-
 lib/banzai/filter/wiki_link_filter/rewriter.rb     |    1 +
 lib/banzai/reference_parser/base_parser.rb         |    2 +-
 lib/ci/api/api.rb                                  |   12 +-
 lib/ci/api/builds.rb                               |    8 +-
 lib/ci/api/entities.rb                             |    9 +
 lib/ci/api/helpers.rb                              |   30 +-
 lib/ci/gitlab_ci_yaml_processor.rb                 |   29 +-
 lib/ci/mask_secret.rb                              |   10 +
 lib/expand_variables.rb                            |   17 +
 lib/extracts_path.rb                               |   13 +-
 lib/gitlab/auth.rb                                 |  123 +-
 lib/gitlab/auth/result.rb                          |   21 +
 lib/gitlab/backend/shell.rb                        |   15 +-
 lib/gitlab/badge/coverage/report.rb                |    4 +-
 lib/gitlab/bitbucket_import/importer.rb            |    4 +-
 lib/gitlab/checks/change_access.rb                 |    1 +
 lib/gitlab/ci/config.rb                            |    2 +-
 lib/gitlab/ci/config/node/configurable.rb          |   10 +-
 lib/gitlab/ci/config/node/entry.rb                 |   14 +-
 lib/gitlab/ci/config/node/environment.rb           |   68 +
 lib/gitlab/ci/config/node/factory.rb               |    8 +-
 lib/gitlab/ci/config/node/global.rb                |   14 +-
 lib/gitlab/ci/config/node/hidden.rb                |   22 +
 lib/gitlab/ci/config/node/hidden_job.rb            |   23 -
 lib/gitlab/ci/config/node/job.rb                   |   81 +-
 lib/gitlab/ci/config/node/jobs.rb                  |   28 +-
 lib/gitlab/ci/config/node/null.rb                  |   34 -
 lib/gitlab/ci/config/node/undefined.rb             |   27 +-
 lib/gitlab/ci/config/node/unspecified.rb           |   19 +
 lib/gitlab/ci/pipeline_duration.rb                 |  141 ++
 lib/gitlab/conflict/parser.rb                      |    2 +-
 lib/gitlab/contributions_calendar.rb               |   18 +-
 lib/gitlab/current_settings.rb                     |    2 +-
 lib/gitlab/database/date_time.rb                   |   27 +
 lib/gitlab/database/median.rb                      |  112 ++
 lib/gitlab/database/migration_helpers.rb           |   10 +-
 lib/gitlab/diff/file_collection/merge_request.rb   |   73 -
 .../diff/file_collection/merge_request_diff.rb     |   73 +
 lib/gitlab/git.rb                                  |   10 +
 lib/gitlab/git/hook.rb                             |   12 +-
 lib/gitlab/git_access.rb                           |   19 +-
 lib/gitlab/github_import/base_formatter.rb         |    7 +-
 lib/gitlab/github_import/comment_formatter.rb      |    8 +-
 lib/gitlab/github_import/importer.rb               |   32 +-
 lib/gitlab/github_import/issue_formatter.rb        |   16 +-
 lib/gitlab/github_import/label_formatter.rb        |    6 +
 lib/gitlab/github_import/milestone_formatter.rb    |   36 +-
 lib/gitlab/github_import/project_creator.rb        |   21 +-
 lib/gitlab/github_import/pull_request_formatter.rb |   21 +-
 lib/gitlab/github_import/release_formatter.rb      |   23 +
 lib/gitlab/gitlab_import/importer.rb               |    5 +-
 lib/gitlab/gitorious_import.rb                     |    5 -
 lib/gitlab/gitorious_import/client.rb              |   29 -
 lib/gitlab/gitorious_import/project_creator.rb     |   27 -
 lib/gitlab/gitorious_import/repository.rb          |   35 -
 lib/gitlab/gon_helper.rb                           |    1 -
 lib/gitlab/import_export.rb                        |    3 +-
 lib/gitlab/import_export/import_export.yml         |   14 +-
 lib/gitlab/import_export/relation_factory.rb       |   28 +-
 lib/gitlab/import_export/repo_restorer.rb          |    4 -
 lib/gitlab/import_export/version_checker.rb        |    4 +-
 lib/gitlab/import_sources.rb                       |   13 +-
 lib/gitlab/ldap/adapter.rb                         |   58 +-
 lib/gitlab/lfs_token.rb                            |   54 +
 lib/gitlab/metrics/rack_middleware.rb              |   22 +-
 lib/gitlab/popen.rb                                |   16 +-
 lib/gitlab/project_search_results.rb               |    5 -
 lib/gitlab/regex.rb                                |    4 +-
 lib/gitlab/search_results.rb                       |    9 -
 lib/gitlab/sentry.rb                               |   27 +
 lib/gitlab/snippet_search_results.rb               |    4 -
 lib/gitlab/url_builder.rb                          |    2 +
 lib/gitlab/workhorse.rb                            |   52 +-
 lib/tasks/haml-lint.rake                           |    5 +
 spec/controllers/autocomplete_controller_spec.rb   |   50 +
 .../import/bitbucket_controller_spec.rb            |   41 +-
 spec/controllers/import/github_controller_spec.rb  |   77 +-
 spec/controllers/import/gitlab_controller_spec.rb  |   41 +-
 .../import/gitorious_controller_spec.rb            |   69 -
 .../projects/boards/issues_controller_spec.rb      |    4 +-
 .../projects/boards/lists_controller_spec.rb       |   43 +-
 .../controllers/projects/boards_controller_spec.rb |    4 +-
 .../projects/discussions_controller_spec.rb        |    2 +-
 .../controllers/projects/issues_controller_spec.rb |    6 +
 .../projects/merge_requests_controller_spec.rb     |   35 +
 .../projects/services_controller_spec.rb           |   16 +
 .../projects/snippets_controller_spec.rb           |    2 +-
 spec/controllers/projects_controller_spec.rb       |   19 +
 .../sent_notifications_controller_spec.rb          |  109 +-
 spec/controllers/sessions_controller_spec.rb       |   23 +
 spec/controllers/snippets_controller_spec.rb       |   33 +-
 spec/factories/ci/runner_projects.rb               |   11 -
 spec/factories/ci/runners.rb                       |   23 +-
 spec/factories/ci/variables.rb                     |   14 -
 spec/factories/deployments.rb                      |    3 +-
 spec/factories/events.rb                           |    5 +-
 spec/factories/group_members.rb                    |   13 -
 spec/factories/issues.rb                           |    4 +
 spec/factories/notes.rb                            |    5 +
 spec/factories/project_hooks.rb                    |    1 +
 spec/factories/projects.rb                         |   21 +-
 spec/features/boards/boards_spec.rb                |  286 ++-
 spec/features/boards/keyboard_shortcut_spec.rb     |   24 +
 spec/features/builds_spec.rb                       |  326 ----
 spec/features/calendar_spec.rb                     |   39 +
 spec/features/environments_spec.rb                 |    2 +-
 spec/features/expand_collapse_diffs_spec.rb        |    7 +
 spec/features/issues/award_emoji_spec.rb           |    1 -
 spec/features/issues/filter_issues_spec.rb         |   51 +-
 spec/features/issues/new_branch_button_spec.rb     |    2 +-
 spec/features/issues/reset_filters_spec.rb         |   81 +
 .../issues/user_uses_slash_commands_spec.rb        |   15 +-
 spec/features/issues_spec.rb                       |   13 +-
 spec/features/merge_requests/diff_notes_spec.rb    |  238 +++
 spec/features/merge_requests/edit_mr_spec.rb       |   11 +
 .../merge_requests/merge_request_versions_spec.rb  |   76 +
 .../merge_requests/update_merge_requests_spec.rb   |  132 ++
 .../user_uses_slash_commands_spec.rb               |   10 +-
 spec/features/milestone_spec.rb                    |   21 +-
 spec/features/notes_on_merge_requests_spec.rb      |    2 +-
 spec/features/profiles/keys_spec.rb                |   18 +
 spec/features/projects/branches/delete_spec.rb     |   24 +
 .../projects/branches/download_buttons_spec.rb     |   44 +
 spec/features/projects/branches_spec.rb            |   50 +-
 spec/features/projects/builds_spec.rb              |  412 ++++
 spec/features/projects/commits/cherry_pick_spec.rb |   31 +-
 spec/features/projects/edit_spec.rb                |   57 +
 spec/features/projects/features_visibility_spec.rb |  122 ++
 .../projects/files/download_buttons_spec.rb        |   45 +
 .../project_owner_creates_license_file_spec.rb     |    4 +-
 ...to_create_license_file_in_empty_project_spec.rb |    2 +-
 .../projects/gfm_autocomplete_load_spec.rb         |   21 +
 .../projects/import_export/export_file_spec.rb     |   80 +
 .../projects/import_export/import_file_spec.rb     |    2 +-
 .../import_export/test_project_export.tar.gz       |  Bin 687442 -> 1363770 bytes
 spec/features/projects/issuable_templates_spec.rb  |   33 +-
 .../projects/main/download_buttons_spec.rb         |   44 +
 .../projects/tags/download_buttons_spec.rb         |   45 +
 spec/features/projects_spec.rb                     |    8 +-
 spec/features/search_spec.rb                       |   32 +
 spec/features/task_lists_spec.rb                   |  266 ++-
 spec/features/todos/todos_filtering_spec.rb        |   75 +
 spec/features/todos/todos_spec.rb                  |   35 +
 spec/features/triggers_spec.rb                     |    2 +-
 spec/features/u2f_spec.rb                          |   14 +
 spec/features/unsubscribe_links_spec.rb            |   75 +
 spec/features/users/snippets_spec.rb               |   32 +
 spec/finders/issues_finder_spec.rb                 |   20 +-
 spec/finders/move_to_project_finder_spec.rb        |   22 +
 spec/finders/pipelines_finder_spec.rb              |   52 +
 spec/finders/tags_finder_spec.rb                   |   79 +
 spec/fixtures/api/schemas/issues.json              |   15 +-
 spec/helpers/git_helper_spec.rb                    |    9 +
 spec/helpers/groups_helper_spec.rb                 |   63 +
 spec/helpers/import_helper_spec.rb                 |   24 +
 spec/helpers/issues_helper_spec.rb                 |   26 +
 spec/helpers/nav_helper_spec.rb                    |   25 -
 spec/helpers/projects_helper_spec.rb               |   44 +
 spec/helpers/search_helper_spec.rb                 |   36 +
 spec/helpers/sidekiq_helper_spec.rb                |   40 +
 spec/javascripts/abuse_reports_spec.js.es6         |   41 +
 spec/javascripts/application_spec.js               |   10 +-
 spec/javascripts/awards_handler_spec.js            |   85 +-
 spec/javascripts/behaviors/quick_submit_spec.js    |    3 +
 spec/javascripts/boards/list_spec.js.es6           |    9 -
 spec/javascripts/boards/mock_data.js.es6           |   15 +-
 spec/javascripts/datetime_utility_spec.js.coffee   |   31 -
 spec/javascripts/datetime_utility_spec.js.es6      |   64 +
 spec/javascripts/fixtures/abuse_reports.html.haml  |   16 +
 spec/javascripts/fixtures/awards_handler.html.haml |    2 +-
 spec/javascripts/fixtures/comments.html.haml       |   21 +
 spec/javascripts/fixtures/projects.json            |    2 +-
 .../fixtures/u2f/authenticate.html.haml            |    2 +-
 .../graphs/stat_graph_contributors_graph_spec.js   |    8 +-
 spec/javascripts/issue_spec.js                     |    2 -
 spec/javascripts/new_branch_spec.js                |    2 -
 spec/javascripts/notes_spec.js                     |   57 +-
 spec/javascripts/project_title_spec.js             |   12 -
 spec/javascripts/right_sidebar_spec.js             |    4 -
 spec/javascripts/search_autocomplete_spec.js       |   14 +-
 spec/javascripts/shortcuts_issuable_spec.js        |    1 +
 spec/javascripts/spec_helper.js                    |   38 +-
 spec/javascripts/u2f/authenticate_spec.js          |    8 -
 spec/javascripts/u2f/register_spec.js              |    8 -
 spec/javascripts/zen_mode_spec.js                  |    4 +-
 .../filter/commit_range_reference_filter_spec.rb   |    6 +-
 .../banzai/filter/commit_reference_filter_spec.rb  |    4 +-
 .../filter/external_issue_reference_filter_spec.rb |    2 +-
 .../banzai/filter/issue_reference_filter_spec.rb   |    4 +-
 .../banzai/filter/label_reference_filter_spec.rb   |    2 +-
 .../filter/merge_request_reference_filter_spec.rb  |    4 +-
 .../filter/milestone_reference_filter_spec.rb      |    2 +-
 .../banzai/filter/snippet_reference_filter_spec.rb |    4 +-
 .../banzai/filter/user_reference_filter_spec.rb    |    2 +-
 spec/lib/banzai/pipeline/wiki_pipeline_spec.rb     |    7 +
 .../banzai/reference_parser/base_parser_spec.rb    |    8 +-
 .../banzai/reference_parser/user_parser_spec.rb    |   10 +-
 spec/lib/ci/gitlab_ci_yaml_processor_spec.rb       |   56 +-
 spec/lib/ci/mask_secret_spec.rb                    |   27 +
 spec/lib/expand_variables_spec.rb                  |   73 +
 spec/lib/extracts_path_spec.rb                     |   10 +
 spec/lib/gitlab/auth_spec.rb                       |   97 +-
 spec/lib/gitlab/backend/shell_spec.rb              |   32 +-
 spec/lib/gitlab/ci/config/node/cache_spec.rb       |    2 +-
 spec/lib/gitlab/ci/config/node/environment_spec.rb |  155 ++
 spec/lib/gitlab/ci/config/node/factory_spec.rb     |    3 +-
 spec/lib/gitlab/ci/config/node/global_spec.rb      |   81 +-
 spec/lib/gitlab/ci/config/node/hidden_job_spec.rb  |   58 -
 spec/lib/gitlab/ci/config/node/hidden_spec.rb      |   47 +
 spec/lib/gitlab/ci/config/node/job_spec.rb         |   88 +-
 spec/lib/gitlab/ci/config/node/jobs_spec.rb        |   10 +-
 spec/lib/gitlab/ci/config/node/null_spec.rb        |   41 -
 spec/lib/gitlab/ci/config/node/script_spec.rb      |    4 +-
 spec/lib/gitlab/ci/config/node/undefined_spec.rb   |   33 +-
 spec/lib/gitlab/ci/config/node/unspecified_spec.rb |   32 +
 spec/lib/gitlab/ci/pipeline_duration_spec.rb       |  115 ++
 spec/lib/gitlab/conflict/parser_spec.rb            |    4 +-
 spec/lib/gitlab/database/migration_helpers_spec.rb |   99 +-
 spec/lib/gitlab/git_access_spec.rb                 |  116 +-
 spec/lib/gitlab/git_access_wiki_spec.rb            |    9 +-
 spec/lib/gitlab/git_spec.rb                        |   45 +
 .../gitlab/github_import/comment_formatter_spec.rb |    6 +
 spec/lib/gitlab/github_import/importer_spec.rb     |   46 +-
 .../gitlab/github_import/issue_formatter_spec.rb   |   11 +-
 .../gitlab/github_import/label_formatter_spec.rb   |   28 +-
 .../github_import/milestone_formatter_spec.rb      |    5 +-
 .../gitlab/github_import/project_creator_spec.rb   |   54 +-
 .../github_import/pull_request_formatter_spec.rb   |   13 +-
 .../gitlab/github_import/release_formatter_spec.rb |   54 +
 spec/lib/gitlab/gitlab_import/importer_spec.rb     |    2 +
 .../gitorious_import/project_creator_spec.rb       |   26 -
 spec/lib/gitlab/import_export/all_models.yml       |  187 ++
 .../import_export/attribute_configuration_spec.rb  |   55 +
 .../import_export/model_configuration_spec.rb      |   57 +
 spec/lib/gitlab/import_export/project.json         |   82 +-
 .../import_export/project_tree_restorer_spec.rb    |   49 +-
 .../import_export/project_tree_saver_spec.rb       |   12 +
 spec/lib/gitlab/import_export/reader_spec.rb       |    6 +
 .../gitlab/import_export/safe_model_attributes.yml |  330 ++++
 .../gitlab/import_export/version_checker_spec.rb   |    2 +-
 spec/lib/gitlab/ldap/adapter_spec.rb               |   71 +-
 spec/lib/gitlab/lfs_token_spec.rb                  |   51 +
 spec/lib/gitlab/metrics/rack_middleware_spec.rb    |   21 +-
 spec/lib/gitlab/popen_spec.rb                      |    9 +
 spec/lib/gitlab/search_results_spec.rb             |   18 -
 spec/lib/gitlab/snippet_search_results_spec.rb     |    6 -
 spec/lib/gitlab/workhorse_spec.rb                  |   91 +-
 spec/mailers/notify_spec.rb                        |   10 +-
 spec/mailers/shared/notify.rb                      |    8 +
 spec/models/ability_spec.rb                        |   77 +-
 spec/models/blob_spec.rb                           |   20 +
 spec/models/build_spec.rb                          |  114 +-
 spec/models/ci/build_spec.rb                       |   62 +-
 spec/models/ci/pipeline_spec.rb                    |  148 +-
 spec/models/commit_range_spec.rb                   |   10 -
 spec/models/commit_status_spec.rb                  |   43 +-
 spec/models/concerns/awardable_spec.rb             |   10 +
 spec/models/concerns/has_status_spec.rb            |  129 ++
 .../project_features_compatibility_spec.rb         |   25 +
 spec/models/concerns/statuseable_spec.rb           |  129 --
 spec/models/cycle_analytics/code_spec.rb           |   42 +
 spec/models/cycle_analytics/issue_spec.rb          |   50 +
 spec/models/cycle_analytics/plan_spec.rb           |   52 +
 spec/models/cycle_analytics/production_spec.rb     |   54 +
 spec/models/cycle_analytics/review_spec.rb         |   35 +
 spec/models/cycle_analytics/staging_spec.rb        |   64 +
 spec/models/cycle_analytics/summary_spec.rb        |   53 +
 spec/models/cycle_analytics/test_spec.rb           |   94 +
 spec/models/diff_note_spec.rb                      |   37 +
 spec/models/discussion_spec.rb                     |   96 +-
 spec/models/environment_spec.rb                    |   16 +
 spec/models/event_spec.rb                          |   43 +-
 spec/models/group_spec.rb                          |   46 +
 spec/models/hooks/project_hook_spec.rb             |   18 -
 spec/models/hooks/service_hook_spec.rb             |   18 -
 spec/models/hooks/system_hook_spec.rb              |   20 +-
 spec/models/hooks/web_hook_spec.rb                 |   18 -
 spec/models/issue/metrics_spec.rb                  |   55 +
 spec/models/member_spec.rb                         |   42 +-
 spec/models/members/group_member_spec.rb           |   19 -
 spec/models/members/project_member_spec.rb         |   26 +-
 spec/models/merge_request/metrics_spec.rb          |   18 +
 spec/models/merge_request_diff_spec.rb             |   43 +
 spec/models/merge_request_spec.rb                  |  344 +++-
 spec/models/note_spec.rb                           |   22 +-
 spec/models/project_feature_spec.rb                |   91 +
 spec/models/project_security_spec.rb               |  112 --
 spec/models/project_services/asana_service_spec.rb |   20 -
 .../project_services/assembla_service_spec.rb      |   20 -
 .../models/project_services/bamboo_service_spec.rb |   20 -
 .../project_services/bugzilla_service_spec.rb      |   20 -
 .../project_services/buildkite_service_spec.rb     |   20 -
 .../project_services/campfire_service_spec.rb      |   20 -
 .../custom_issue_tracker_service_spec.rb           |   20 -
 .../project_services/drone_ci_service_spec.rb      |   20 -
 .../project_services/external_wiki_service_spec.rb |   21 -
 .../project_services/flowdock_service_spec.rb      |   20 -
 .../project_services/gemnasium_service_spec.rb     |   20 -
 .../gitlab_issue_tracker_service_spec.rb           |   20 -
 .../project_services/hipchat_service_spec.rb       |   20 -
 spec/models/project_services/irker_service_spec.rb |   20 -
 spec/models/project_services/jira_service_spec.rb  |   20 -
 .../pivotaltracker_service_spec.rb                 |   20 -
 .../project_services/pushover_service_spec.rb      |   20 -
 .../project_services/redmine_service_spec.rb       |   20 -
 .../slack_service/build_message_spec.rb            |   32 +-
 .../slack_service/pipeline_message_spec.rb         |   55 +
 spec/models/project_services/slack_service_spec.rb |   91 +-
 .../project_services/teamcity_service_spec.rb      |   20 -
 spec/models/project_spec.rb                        |  289 ++-
 spec/models/repository_spec.rb                     |  245 ++-
 spec/models/snippet_spec.rb                        |    2 +
 spec/models/user_spec.rb                           |    3 +-
 spec/policies/project_policy_spec.rb               |   49 +
 spec/requests/api/api_helpers_spec.rb              |   52 +
 spec/requests/api/award_emoji_spec.rb              |   72 +-
 spec/requests/api/broadcast_messages_spec.rb       |  180 ++
 spec/requests/api/builds_spec.rb                   |   20 +-
 spec/requests/api/commit_statuses_spec.rb          |   39 +-
 spec/requests/api/commits_spec.rb                  |    6 +-
 spec/requests/api/files_spec.rb                    |   75 +
 spec/requests/api/fork_spec.rb                     |   66 +
 spec/requests/api/groups_spec.rb                   |   11 +-
 spec/requests/api/internal_spec.rb                 |  108 ++
 spec/requests/api/issues_spec.rb                   |  220 ++-
 spec/requests/api/lint_spec.rb                     |   49 +
 spec/requests/api/members_spec.rb                  |   27 +-
 spec/requests/api/merge_request_diffs_spec.rb      |   49 +
 spec/requests/api/merge_requests_spec.rb           |   10 +-
 spec/requests/api/milestones_spec.rb               |   23 +-
 spec/requests/api/notes_spec.rb                    |   11 +-
 spec/requests/api/notification_settings_spec.rb    |   89 +
 spec/requests/api/project_hooks_spec.rb            |    8 +
 spec/requests/api/project_snippets_spec.rb         |    1 +
 spec/requests/api/projects_spec.rb                 |   53 +-
 spec/requests/api/users_spec.rb                    |    1 +
 spec/requests/ci/api/builds_spec.rb                |  157 +-
 spec/requests/git_http_spec.rb                     |   91 +-
 spec/requests/jwt_controller_spec.rb               |   39 +-
 spec/requests/lfs_http_spec.rb                     |  375 +++-
 .../requests/projects/artifacts_controller_spec.rb |  117 ++
 spec/routing/routing_spec.rb                       |    4 +-
 ...ntainer_registry_authentication_service_spec.rb |   64 +-
 spec/services/boards/lists/create_service_spec.rb  |   11 +-
 spec/services/ci/image_for_build_service_spec.rb   |    2 +-
 spec/services/ci/register_build_service_spec.rb    |   19 +
 spec/services/create_deployment_service_spec.rb    |  122 +-
 spec/services/git_push_service_spec.rb             |   52 +
 spec/services/issuable/bulk_update_service_spec.rb |  282 +++
 spec/services/issues/bulk_update_service_spec.rb   |  282 ---
 spec/services/issues/close_service_spec.rb         |   40 +-
 spec/services/issues/create_service_spec.rb        |   18 +
 spec/services/issues/reopen_service_spec.rb        |   46 +-
 spec/services/issues/update_service_spec.rb        |   93 +-
 spec/services/merge_requests/build_service_spec.rb |    4 +-
 .../services/merge_requests/create_service_spec.rb |   29 +
 .../merge_requests/get_urls_service_spec.rb        |    2 +-
 .../merge_request_diff_cache_service_spec.rb       |    2 +-
 .../merge_requests/refresh_service_spec.rb         |   52 +
 .../merge_requests/resolve_service_spec.rb         |   87 +
 .../services/merge_requests/update_service_spec.rb |   37 +
 spec/services/notes/slash_commands_service_spec.rb |   69 +
 spec/services/notification_service_spec.rb         |    2 +
 spec/services/projects/create_service_spec.rb      |    6 +-
 .../services/projects/housekeeping_service_spec.rb |   33 +-
 .../protected_branches/create_service_spec.rb      |   23 +
 spec/services/system_note_service_spec.rb          |   10 +-
 spec/services/todo_service_spec.rb                 |   37 +
 spec/spec_helper.rb                                |    6 +-
 spec/support/cycle_analytics_helpers.rb            |   64 +
 .../cycle_analytics_helpers/test_generation.rb     |  161 ++
 spec/support/db_cleaner.rb                         |    2 +-
 spec/support/git_helpers.rb                        |    9 +
 spec/support/import_export/configuration_helper.rb |   29 +
 spec/support/import_export/export_file_helper.rb   |  133 ++
 .../issuable_slash_commands_shared_examples.rb     |   68 +-
 spec/support/ldap_helpers.rb                       |   47 +
 spec/support/login_helpers.rb                      |    1 +
 spec/support/slash_commands_helpers.rb             |   10 +
 spec/support/taskable_shared_examples.rb           |   63 +-
 spec/support/test_env.rb                           |   47 +-
 spec/support/wait_for_vue_resource.rb              |    7 +
 spec/support/workhorse_helpers.rb                  |    5 +
 spec/views/projects/builds/show.html.haml_spec.rb  |   12 +-
 .../merge_requests/_heading.html.haml_spec.rb      |    2 +
 .../projects/merge_requests/edit.html.haml_spec.rb |   53 +
 .../projects/merge_requests/show.html.haml_spec.rb |   44 +
 spec/views/projects/notes/_form.html.haml_spec.rb  |   36 +
 .../projects/pipelines/show.html.haml_spec.rb      |   53 +
 spec/workers/prune_old_events_worker_spec.rb       |   24 +
 .../single_repository_worker_spec.rb               |    8 +-
 vendor/assets/javascripts/Chart.js                 |    0
 vendor/assets/javascripts/autosize.js              |    0
 vendor/assets/javascripts/jquery.scrollTo.js       |    0
 vendor/assets/javascripts/task_list.js             |  161 +-
 vendor/gitignore/Global/NetBeans.gitignore         |    1 -
 vendor/gitignore/Global/OSX.gitignore              |   25 -
 vendor/gitignore/Global/Tags.gitignore             |    1 +
 vendor/gitignore/Global/macOS.gitignore            |   26 +
 vendor/gitignore/Haskell.gitignore                 |    1 +
 vendor/gitignore/Joomla.gitignore                  |   16 +
 vendor/gitignore/Node.gitignore                    |    3 +
 vendor/gitignore/Objective-C.gitignore             |    2 +
 vendor/gitignore/Python.gitignore                  |    1 +
 vendor/gitignore/Rails.gitignore                   |    6 +-
 vendor/gitignore/VisualStudio.gitignore            |    7 +
 vendor/gitlab-ci-yml/Docker.gitlab-ci.yml          |    7 +-
 vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml            |    9 +
 vendor/gitlab-ci-yml/Swift.gitlab-ci.yml           |   30 +
 1105 files changed, 27129 insertions(+), 10672 deletions(-)

diff --git a/.flayignore b/.flayignore
index 9c9875d..f120de5 100644
--- a/.flayignore
+++ b/.flayignore
@@ -1 +1,2 @@
 *.erb
+lib/gitlab/sanitizers/svg/whitelist.rb
diff --git a/.gitignore b/.gitignore
index 1bf9a47..9166512 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,3 +48,4 @@
 /vendor/bundle/*
 /builds/*
 /shared/*
+/.gitlab_workhorse_secret
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index be56145..b167fc7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -82,7 +82,7 @@ update-knapsack:
     - export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
     - export KNAPSACK_GENERATE_REPORT=true
     - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
-    - knapsack rspec
+    - knapsack rspec "--color --format documentation"
   artifacts:
     expire_in: 31d
     paths:
@@ -206,10 +206,15 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
     - bundle exec $CI_BUILD_NAME
 
 rubocop: *exec
+rake haml_lint: *exec
 rake scss_lint: *exec
 rake brakeman: *exec
-rake flog: *exec
-rake flay: *exec
+rake flog:
+  <<: *exec
+  allow_failure: yes
+rake flay:
+  <<: *exec
+  allow_failure: yes
 license_finder: *exec
 rake downtime_check: *exec
 
@@ -248,6 +253,21 @@ bundler:audit:
   script:
     - "bundle exec bundle-audit check --update --ignore OSVDB-115941"
 
+migration paths:
+  stage: test
+  <<: *use-db
+  only:
+    - master at gitlab-org/gitlab-ce
+  script:
+    - git checkout HEAD .
+    - git fetch --tags
+    - git checkout v8.5.9
+    - 'echo test: unix:/var/opt/gitlab/redis/redis.socket > config/resque.yml'
+    - bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" --retry=3
+    - rake db:drop db:create db:schema:load db:seed_fu
+    - git checkout $CI_BUILD_REF
+    - rake db:migrate
+
 coverage:
   stage: post-test
   services: []
@@ -263,7 +283,6 @@ coverage:
     - coverage/index.html
     - coverage/assets/
 
-
 # Notify slack in the end
 
 notify:slack:
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
new file mode 100644
index 0000000..ac38f0c
--- /dev/null
+++ b/.gitlab/issue_templates/Bug.md
@@ -0,0 +1,44 @@
+### Summary
+
+(Summarize the bug encountered concisely)
+
+### Steps to reproduce
+
+(How one can reproduce the issue - this is very important)
+
+### Expected behavior
+
+(What you should see instead)
+
+### Actual behavior
+
+(What actually happens)
+
+### Relevant logs and/or screenshots
+
+(Paste any relevant logs - please use code blocks (```) to format console output,
+logs, and code as it's very hard to read otherwise.)
+
+### Output of checks
+
+#### Results of GitLab application Check
+
+(For installations with omnibus-gitlab package run and paste the output of:
+`sudo gitlab-rake gitlab:check SANITIZE=true`)
+
+(For installations from source run and paste the output of:
+`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`)
+
+(we will only investigate if the tests are passing)
+
+#### Results of GitLab environment info
+
+(For installations with omnibus-gitlab package run and paste the output of:
+`sudo gitlab-rake gitlab:env:info`)
+
+(For installations from source run and paste the output of:
+`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
+
+### Possible fixes
+
+(If you can, link to the line of code that might be responsible for the problem)
diff --git a/.gitlab/issue_templates/Feature Proposal.md b/.gitlab/issue_templates/Feature Proposal.md
new file mode 100644
index 0000000..ea895ee
--- /dev/null
+++ b/.gitlab/issue_templates/Feature Proposal.md	
@@ -0,0 +1,7 @@
+### Description
+
+(Include problem, use cases, benefits, and/or goals)
+
+### Proposal
+
+### Links / references
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md
new file mode 100644
index 0000000..d2a1eb5
--- /dev/null
+++ b/.gitlab/merge_request_templates/Documentation.md
@@ -0,0 +1,14 @@
+See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html.
+
+## What does this MR do?
+
+(briefly describe what this MR is about)
+
+## Moving docs to a new location?
+
+See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#changing-document-location
+
+- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location.
+- [ ] Make sure internal links pointing to the document in question are not broken.
+- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory.
+- [ ] If working on CE, submit an MR to EE with the changes as well.
diff --git a/.haml-lint.yml b/.haml-lint.yml
new file mode 100644
index 0000000..da9a43d
--- /dev/null
+++ b/.haml-lint.yml
@@ -0,0 +1,103 @@
+# Whether to ignore frontmatter at the beginning of HAML documents for
+# frameworks such as Jekyll/Middleman
+skip_frontmatter: false
+exclude:
+  - 'vendor/**/*'
+  - 'spec/**/*'
+
+linters:
+  AltText:
+    enabled: false
+
+  ClassAttributeWithStaticValue:
+    enabled: false
+
+  ClassesBeforeIds:
+    enabled: false
+
+  ConsecutiveComments:
+    enabled: false
+
+  ConsecutiveSilentScripts:
+    enabled: false
+    max_consecutive: 2
+
+  EmptyObjectReference:
+    enabled: true
+
+  EmptyScript:
+    enabled: true
+
+  FinalNewline:
+    enabled: false
+    present: true
+
+  HtmlAttributes:
+    enabled: false
+
+  ImplicitDiv:
+    enabled: false
+
+  LeadingCommentSpace:
+    enabled: false
+
+  LineLength:
+    enabled: false
+    max: 80
+
+  MultilinePipe:
+    enabled: false
+
+  MultilineScript:
+    enabled: true
+
+  ObjectReferenceAttributes:
+    enabled: true
+
+  RuboCop:
+    enabled: false
+    # These cops are incredibly noisy when it comes to HAML templates, so we
+    # ignore them.
+    ignored_cops:
+      - Lint/BlockAlignment
+      - Lint/EndAlignment
+      - Lint/Void
+      - Metrics/LineLength
+      - Style/AlignParameters
+      - Style/BlockNesting
+      - Style/ElseAlignment
+      - Style/FileName
+      - Style/FinalNewline
+      - Style/FrozenStringLiteralComment
+      - Style/IfUnlessModifier
+      - Style/IndentationWidth
+      - Style/Next
+      - Style/TrailingBlankLines
+      - Style/TrailingWhitespace
+      - Style/WhileUntilModifier
+
+  RubyComments:
+    enabled: false
+
+  SpaceBeforeScript:
+    enabled: false
+
+  SpaceInsideHashAttributes:
+    enabled: false
+    style: space
+
+  Indentation:
+    enabled: true
+    character: space # or tab
+
+  TagName:
+    enabled: true
+
+  TrailingWhitespace:
+    enabled: false
+
+  UnnecessaryInterpolation:
+    enabled: false
+
+  UnnecessaryStringOutput:
+    enabled: false
diff --git a/.pkgr.yml b/.pkgr.yml
index 8fc9fdd..10bcd7b 100644
--- a/.pkgr.yml
+++ b/.pkgr.yml
@@ -3,6 +3,8 @@ group: git
 services:
   - postgres
 before_precompile: ./bin/pkgr_before_precompile.sh
+env: 
+  - SKIP_STORAGE_VALIDATION=true 
 targets:
   debian-7: &wheezy
     build_dependencies:
@@ -25,6 +27,16 @@ targets:
       - libicu52
       - libpcre3
       - git
+  ubuntu-16.04:
+    build_dependencies:
+      - libkrb5-dev
+      - libicu-dev
+      - cmake
+      - pkg-config
+    dependencies:
+      - libicu55
+      - libpcre3
+      - git
   centos-6:
     build_dependencies:
       - krb5-devel
diff --git a/.rubocop.yml b/.rubocop.yml
index 282f453..5bd31cc 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -5,8 +5,8 @@ require:
 inherit_from: .rubocop_todo.yml
 
 AllCops:
-  TargetRubyVersion: 2.1
-  # Cop names are not displayed in offense messages by default. Change behavior
+  TargetRubyVersion: 2.3
+  # Cop names are not d§splayed in offense messages by default. Change behavior
   # by overriding DisplayCopNames, or by giving the -D/--display-cop-names
   # option.
   DisplayCopNames: true
@@ -192,6 +192,9 @@ Style/FlipFlop:
 Style/For:
   Enabled: true
 
+# Checks if there is a magic comment to enforce string literals
+Style/FrozenStringLiteralComment:
+  Enabled: false
 # Do not introduce global variables.
 Style/GlobalVars:
   Enabled: true
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 20daf16..87520c6 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,21 +1,21 @@
 # This configuration was generated by
 # `rubocop --auto-gen-config --exclude-limit 0`
-# on 2016-07-13 12:36:08 -0600 using RuboCop version 0.41.2.
+# on 2016-09-14 15:44:53 -0400 using RuboCop version 0.42.0.
 # The point is for the user to remove these configuration records
 # one by one as the offenses are removed from the code base.
 # Note that changes in the inspected code, or installation of new
 # versions of RuboCop, may require this file to be generated again.
 
-# Offense count: 154
+# Offense count: 158
 Lint/AmbiguousRegexpLiteral:
   Enabled: false
 
-# Offense count: 43
+# Offense count: 41
 # Configuration parameters: AllowSafeAssignment.
 Lint/AssignmentInCondition:
   Enabled: false
 
-# Offense count: 14
+# Offense count: 16
 Lint/HandleExceptions:
   Enabled: false
 
@@ -23,28 +23,28 @@ Lint/HandleExceptions:
 Lint/Loop:
   Enabled: false
 
-# Offense count: 15
+# Offense count: 16
 Lint/ShadowingOuterLocalVariable:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 6
 # Cop supports --auto-correct.
 Lint/StringConversionInInterpolation:
   Enabled: false
 
-# Offense count: 44
+# Offense count: 49
 # Cop supports --auto-correct.
 # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
 Lint/UnusedBlockArgument:
   Enabled: false
 
-# Offense count: 129
+# Offense count: 144
 # Cop supports --auto-correct.
 # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
 Lint/UnusedMethodArgument:
   Enabled: false
 
-# Offense count: 12
+# Offense count: 9
 # Cop supports --auto-correct.
 Performance/PushSplat:
   Enabled: false
@@ -59,51 +59,51 @@ Performance/RedundantBlockCall:
 Performance/RedundantMatch:
   Enabled: false
 
-# Offense count: 24
+# Offense count: 27
 # Cop supports --auto-correct.
 # Configuration parameters: MaxKeyValuePairs.
 Performance/RedundantMerge:
   Enabled: false
 
-# Offense count: 60
+# Offense count: 61
 Rails/OutputSafety:
   Enabled: false
 
-# Offense count: 128
+# Offense count: 129
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: strict, flexible
 Rails/TimeZone:
   Enabled: false
 
-# Offense count: 12
+# Offense count: 15
 # Cop supports --auto-correct.
 # Configuration parameters: Include.
 # Include: app/models/**/*.rb
 Rails/Validation:
   Enabled: false
 
-# Offense count: 217
+# Offense count: 273
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
 # SupportedStyles: with_first_parameter, with_fixed_indentation
 Style/AlignParameters:
   Enabled: false
 
-# Offense count: 32
+# Offense count: 30
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: always, conditionals
 Style/AndOr:
   Enabled: false
 
-# Offense count: 47
+# Offense count: 50
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: percent_q, bare_percent
 Style/BarePercentLiterals:
   Enabled: false
 
-# Offense count: 258
+# Offense count: 289
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: braces, no_braces, context_dependent
@@ -126,14 +126,14 @@ Style/ColonMethodCall:
 Style/CommentAnnotation:
   Enabled: false
 
-# Offense count: 34
+# Offense count: 33
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly.
 # SupportedStyles: assign_to_condition, assign_inside_condition
 Style/ConditionalAssignment:
   Enabled: false
 
-# Offense count: 789
+# Offense count: 881
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: leading, trailing
@@ -144,11 +144,12 @@ Style/DotPosition:
 Style/DoubleNegation:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 4
+# Cop supports --auto-correct.
 Style/EachWithObject:
   Enabled: false
 
-# Offense count: 30
+# Offense count: 25
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: empty, nil, both
@@ -160,7 +161,7 @@ Style/EmptyElse:
 Style/EmptyLiteral:
   Enabled: false
 
-# Offense count: 123
+# Offense count: 135
 # Cop supports --auto-correct.
 # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
 Style/ExtraSpacing:
@@ -172,16 +173,16 @@ Style/ExtraSpacing:
 Style/FormatString:
   Enabled: false
 
-# Offense count: 48
+# Offense count: 51
 # Configuration parameters: MinBodyLength.
 Style/GuardClause:
   Enabled: false
 
-# Offense count: 11
+# Offense count: 9
 Style/IfInsideElse:
   Enabled: false
 
-# Offense count: 177
+# Offense count: 174
 # Cop supports --auto-correct.
 # Configuration parameters: MaxLineLength.
 Style/IfUnlessModifier:
@@ -194,7 +195,7 @@ Style/IfUnlessModifier:
 Style/IndentArray:
   Enabled: false
 
-# Offense count: 89
+# Offense count: 97
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
 # SupportedStyles: special_inside_parentheses, consistent, align_braces
@@ -208,7 +209,7 @@ Style/IndentHash:
 Style/Lambda:
   Enabled: false
 
-# Offense count: 6
+# Offense count: 5
 # Cop supports --auto-correct.
 Style/LineEndConcatenation:
   Enabled: false
@@ -218,17 +219,21 @@ Style/LineEndConcatenation:
 Style/MethodCallParentheses:
   Enabled: false
 
-# Offense count: 62
+# Offense count: 8
+Style/MethodMissing:
+  Enabled: false
+
+# Offense count: 85
 # Cop supports --auto-correct.
 Style/MutableConstant:
   Enabled: false
 
-# Offense count: 10
+# Offense count: 8
 # Cop supports --auto-correct.
 Style/NestedParenthesizedCalls:
   Enabled: false
 
-# Offense count: 12
+# Offense count: 13
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
 # SupportedStyles: skip_modifier_ifs, always
@@ -242,12 +247,19 @@ Style/Next:
 Style/NumericLiteralPrefix:
   Enabled: false
 
+# Offense count: 64
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: predicate, comparison
+Style/NumericPredicate:
+  Enabled: false
+
 # Offense count: 29
 # Cop supports --auto-correct.
 Style/ParallelAssignment:
   Enabled: false
 
-# Offense count: 208
+# Offense count: 264
 # Cop supports --auto-correct.
 # Configuration parameters: PreferredDelimiters.
 Style/PercentLiteralDelimiters:
@@ -265,7 +277,7 @@ Style/PercentQLiterals:
 Style/PerlBackrefs:
   Enabled: false
 
-# Offense count: 32
+# Offense count: 35
 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
 # NamePrefix: is_, has_, have_
 # NamePrefixBlacklist: is_, has_, have_
@@ -273,7 +285,7 @@ Style/PerlBackrefs:
 Style/PredicateName:
   Enabled: false
 
-# Offense count: 28
+# Offense count: 27
 # Cop supports --auto-correct.
 Style/PreferredHashMethods:
   Enabled: false
@@ -283,14 +295,14 @@ Style/PreferredHashMethods:
 Style/Proc:
   Enabled: false
 
-# Offense count: 20
+# Offense count: 22
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: compact, exploded
 Style/RaiseArgs:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 4
 # Cop supports --auto-correct.
 Style/RedundantBegin:
   Enabled: false
@@ -300,29 +312,29 @@ Style/RedundantBegin:
 Style/RedundantException:
   Enabled: false
 
-# Offense count: 23
+# Offense count: 24
 # Cop supports --auto-correct.
 Style/RedundantFreeze:
   Enabled: false
 
-# Offense count: 377
+# Offense count: 408
 # Cop supports --auto-correct.
 Style/RedundantSelf:
   Enabled: false
 
-# Offense count: 94
+# Offense count: 93
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
 # SupportedStyles: slashes, percent_r, mixed
 Style/RegexpLiteral:
   Enabled: false
 
-# Offense count: 17
+# Offense count: 18
 # Cop supports --auto-correct.
 Style/RescueModifier:
   Enabled: false
 
-# Offense count: 2
+# Offense count: 5
 # Cop supports --auto-correct.
 Style/SelfAssignment:
   Enabled: false
@@ -339,42 +351,42 @@ Style/SingleLineBlockParams:
 Style/SingleLineMethods:
   Enabled: false
 
-# Offense count: 119
+# Offense count: 124
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: space, no_space
 Style/SpaceBeforeBlockBraces:
   Enabled: false
 
-# Offense count: 11
+# Offense count: 10
 # Cop supports --auto-correct.
 # Configuration parameters: AllowForAlignment.
 Style/SpaceBeforeFirstArg:
   Enabled: false
 
-# Offense count: 130
+# Offense count: 141
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
 # SupportedStyles: space, no_space
 Style/SpaceInsideBlockBraces:
   Enabled: false
 
-# Offense count: 98
+# Offense count: 96
 # Cop supports --auto-correct.
 Style/SpaceInsideBrackets:
   Enabled: false
 
-# Offense count: 60
+# Offense count: 62
 # Cop supports --auto-correct.
 Style/SpaceInsideParens:
   Enabled: false
 
-# Offense count: 5
+# Offense count: 7
 # Cop supports --auto-correct.
 Style/SpaceInsidePercentLiteralDelimiters:
   Enabled: false
 
-# Offense count: 36
+# Offense count: 40
 # Cop supports --auto-correct.
 # Configuration parameters: SupportedStyles.
 # SupportedStyles: use_perl_names, use_english_names
@@ -388,21 +400,28 @@ Style/SpecialGlobalVars:
 Style/StringLiteralsInInterpolation:
   Enabled: false
 
-# Offense count: 24
+# Offense count: 32
 # Cop supports --auto-correct.
 # Configuration parameters: IgnoredMethods.
 # IgnoredMethods: respond_to, define_method
 Style/SymbolProc:
   Enabled: false
 
-# Offense count: 23
+# Offense count: 5
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment.
+# SupportedStyles: require_parentheses, require_no_parentheses
+Style/TernaryParentheses:
+  Enabled: false
+
+# Offense count: 24
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
 # SupportedStyles: comma, consistent_comma, no_comma
 Style/TrailingCommaInArguments:
   Enabled: false
 
-# Offense count: 113
+# Offense count: 102
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
 # SupportedStyles: comma, consistent_comma, no_comma
@@ -415,7 +434,7 @@ Style/TrailingCommaInLiteral:
 Style/TrailingUnderscoreVariable:
   Enabled: false
 
-# Offense count: 90
+# Offense count: 76
 # Cop supports --auto-correct.
 Style/TrailingWhitespace:
   Enabled: false
@@ -427,12 +446,12 @@ Style/TrailingWhitespace:
 Style/TrivialAccessors:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 2
 # Cop supports --auto-correct.
 Style/UnlessElse:
   Enabled: false
 
-# Offense count: 13
+# Offense count: 14
 # Cop supports --auto-correct.
 Style/UnneededInterpolation:
   Enabled: false
diff --git a/CHANGELOG b/CHANGELOG
index 22c5cd7..fcaaa28 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,14 +1,242 @@
 Please view this file on the master branch, on stable branches it's out of date.
 
+v 8.12.2 (unreleased)
+
+v 8.12.1
+  - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
+  - Fix issue with search filter labels not displaying
+
+v 8.12.0
+  - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
+  - Only check :can_resolve permission if the note is resolvable
+  - Bump fog-aws to v0.11.0 to support ap-south-1 region
+  - Add ability to fork to a specific namespace using API. (ritave)
+  - Allow to set request_access_enabled for groups and projects
+  - Cleanup misalignments in Issue list view !6206
+  - Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist
+  - Prune events older than 12 months. (ritave)
+  - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell)
+  - Fix issues/merge-request templates dropdown for forked projects
+  - Amends the packager.io configuration file to create a build for Ubuntu 16.04. !6247 (Jon "The Nice Guy" Spriggs)
+  - Filter tags by name !6121
+  - Update gitlab shell secret file also when it is empty. !3774 (glensc)
+  - Give project selection dropdowns responsive width, make non-wrapping.
+  - Fix note form hint showing slash commands supported for commits.
+  - Make push events have equal vertical spacing.
+  - API: Ensure invitees are not returned in Members API.
+  - Preserve applied filters on issues search.
+  - Add two-factor recovery endpoint to internal API !5510
+  - Pass the "Remember me" value to the U2F authentication form
+  - Display stages in valid order in stages dropdown on build page
+  - Only update projects.last_activity_at once per hour when creating a new event
+  - Cycle analytics (first iteration) !5986
+  - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps)
+  - Move pushes_since_gc from the database to Redis
+  - Limit number of shown environments on Merge Request: show only environments for target_branch, source_branch and tags
+  - Add font color contrast to external label in admin area (ClemMakesApps)
+  - Change logo animation to CSS (ClemMakesApps)
+  - Instructions for enabling Git packfile bitmaps !6104
+  - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint
+  - Fix long comments in diffs messing with table width
+  - Add spec covering 'Gitlab::Git::committer_hash' !6433 (dandunckelman)
+  - Fix pagination on user snippets page
+  - Run CI builds with the permissions of users !5735
+  - Fix sorting of issues in API
+  - Fix download artifacts button links !6407
+  - Sort project variables by key. !6275 (Diego Souza)
+  - Ensure specs on sorting of issues in API are deterministic on MySQL
+  - Added ability to use predefined CI variables for environment name
+  - Added ability to specify URL in environment configuration in gitlab-ci.yml
+  - Escape search term before passing it to Regexp.new !6241 (winniehell)
+  - Fix pinned sidebar behavior in smaller viewports !6169
+  - Fix file permissions change when updating a file on the Gitlab UI !5979
+  - Added horizontal padding on build page sidebar on code coverage block. !6196 (Vitaly Baev)
+  - Change merge_error column from string to text type
+  - Reduce contributions calendar data payload (ClemMakesApps)
+  - Show all pipelines for merge requests even from discarded commits !6414
+  - Replace contributions calendar timezone payload with dates (ClemMakesApps)
+  - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
+  - Enable pipeline events by default !6278
+  - Move parsing of sidekiq ps into helper !6245 (pascalbetz)
+  - Added go to issue boards keyboard shortcut
+  - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel)
+  - Emoji can be awarded on Snippets !4456
+  - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
+  - Fix blame table layout width
+  - Spec testing if issue authors can read issues on private projects
+  - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps)
+  - Request only the LDAP attributes we need !6187
+  - Center build stage columns in pipeline overview (ClemMakesApps)
+  - Fix bug with tooltip not hiding on discussion toggle button
+  - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps)
+  - Fix bug stopping issue description being scrollable after selecting issue template
+  - Remove suggested colors hover underline (ClemMakesApps)
+  - Fix jump to discussion button being displayed on commit notes
+  - Shorten task status phrase (ClemMakesApps)
+  - Fix project visibility level fields on settings
+  - Add hover color to emoji icon (ClemMakesApps)
+  - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files
+  - Add textarea autoresize after comment (ClemMakesApps)
+  - Do not write SSH public key 'comments' to authorized_keys !6381
+  - Refresh todos count cache when an Issue/MR is deleted
+  - Fix branches page dropdown sort alignment (ClemMakesApps)
+  - Hides merge request button on branches page is user doesn't have permissions
+  - Add white background for no readme container (ClemMakesApps)
+  - API: Expose issue confidentiality flag. (Robert Schilling)
+  - Fix markdown anchor icon interaction (ClemMakesApps)
+  - Test migration paths from 8.5 until current release !4874
+  - Replace animateEmoji timeout with eventListener (ClemMakesApps)
+  - Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
+  - Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto)
+  - Add `wiki_page_events` to project hook APIs (Ben Boeckel)
+  - Remove Gitorious import
+  - Loads GFM autocomplete source only when required
+  - Fix issue with slash commands not loading on new issue page
+  - Fix inconsistent background color for filter input field (ClemMakesApps)
+  - Remove prefixes from transition CSS property (ClemMakesApps)
+  - Add Sentry logging to API calls
+  - Add BroadcastMessage API
+  - Use 'git update-ref' for safer web commits !6130
+  - Sort pipelines requested through the API
+  - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
+  - Fix issue boards loading on large screens
+  - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084
+  - Show queued time when showing a pipeline !6084
+  - Remove unused mixins (ClemMakesApps)
+  - Add search to all issue board lists
+  - Scroll active tab into view on mobile
+  - Fix groups sort dropdown alignment (ClemMakesApps)
+  - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
+  - Use JavaScript tooltips for mentions !5301 (winniehell)
+  - Add hover state to todos !5361 (winniehell)
+  - Fix icon alignment of star and fork buttons !5451 (winniehell)
+  - Fix alignment of icon buttons !5887 (winniehell)
+  - Added Ubuntu 16.04 support for packager.io (JonTheNiceGuy)
+  - Fix markdown help references (ClemMakesApps)
+  - Add last commit time to repo view (ClemMakesApps)
+  - Fix accessibility and visibility of project list dropdown button !6140
+  - Fix missing flash messages on service edit page (airatshigapov)
+  - Added project-specific enable/disable setting for LFS !5997
+  - Added group-specific enable/disable setting for LFS !6164
+  - Add optional 'author' param when making commits. !5822 (dandunckelman)
+  - Don't expose a user's token in the `/api/v3/user` API (!6047)
+  - Remove redundant js-timeago-pending from user activity log (ClemMakesApps)
+  - Ability to manage project issues, snippets, wiki, merge requests and builds access level
+  - Remove inconsistent font weight for sidebar's labels (ClemMakesApps)
+  - Align add button on repository view (ClemMakesApps)
+  - Fix contributions calendar month label truncation (ClemMakesApps)
+  - Import release note descriptions from GitHub (EspadaV8)
+  - Added tests for diff notes
+  - Add pipeline events to Slack integration !5525
+  - Add a button to download latest successful artifacts for branches and tags !5142
+  - Remove redundant pipeline tooltips (ClemMakesApps)
+  - Expire commit info views after one day, instead of two weeks, to allow for user email updates
+  - Add delimiter to project stars and forks count (ClemMakesApps)
+  - Fix badge count alignment (ClemMakesApps)
+  - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell)
+  - Fix repo title alignment (ClemMakesApps)
+  - Change update interval of contacted_at
+  - Add LFS support to SSH !6043
+  - Fix branch title trailing space on hover (ClemMakesApps)
+  - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8)
+  - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison)
+  - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison)
+  - Order award emoji tooltips in order they were added (EspadaV8)
+  - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps)
+  - Update merge_requests.md with a simpler way to check out a merge request. !5944
+  - Fix button missing type (ClemMakesApps)
+  - Gitlab::Checks is now instrumented
+  - Move to project dropdown with infinite scroll for better performance
+  - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz)
+  - Load branches asynchronously in Cherry Pick and Revert dialogs.
+  - Convert datetime coffeescript spec to ES6 (ClemMakesApps)
+  - Add merge request versions !5467
+  - Change using size to use count and caching it for number of group members. !5935
+  - Replace play icon font with svg (ClemMakesApps)
+  - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck)
+  - Reduce number of database queries on builds tab
+  - Wrap text in commit message containers
+  - Capitalize mentioned issue timeline notes (ClemMakesApps)
+  - Fix inconsistent checkbox alignment (ClemMakesApps)
+  - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger)
+  - Adds response mime type to transaction metric action when it's not HTML
+  - Fix hover leading space bug in pipeline graph !5980
+  - Avoid conflict with admin labels when importing GitHub labels
+  - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496
+  - Fix repository page ui issues
+  - Avoid protected branches checks when verifying access without branch name
+  - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov)
+  - Fixed invisible scroll controls on build page on iPhone
+  - Fix error on raw build trace download for old builds stored in database !4822
+  - Refactor the triggers page and documentation !6217
+  - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska)
+  - Use default clone protocol on "check out, review, and merge locally" help page URL
+  - Let the user choose a namespace and name on GitHub imports
+  - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska)
+  - Allow bulk update merge requests from merge requests index page
+  - Ensure validation messages are shown within the milestone form
+  - Add notification_settings API calls !5632 (mahcsig)
+  - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska)
+  - Fix URLs with anchors in wiki !6300 (houqp)
+  - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska)
+  - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225
+  - Fix Gitlab::Popen.popen thread-safety issue
+  - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska)
+  - Clean environment variables when running git hooks
+  - Fix Import/Export issues importing protected branches and some specific models
+  - Fix non-master branch readme display in tree view
+  - Add UX improvements for merge request version diffs
+
+v 8.11.6
+  - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005
+  - Make merge conflict file size limit 200 KB, to match the docs. !6052
+  - Fix an error where we were unable to create a CommitStatus for running state. !6107
+  - Optimize discussion notes resolving and unresolving. !6141
+  - Fix GitLab import button. !6167
+  - Restore SSH Key title auto-population behavior. !6186
+  - Fix DB schema to match latest migration. !6256
+  - Exclude some pending or inactivated rows in Member scopes.
+
+v 8.11.5
+  - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087
+  - Fix member expiration date picker after update. !6184
+  - Fix suggested colors options for new labels in the admin area. !6138
+  - Optimize discussion notes resolving and unresolving
+  - Fix GitLab import button
+  - Fix confidential issues being exposed as public using gitlab.com export
+  - Remove gitorious from import_sources. !6180
+  - Scope webhooks/services that will run for confidential issues
+  - Remove gitorious from import_sources
+  - Fix confidential issues being exposed as public using gitlab.com export
+  - Use oj gem for faster JSON processing
+
+v 8.11.4
+  - Fix resolving conflicts on forks. !6082
+  - Fix diff commenting on merge requests created prior to 8.10. !6029
+  - Fix pipelines tab layout regression. !5952
+  - Fix "Wiki" link not appearing in navigation for projects with external wiki. !6057
+  - Do not enforce using hash with hidden key in CI configuration. !6079
+  - Fix hover leading space bug in pipeline graph !5980
+  - Fix sorting issues by "last updated" doesn't work after import from GitHub
+  - GitHub importer use default project visibility for non-private projects
+  - Creating an issue through our API now emails label subscribers !5720
+  - Block concurrent updates for Pipeline
+  - Don't create groups for unallowed users when importing projects
+  - Fix issue boards leak private label names and descriptions
+  - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
+  - Remove gitorious. !5866
+  - Allow compare merge request versions
+
 v 8.11.3
+  - Allow system info page to handle case where info is unavailable
+  - Label list shows all issues (opened or closed) with that label
   - Don't show resolve conflicts link before MR status is updated
-  - Fix IE11 fork button bug !598
+  - Fix IE11 fork button bug !5982
   - Don't prevent viewing the MR when git refs for conflicts can't be found on disk
-  - Allow system info page to handle case where info is unavailable
   - Fix external issue tracker "Issues" link leading to 404s
   - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters
-  - Label list shows all issues (opened or closed) with that label
   - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
+  - Issues filters reset button
 
 v 8.11.2
   - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978
@@ -27,6 +255,7 @@ v 8.11.0
   - Add Koding (online IDE) integration
   - Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
   - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
+  - Add delimiter to project stars and forks count (ClemMakesApps)
   - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
   - Fix adding line comments on the initial commit to a repo !5900
   - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
@@ -77,11 +306,10 @@ v 8.11.0
   - Update `timeago` plugin to use multiple string/locale settings
   - Remove unused images (ClemMakesApps)
   - Get issue and merge request description templates from repositories
-  - Add hover state to todos !5361 (winniehell)
-  - Fix icon alignment of star and fork buttons !5451 (winniehell)
   - Enforce 2FA restrictions on API authentication endpoints !5820
   - Limit git rev-list output count to one in forced push check
   - Show deployment status on merge requests with external URLs
+  - Fix branch title trailing space on hover (ClemMakesApps)
   - Clean up unused routes (Josef Strzibny)
   - Fix issue on empty project to allow developers to only push to protected branches if given permission
   - API: Add enpoints for pipelines
@@ -175,8 +403,22 @@ v 8.11.0
   - Update gitlab_git gem to 10.4.7
   - Simplify SQL queries of marking a todo as done
 
-v 8.10.6 (unreleased)
-  - Fix import/export configuration missing some included attributes
+v 8.10.9
+  - Exclude some pending or inactivated rows in Member scopes
+
+v 8.10.8
+  - Fix information disclosure in issue boards.
+  - Fix privilege escalation in project import.
+
+v 8.10.7
+  - Upgrade Hamlit to 2.6.1. !5873
+  - Upgrade Doorkeeper to 4.2.0. !5881
+
+v 8.10.6
+  - Upgrade Rails to 4.2.7.1 for security fixes. !5781
+  - Restore "Largest repository" sort option on Admin > Projects page. !5797
+  - Fix privilege escalation via project export.
+  - Require administrator privileges to perform a project import.
 
 v 8.10.5
   - Add a data migration to fix some missing timestamps in the members table. !5670
@@ -346,6 +588,7 @@ v 8.10.0
   - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
   - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
   - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
+  - Made project list visibility icon fixed width
   - Set import_url validation to be more strict
   - Memoize MR merged/closed events retrieval
   - Don't render discussion notes when requesting diff tab through AJAX
@@ -392,6 +635,16 @@ v 8.10.0
   - Fix migration corrupting import data for old version upgrades
   - Show tooltip on GitLab export link in new project page
 
+v 8.9.9
+  - Exclude some pending or inactivated rows in Member scopes
+
+v 8.9.8
+  - Upgrade Doorkeeper to 4.2.0. !5881
+
+v 8.9.7
+  - Upgrade Rails to 4.2.7.1 for security fixes. !5781
+  - Require administrator privileges to perform a project import.
+
 v 8.9.6
   - Fix importing of events under notes for GitLab projects. !5154
   - Fix log statements in import/export. !5129
@@ -657,6 +910,12 @@ v 8.9.0
   - Add tooltip to pin/unpin navbar
   - Add new sub nav style to Wiki and Graphs sub navigation
 
+v 8.8.9
+  - Upgrade Doorkeeper to 4.2.0. !5881
+
+v 8.8.8
+  - Upgrade Rails to 4.2.7.1 for security fixes. !5781
+
 v 8.8.7
   - Fix privilege escalation issue with OAuth external users.
   - Ensure references to private repos aren't shown to logged-out users.
@@ -1865,1692 +2124,5 @@ v 8.0.0
   - Redirect from incorrectly cased group or project path to correct one (Francesco Levorato)
   - Removed API calls from CE to CI
 
-v 7.14.3
-  - No changes
-
-v 7.14.2
-  - Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with ISO-encoded files (Stan Hu)
-  - Allow configuration of LDAP attributes GitLab will use for the new user account.
-
-v 7.14.1
-  - Improve abuse reports management from admin area
-  - Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
-  - Disabled DNS lookups for SSH in docker image (Rowan Wookey)
-  - Only include base URL in OmniAuth full_host parameter (Stan Hu)
-  - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu)
-  - Ability to enable SSL verification for Webhooks
-
-v 7.14.0
-  - Fix bug where non-project members of the target project could set labels on new merge requests.
-  - Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller)
-  - Fix redirection after sign in when using auto_sign_in_with_provider
-  - Upgrade gitlab_git to 7.2.14 to ignore CRLFs in .gitmodules (Stan Hu)
-  - Clear cache to prevent listing deleted branches after MR removes source branch (Stan Hu)
-  - Provide more feedback what went wrong if HipChat service failed test (Stan Hu)
-  - Fix bug where backslashes in inline diffs could be dropped (Stan Hu)
-  - Disable turbolinks when linking to Bitbucket import status (Stan Hu)
-  - Fix broken code import and display error messages if something went wrong with creating project (Stan Hu)
-  - Fix corrupted binary files when using API files endpoint (Stan Hu)
-  - Bump Haml to 4.0.7 to speed up textarea rendering (Stan Hu)
-  - Show incompatible projects in Bitbucket import status (Stan Hu)
-  - Fix coloring of diffs on MR Discussion-tab (Gert Goet)
-  - Fix "Network" and "Graphs" pages for branches with encoded slashes (Stan Hu)
-  - Fix errors deleting and creating branches with encoded slashes (Stan Hu)
-  - Always add current user to autocomplete controller to support filter by "Me" (Stan Hu)
-  - Fix multi-line syntax highlighting (Stan Hu)
-  - Fix network graph when branch name has single quotes (Stan Hu)
-  - Add "Confirm user" button in user admin page (Stan Hu)
-  - Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu)
-  - Add support for Unicode filenames in relative links (Hiroyuki Sato)
-  - Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki)
-  - Fix commit data retrieval when branch name has single quotes (Stan Hu)
-  - Check that project was actually created rather than just validated in import:repos task (Stan Hu)
-  - Fix full screen mode for snippet comments (Daniel Gerhardt)
-  - Fix 404 error in files view after deleting the last file in a repository (Stan Hu)
-  - Fix the "Reload with full diff" URL button (Stan Hu)
-  - Fix label read access for unauthenticated users (Daniel Gerhardt)
-  - Fix access to disabled features for unauthenticated users (Daniel Gerhardt)
-  - Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu)
-  - Fix file upload dialog for comment editing (Daniel Gerhardt)
-  - Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu)
-  - Return comments in created order in merge request API (Stan Hu)
-  - Disable internal issue tracker controller if external tracker is used (Stan Hu)
-  - Expire Rails cache entries after two weeks to prevent endless Redis growth
-  - Add support for destroying project milestones (Stan Hu)
-  - Allow custom backup archive permissions
-  - Add project star and fork count, group avatar URL and user/group web URL attributes to API
-  - Show who last edited a comment if it wasn't the original author
-  - Send notification to all participants when MR is merged.
-  - Add ability to manage user email addresses via the API.
-  - Show buttons to add license, changelog and contribution guide if they're missing.
-  - Tweak project page buttons.
-  - Disabled autocapitalize and autocorrect on login field (Daryl Chan)
-  - Mention group and project name in creation, update and deletion notices (Achilleas Pipinellis)
-  - Update gravatar link on profile page to link to configured gravatar host (Ben Bodenmiller)
-  - Remove redis-store TTL monkey patch
-  - Add support for CI skipped status
-  - Fetch code from forks to refs/merge-requests/:id/head when merge request created
-  - Remove comments and email addresses when publicly exposing ssh keys (Zeger-Jan van de Weg)
-  - Add "Check out branch" button to the MR page.
-  - Improve MR merge widget text and UI consistency.
-  - Improve text in MR "How To Merge" modal.
-  - Cache all events
-  - Order commits by date when comparing branches
-  - Fix bug causing error when the target branch of a symbolic ref was deleted
-  - Include branch/tag name in archive file and directory name
-  - Add dropzone upload progress
-  - Add a label for merged branches on branches page (Florent Baldino)
-  - Detect .mkd and .mkdn files as markdown (Ben Boeckel)
-  - Fix: User search feature in admin area does not respect filters
-  - Set max-width for README, issue and merge request description for easier read on big screens
-  - Update Flowdock integration to support new Flowdock API (Boyan Tabakov)
-  - Remove author from files view (Sven Strickroth)
-  - Fix infinite loop when SAML was incorrectly configured.
-
-v 7.13.5
-  - Satellites reverted
-
-v 7.13.4
-  - Allow users to send abuse reports
-
-v 7.13.3
-  - Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
-  - Allow users to send abuse reports
-  - Remove satellites
-  - Link username to profile on Group Members page (Tom Webster)
-
-v 7.13.2
-  - Fix randomly failed spec
-  - Create project services on Project creation
-  - Add admin_merge_request ability to Developer level and up
-  - Fix Error 500 when browsing projects with no HEAD (Stan Hu)
-  - Fix labels / assignee / milestone for the merge requests when issues are disabled
-  - Show the first tab automatically on MergeRequests#new
-  - Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt)
-  - Fix Gmail Actions
-
-v 7.13.1
-  - Fix: Label modifications are not reflected in existing notes and in the issue list
-  - Fix: Label not shown in the Issue list, although it's set through web interface
-  - Fix: Group/project references are linked incorrectly
-  - Improve documentation
-  - Fix of migration: Check if session_expire_delay column exists before adding the column
-  - Fix: ActionView::Template::Error
-  - Fix: "Create Merge Request" isn't always shown in event for newly pushed branch
-  - Fix bug causing "Remove source-branch" option not to work for merge requests from the same project.
-  - Render Note field hints consistently for "new" and "edit" forms
-
-v 7.13.0
-  - Remove repository graph log to fix slow cache updates after push event (Stan Hu)
-  - Only enable HSTS header for HTTPS and port 443 (Stan Hu)
-  - Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
-  - Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
-  - Add branch switching support for graphs (Daniel Gerhardt)
-  - Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
-  - Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
-  - Add support for unlocking users in admin settings (Stan Hu)
-  - Add Irker service configuration options (Stan Hu)
-  - Fix order of issues imported from GitHub (Hiroyuki Sato)
-  - Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart)
-  - Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI
-  - Add `two_factor_enabled` field to admin user API (Stan Hu)
-  - Fix invalid timestamps in RSS feeds (Rowan Wookey)
-  - Fix downloading of patches on public merge requests when user logged out (Stan Hu)
-  - Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu)
-  - Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu)
-  - Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu)
-  - Support commenting on diffs in side-by-side mode (Stan Hu)
-  - Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu)
-  - Return 40x error codes if branch could not be deleted in UI (Stan Hu)
-  - Remove project visibility icons from dashboard projects list
-  - Rename "Design" profile settings page to "Preferences".
-  - Allow users to customize their default Dashboard page.
-  - Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8
-  - Admin can edit and remove user identities
-  - Convert CRLF newlines to LF when committing using the web editor.
-  - API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged.
-  - Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled.
-  - Show a user's Two-factor Authentication status in the administration area.
-  - Explicit error when commit not found in the CI
-  - Improve performance for issue and merge request pages
-  - Users with guest access level can not set assignee, labels or milestones for issue and merge request
-  - Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels
-  - Better performance for pages with events list, issues list and commits list
-  - Faster automerge check and merge itself when source and target branches are in same repository
-  - Correctly show anonymous authorized applications under Profile > Applications.
-  - Query Optimization in MySQL.
-  - Allow users to be blocked and unblocked via the API
-  - Use native Postgres database cleaning during backup restore
-  - Redesign project page. Show README as default instead of activity. Move project activity to separate page
-  - Make left menu more hierarchical and less contextual by adding back item at top
-  - A fork can’t have a visibility level that is greater than the original project.
-  - Faster code search in repository and wiki. Fixes search page timeout for big repositories
-  - Allow administrators to disable 2FA for a specific user
-  - Add error message for SSH key linebreaks
-  - Store commits count in database (will populate with valid values only after first push)
-  - Rebuild cache after push to repository in background job
-  - Fix transferring of project to another group using the API.
-
-v 7.12.2
-  - Correctly show anonymous authorized applications under Profile > Applications.
-  - Faster automerge check and merge itself when source and target branches are in same repository
-  - Audit log for user authentication
-  - Allow custom label to be set for authentication providers.
-
-v 7.12.1
-  - Fix error when deleting a user who has projects (Stan Hu)
-  - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
-  - Add SAML to list of social_provider (Matt Firtion)
-  - Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets)
-  - Fix closed merge request scope at milestone page (Dmitriy Zaporozhets)
-  - Revert merge request states renaming
-  - Fix hooks for web based events with external issue references (Daniel Gerhardt)
-  - Improve performance for issue and merge request pages
-  - Compress database dumps to reduce backup size
-
-v 7.12.0
-  - Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu)
-  - Disable changing of target branch in new merge request page when a branch has already been specified (Stan Hu)
-  - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
-  - Update oauth button logos for Twitter and Google to recommended assets
-  - Update browser gem to version 0.8.0 for IE11 support (Stan Hu)
-  - Fix timeout when rendering file with thousands of lines.
-  - Add "Remember me" checkbox to LDAP signin form.
-  - Add session expiration delay configuration through UI application settings
-  - Don't notify users mentioned in code blocks or blockquotes.
-  - Omit link to generate labels if user does not have access to create them (Stan Hu)
-  - Show warning when a comment will add 10 or more people to the discussion.
-  - Disable changing of the source branch in merge request update API (Stan Hu)
-  - Shorten merge request WIP text.
-  - Add option to disallow users from registering any application to use GitLab as an OAuth provider
-  - Support editing target branch of merge request (Stan Hu)
-  - Refactor permission checks with issues and merge requests project settings (Stan Hu)
-  - Fix Markdown preview not working in Edit Milestone page (Stan Hu)
-  - Fix Zen Mode not closing with ESC key (Stan Hu)
-  - Allow HipChat API version to be blank and default to v2 (Stan Hu)
-  - Add file attachment support in Milestone description (Stan Hu)
-  - Fix milestone "Browse Issues" button.
-  - Set milestone on new issue when creating issue from index with milestone filter active.
-  - Make namespace API available to all users (Stan Hu)
-  - Add webhook support for note events (Stan Hu)
-  - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu)
-  - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu)
-  - Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu)
-  - Fix git blame syntax highlighting when different commits break up lines (Stan Hu)
-  - Add "Resend confirmation e-mail" link in profile settings (Stan Hu)
-  - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka)
-  - Disabled expansion of top/bottom blobs for new file diffs
-  - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka)
-  - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka)
-  - Use the user list from the target project in a merge request (Stan Hu)
-  - Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen)
-  - Add validation to wiki page creation (only [a-zA-Z0-9/_-] are allowed) (Jeroen van Baarsen)
-  - Fix new/empty milestones showing 100% completion value (Jonah Bishop)
-  - Add a note when an Issue or Merge Request's title changes
-  - Consistently refer to MRs as either Merged or Closed.
-  - Add Merged tab to MR lists.
-  - Prefix EmailsOnPush email subject with `[Git]`.
-  - Group project contributions by both name and email.
-  - Clarify navigation labels for Project Settings and Group Settings.
-  - Move user avatar and logout button to sidebar
-  - You can not remove user if he/she is an only owner of group
-  - User should be able to leave group. If not - show him proper message
-  - User has ability to leave project
-  - Add SAML support as an omniauth provider
-  - Allow to configure a URL to show after sign out
-  - Add an option to automatically sign-in with an Omniauth provider
-  - GitLab CI service sends .gitlab-ci.yml in each push call
-  - When remove project - move repository and schedule it removal
-  - Improve group removing logic
-  - Trigger create-hooks on backup restore task
-  - Add option to automatically link omniauth and LDAP identities
-  - Allow special character in users bio. I.e.: I <3 GitLab
-
-v 7.11.4
-  - Fix missing bullets when creating lists
-  - Set rel="nofollow" on external links
-
-v 7.11.3
-  - no changes
-  - Fix upgrader script (Martins Polakovs)
-
-v 7.11.2
-  - no changes
-
-v 7.11.1
-  - no changes
-
-v 7.11.0
-  - Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger)
-  - Get editing comments to work in Chrome 43 again.
-  - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu)
-  - Don't show duplicate deploy keys
-  - Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger)
-  - Make the first branch pushed to an empty repository the default HEAD (Stan Hu)
-  - Fix broken view when using a tag to display a tree that contains git submodules (Stan Hu)
-  - Make Reply-To config apply to change e-mail confirmation and other Devise notifications (Stan Hu)
-  - Add application setting to restrict user signups to e-mail domains (Stan Hu)
-  - Don't allow a merge request to be merged when its title starts with "WIP".
-  - Add a page title to every page.
-  - Allow primary email to be set to an email that you've already added.
-  - Fix clone URL field and X11 Primary selection (Dmitry Medvinsky)
-  - Ignore invalid lines in .gitmodules
-  - Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu)
-  - Redirect to sign in page after signing out.
-  - Fix "Hello @username." references not working by no longer allowing usernames to end in period.
-  - Fix "Revspec not found" errors when viewing diffs in a forked project with submodules (Stan Hu)
-  - Improve project page UI
-  - Fix broken file browsing with relative submodule in personal projects (Stan Hu)
-  - Add "Reply quoting selected text" shortcut key (`r`)
-  - Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention.
-  - Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention.
-  - When use change branches link at MR form - save source branch selection instead of target one
-  - Improve handling of large diffs
-  - Added GitLab Event header for project hooks
-  - Add Two-factor authentication (2FA) for GitLab logins
-  - Show Atom feed buttons everywhere where applicable.
-  - Add project activity atom feed.
-  - Don't crash when an MR from a fork has a cross-reference comment from the target project on one of its commits.
-  - Explain how to get a new password reset token in welcome emails
-  - Include commit comments in MR from a forked project.
-  - Group milestones by title in the dashboard and all other issue views.
-  - Query issues, merge requests and milestones with their IID through API (Julien Bianchi)
-  - Add default project and snippet visibility settings to the admin web UI.
-  - Show incompatible projects in Google Code import status (Stan Hu)
-  - Fix bug where commit data would not appear in some subdirectories (Stan Hu)
-  - Task lists are now usable in comments, and will show up in Markdown previews.
-  - Fix bug where avatar filenames were not actually deleted from the database during removal (Stan Hu)
-  - Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu)
-  - Protect OmniAuth request phase against CSRF.
-  - Don't send notifications to mentioned users that don't have access to the project in question.
-  - Add search issues/MR by number
-  - Change plots to bar graphs in commit statistics screen
-  - Move snippets UI to fluid layout
-  - Improve UI for sidebar. Increase separation between navigation and content
-  - Improve new project command options (Ben Bodenmiller)
-  - Add common method to force UTF-8 and use it to properly handle non-ascii OAuth user properties (Onur Küçük)
-  - Prevent sending empty messages to HipChat (Chulki Lee)
-  - Improve UI for mobile phones on dashboard and project pages
-  - Add room notification and message color option for HipChat
-  - Allow to use non-ASCII letters and dashes in project and namespace name. (Jakub Jirutka)
-  - Add footnotes support to Markdown (Guillaume Delbergue)
-  - Add current_sign_in_at to UserFull REST api.
-  - Make Sidekiq MemoryKiller shutdown signal configurable
-  - Add "Create Merge Request" buttons to commits and branches pages and push event.
-  - Show user roles by comments.
-  - Fix automatic blocking of auto-created users from Active Directory.
-  - Call merge request webhook for each new commits (Arthur Gautier)
-  - Use SIGKILL by default in Sidekiq::MemoryKiller
-  - Fix mentioning of private groups.
-  - Add style for <kbd> element in markdown
-  - Spin spinner icon next to "Checking for CI status..." on MR page.
-  - Fix reference links in dashboard activity and ATOM feeds.
-  - Ensure that the first added admin performs repository imports
-
-v 7.10.4
-  - Fix migrations broken in 7.10.2
-  - Make tags for GitLab installations running on MySQL case sensitive
-  - Get Gitorious importer to work again.
-  - Fix adding new group members from admin area
-  - Fix DB error when trying to tag a repository (Stan Hu)
-  - Fix Error 500 when searching Wiki pages (Stan Hu)
-  - Unescape branch names in compare commit (Stan Hu)
-  - Order commit comments chronologically in API.
-
-v 7.10.2
-  - Fix CI links on MR page
-
-v 7.10.0
-  - Ignore submodules that are defined in .gitmodules but are checked in as directories.
-  - Allow projects to be imported from Google Code.
-  - Remove access control for uploaded images to fix broken images in emails (Hannes Rosenögger)
-  - Allow users to be invited by email to join a group or project.
-  - Don't crash when project repository doesn't exist.
-  - Add config var to block auto-created LDAP users.
-  - Don't use HTML ellipsis in EmailsOnPush subject truncated commit message.
-  - Set EmailsOnPush reply-to address to committer email when enabled.
-  - Fix broken file browsing with a submodule that contains a relative link (Stan Hu)
-  - Fix persistent XSS vulnerability around profile website URLs.
-  - Fix project import URL regex to prevent arbitary local repos from being imported.
-  - Fix directory traversal vulnerability around uploads routes.
-  - Fix directory traversal vulnerability around help pages.
-  - Don't leak existence of project via search autocomplete.
-  - Don't leak existence of group or project via search.
-  - Fix bug where Wiki pages that included a '/' were no longer accessible (Stan Hu)
-  - Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu)
-  - Add a rake task to check repository integrity with `git fsck`
-  - Add ability to configure Reply-To address in gitlab.yml (Stan Hu)
-  - Move current user to the top of the list in assignee/author filters (Stan Hu)
-  - Fix broken side-by-side diff view on merge request page (Stan Hu)
-  - Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu)
-  - Allow HTML tags in Markdown input
-  - Fix code unfold not working on Compare commits page (Stan Hu)
-  - Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik)
-  - Fix "Import projects from" button to show the correct instructions (Stan Hu)
-  - Fix dots in Wiki slugs causing errors (Stan Hu)
-  - Make maximum attachment size configurable via Application Settings (Stan Hu)
-  - Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg)
-  - Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu)
-  - Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu)
-  - Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu)
-  - enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger)
-  - Fix a link in the patch update guide
-  - Add a service to support external wikis (Hannes Rosenögger)
-  - Omit the "email patches" link and fix plain diff view for merge commits
-  - List new commits for newly pushed branch in activity view.
-  - Add sidetiq gem dependency to match EE
-  - Add changelog, license and contribution guide links to project tab bar.
-  - Improve diff UI
-  - Fix alignment of navbar toggle button (Cody Mize)
-  - Fix checkbox rendering for nested task lists
-  - Identical look of selectboxes in UI
-  - Upgrade the gitlab_git gem to version 7.1.3
-  - Move "Import existing repository by URL" option to button.
-  - Improve error message when save profile has error.
-  - Passing the name of pushed ref to CI service (requires GitLab CI 7.9+)
-  - Add location field to user profile
-  - Fix print view for markdown files and wiki pages
-  - Fix errors when deleting old backups
-  - Improve GitLab performance when working with git repositories
-  - Add tag message and last commit to tag hook (Kamil Trzciński)
-  - Restrict permissions on backup files
-  - Improve oauth accounts UI in profile page
-  - Add ability to unlink connected accounts
-  - Replace commits calendar with faster contribution calendar that includes issues and merge requests
-  - Add inifinite scroll to user page activity
-  - Don't include system notes in issue/MR comment count.
-  - Don't mark merge request as updated when merge status relative to target branch changes.
-  - Link note avatar to user.
-  - Make Git-over-SSH errors more descriptive.
-  - Fix EmailsOnPush.
-  - Refactor issue filtering
-  - AJAX selectbox for issue assignee and author filters
-  - Fix issue with missing options in issue filtering dropdown if selected one
-  - Prevent holding Control-Enter or Command-Enter from posting comment multiple times.
-  - Prevent note form from being cleared when submitting failed.
-  - Improve file icons rendering on tree (Sullivan Sénéchal)
-  - API: Add pagination to project events
-  - Get issue links in notification mail to work again.
-  - Don't show commit comment button when user is not signed in.
-  - Fix admin user projects lists.
-  - Don't leak private group existence by redirecting from namespace controller to group controller.
-  - Ability to skip some items from backup (database, respositories or uploads)
-  - Archive repositories in background worker.
-  - Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace.
-  - Project labels are now available over the API under the "tag_list" field (Cristian Medina)
-  - Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz)
-  - Fix and improve help rendering (Sullivan Sénéchal)
-  - Fix final line in EmailsOnPush email diff being rendered as error.
-  - Prevent duplicate Buildkite service creation.
-  - Fix git over ssh errors 'fatal: protocol error: bad line length character'
-  - Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled
-  - Bust group page project list cache when namespace name or path changes.
-  - Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded
-  - Allow user to choose a public email to show on public profile
-  - Remove truncation from issue titles on milestone page (Jason Blanchard)
-  - Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
-  - Fix merge request comments on files with multiple commits
-  - Fix Resource Owner Password Authentication Flow
-  - Add icons to Add dropdown items.
-  - Allow admin to create public deploy keys that are accessible to any project.
-  - Warn when gitlab-shell version doesn't match requirement.
-  - Skip email confirmation when set by admin or via LDAP.
-  - Only allow users to reference groups, projects, issues, MRs, commits they have access to.
-
-v 7.9.4
-  - Security: Fix project import URL regex to prevent arbitary local repos from being imported
-  - Fixed issue where only 25 commits would load in file listings
-  - Fix LDAP identities  after config update
-
-v 7.9.3
-  - Contains no changes
-
-v 7.9.2
-  - Contains no changes
-
-v 7.9.1
-  - Include missing events and fix save functionality in admin service template settings form (Stan Hu)
-  - Fix "Import projects from" button to show the correct instructions (Stan Hu)
-  - Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu)
-  - Fix for LDAP with commas in DN
-  - Fix missing events and in admin Slack service template settings form (Stan Hu)
-  - Don't show commit comment button when user is not signed in.
-  - Downgrade gemnasium-gitlab-service gem
-
-v 7.9.0
-  - Add HipChat integration documentation (Stan Hu)
-  - Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu)
-  - Fix broken email images (Hannes Rosenögger)
-  - Automatically config git if user forgot, where possible (Zeger-Jan van de Weg)
-  - Fix mass SQL statements on initial push (Hannes Rosenögger)
-  - Add tag push notifications and normalize HipChat and Slack messages to be consistent (Stan Hu)
-  - Add comment notification events to HipChat and Slack services (Stan Hu)
-  - Add issue and merge request events to HipChat and Slack services (Stan Hu)
-  - Fix merge request URL passed to Webhooks. (Stan Hu)
-  - Fix bug that caused a server error when editing a comment to "+1" or "-1" (Stan Hu)
-  - Fix code preview theme setting for comments, issues, merge requests, and snippets (Stan Hu)
-  - Move labels/milestones tabs to sidebar
-  - Upgrade Rails gem to version 4.1.9.
-  - Improve error messages for file edit failures
-  - Improve UI for commits, issues and merge request lists
-  - Fix commit comments on first line of diff not rendering in Merge Request Discussion view.
-  - Allow admins to override restricted project visibility settings.
-  - Move restricted visibility settings from gitlab.yml into the web UI.
-  - Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev)
-  - Save web edit in new branch
-  - Fix ordering of imported but unchanged projects (Marco Wessel)
-  - Mobile UI improvements: make aside content expandable
-  - Expose avatar_url in projects API
-  - Fix checkbox alignment on the application settings page.
-  - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger)
-  - Fix mass-unassignment of issues (Robert Speicher)
-  - Fix hidden diff comments in merge request discussion view
-  - Allow user confirmation to be skipped for new users via API
-  - Add a service to send updates to an Irker gateway (Romain Coltel)
-  - Add brakeman (security scanner for Ruby on Rails)
-  - Slack username and channel options
-  - Add grouped milestones from all projects to dashboard.
-  - Webhook sends pusher email as well as commiter
-  - Add Bitbucket omniauth provider.
-  - Add Bitbucket importer.
-  - Support referencing issues to a project whose name starts with a digit
-  - Condense commits already in target branch when updating merge request source branch.
-  - Send notifications and leave system comments when bulk updating issues.
-  - Automatically link commit ranges to compare page: sha1...sha4 or sha1..sha4 (includes sha1 in comparison)
-  - Move groups page from profile to dashboard
-  - Starred projects page at dashboard
-  - Blocking user does not remove him/her from project/groups but show blocked label
-  - Change subject of EmailsOnPush emails to include namespace, project and branch.
-  - Change subject of EmailsOnPush emails to include first commit message when multiple were pushed.
-  - Remove confusing footer from EmailsOnPush mail body.
-  - Add list of changed files to EmailsOnPush emails.
-  - Add option to send EmailsOnPush emails from committer email if domain matches.
-  - Add option to disable code diffs in EmailOnPush emails.
-  - Wrap commit message in EmailsOnPush email.
-  - Send EmailsOnPush emails when deleting commits using force push.
-  - Fix EmailsOnPush email comparison link to include first commit.
-  - Fix highliht of selected lines in file
-  - Reject access to group/project avatar if the user doesn't have access.
-  - Add database migration to clean group duplicates with same path and name (Make sure you have a backup before update)
-  - Add GitLab active users count to rake gitlab:check
-  - Starred projects page at dashboard
-  - Make email display name configurable
-  - Improve json validation in hook data
-  - Use Emoji One
-  - Updated emoji help documentation to properly reference EmojiOne.
-  - Fix missing GitHub organisation repositories on import page.
-  - Added blue theme
-  - Remove annoying notice messages when create/update merge request
-  - Allow smb:// links in Markdown text.
-  - Filter merge request by title or description at Merge Requests page
-  - Block user if he/she was blocked in Active Directory
-  - Fix import pages not working after first load.
-  - Use custom LDAP label in LDAP signin form.
-  - Execute hooks and services when branch or tag is created or deleted through web interface.
-  - Block and unblock user if he/she was blocked/unblocked in Active Directory
-  - Raise recommended number of unicorn workers from 2 to 3
-  - Use same layout and interactivity for project members as group members.
-  - Prevent gitlab-shell character encoding issues by receiving its changes as raw data.
-  - Ability to unsubscribe/subscribe to issue or merge request
-  - Delete deploy key when last connection to a project is destroyed.
-  - Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther)
-  - Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup)
-  - Add canceled status for CI
-  - Send EmailsOnPush email when branch or tag is created or deleted.
-  - Faster merge request processing for large repository
-  - Prevent doubling AJAX request with each commit visit via Turbolink
-  - Prevent unnecessary doubling of js events on import pages and user calendar
-
-v 7.8.4
-  - Fix issue_tracker_id substitution in custom issue trackers
-  - Fix path and name duplication in namespaces
-
-v 7.8.3
-  - Bump version of gitlab_git fixing annotated tags without message
-
-v 7.8.2
-  - Fix service migration issue when upgrading from versions prior to 7.3
-  - Fix setting of the default use project limit via admin UI
-  - Fix showing of already imported projects for GitLab and Gitorious importers
-  - Fix response of push to repository to return "Not found" if user doesn't have access
-  - Fix check if user is allowed to view the file attachment
-  - Fix import check for case sensetive namespaces
-  - Increase timeout for Git-over-HTTP requests to 1 hour since large pulls/pushes can take a long time.
-  - Properly handle autosave local storage exceptions.
-  - Escape wildcards when searching LDAP by username.
-
-v 7.8.1
-  - Fix run of custom post receive hooks
-  - Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3
-  - Fix the warning for LDAP users about need to set password
-  - Fix avatars which were not shown for non logged in users
-  - Fix urls for the issues when relative url was enabled
-
-v 7.8.0
-  - Fix access control and protection against XSS for note attachments and other uploads.
-  - Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
-  - Make project search case insensitive (Hannes Rosenögger)
-  - Include issue/mr participants in list of recipients for reassign/close/reopen emails
-  - Expose description in groups API
-  - Better UI for project services page
-  - Cleaner UI for web editor
-  - Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
-  - Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen)
-  - View note image attachments in new tab when clicked instead of downloading them
-  - Improve sorting logic in UI and API. Explicitly define what sorting method is used by default
-  - Fix overflow at sidebar when have several items
-  - Add notes for label changes in issue and merge requests
-  - Show tags in commit view (Hannes Rosenögger)
-  - Only count a user's vote once on a merge request or issue (Michael Clarke)
-  - Increase font size when browse source files and diffs
-  - Service Templates now let you set default values for all services
-  - Create new file in empty repository using GitLab UI
-  - Ability to clone project using oauth2 token
-  - Upgrade Sidekiq gem to version 3.3.0
-  - Stop git zombie creation during force push check
-  - Show success/error messages for test setting button in services
-  - Added Rubocop for code style checks
-  - Fix commits pagination
-  - Async load a branch information at the commit page
-  - Disable blacklist validation for project names
-  - Allow configuring protection of the default branch upon first push (Marco Wessel)
-  - Add gitlab.com importer
-  - Add an ability to login with gitlab.com
-  - Add a commit calendar to the user profile (Hannes Rosenögger)
-  - Submit comment on command-enter
-  - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`.
-  - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" (Julien Bianchi and Hannes Rosenögger)
-  - Fix long broadcast message cut-off on left sidebar (Visay Keo)
-  - Add Project Avatars (Steven Thonus and Hannes Rosenögger)
-  - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
-  - Edit group members via API
-  - Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks)
-  - Add action property to merge request hook (Julien Bianchi)
-  - Remove duplicates from group milestone participants list.
-  - Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger)
-  - API: Access groups with their path (Julien Bianchi)
-  - Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard)
-  - Allow notification email to be set separately from primary email.
-  - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
-  - Don't have Markdown preview fail for long comments/wiki pages.
-  - When test webhook - show error message instead of 500 error page if connection to hook url was reset
-  - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
-  - Added persistent collapse button for left side nav bar (Jason Blanchard)
-  - Prevent losing unsaved comments by automatically restoring them when comment page is loaded again.
-  - Don't allow page to be scaled on mobile.
-  - Clean the username acquired from OAuth/LDAP so it doesn't fail username validation and block signing up.
-  - Show assignees in merge request index page (Kelvin Mutuma)
-  - Link head panel titles to relevant root page.
-  - Allow users that signed up via OAuth to set their password in order to use Git over HTTP(S).
-  - Show users button to share their newly created public or internal projects on twitter
-  - Add quick help links to the GitLab pricing and feature comparison pages.
-  - Fix duplicate authorized applications in user profile and incorrect application client count in admin area.
-  - Make sure Markdown previews always use the same styling as the eventual destination.
-  - Remove deprecated Group#owner_id from API
-  - Show projects user contributed to on user page. Show stars near project on user page.
-  - Improve database performance for GitLab
-  - Add Asana service (Jeremy Benoist)
-  - Improve project webhooks with extra data
-
-v 7.7.2
-  - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
-  - Fix issue when LDAP user can't login with existing GitLab account
-
-v 7.7.1
-  - Improve mention autocomplete performance
-  - Show setup instructions for GitHub import if disabled
-  - Allow use http for OAuth applications
-
-v 7.7.0
-  - Import from GitHub.com feature
-  - Add Jetbrains Teamcity CI service (Jason Lippert)
-  - Mention notification level
-  - Markdown preview in wiki (Yuriy Glukhov)
-  - Raise group avatar filesize limit to 200kb
-  - OAuth applications feature
-  - Show user SSH keys in admin area
-  - Developer can push to protected branches option
-  - Set project path instead of project name in create form
-  - Block Git HTTP access after 10 failed authentication attempts
-  - Updates to the messages returned by API (sponsored by O'Reilly Media)
-  - New UI layout with side navigation
-  - Add alert message in case of outdated browser (IE < 10)
-  - Added API support for sorting projects
-  - Update gitlab_git to version 7.0.0.rc14
-  - Add API project search filter option for authorized projects
-  - Fix File blame not respecting branch selection
-  - Change some of application settings on fly in admin area UI
-  - Redesign signin/signup pages
-  - Close standard input in Gitlab::Popen.popen
-  - Trigger GitLab CI when push tags
-  - When accept merge request - do merge using sidaekiq job
-  - Enable web signups by default
-  - Fixes for diff comments: drag-n-drop images, selecting images
-  - Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update
-  - Remove password strength indicator
-
-v 7.6.0
-  - Fork repository to groups
-  - New rugged version
-  - Add CRON=1 backup setting for quiet backups
-  - Fix failing wiki restore
-  - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
-  - Monokai highlighting style now more faithful to original design (Mark Riedesel)
-  - Create project with repository in synchrony
-  - Added ability to create empty repo or import existing one if project does not have repository
-  - Reactivate highlight.js language autodetection
-  - Mobile UI improvements
-  - Change maximum avatar file size from 100KB to 200KB
-  - Strict validation for snippet file names
-  - Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada)
-  - In the docker directory is a container template based on the Omnibus packages.
-  - Update Sidekiq to version 2.17.8
-  - Add author filter to project issues and merge requests pages
-  - Atom feed for user activity
-  - Support multiple omniauth providers for the same user
-  - Rendering cross reference in issue title and tooltip for merge request
-  - Show username in comments
-  - Possibility to create Milestones or Labels when Issues are disabled
-  - Fix bug with showing gpg signature in tag
-
-v 7.5.3
-  - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
-
-v 7.5.2
-  - Don't log Sidekiq arguments by default
-  - Fix restore of wiki repositories from backups
-
-v 7.5.1
-  - Add missing timestamps to 'members' table
-
-v 7.5.0
-  - API: Add support for Hipchat (Kevin Houdebert)
-  - Add time zone configuration in gitlab.yml (Sullivan Senechal)
-  - Fix LDAP authentication for Git HTTP access
-  - Run 'GC.start' after every EmailsOnPushWorker job
-  - Fix LDAP config lookup for provider 'ldap'
-  - Drop all sequences during Postgres database restore
-  - 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
-  - Performance improvements
-  - Fix post-receive issue for projects with deleted forks
-  - New gitlab-shell version with custom hooks support
-  - Improve code
-  - GitLab CI 5.2+ support (does not support older versions)
-  - Fixed bug when you can not push commits starting with 000000 to protected branches
-  - Added a password strength indicator
-  - Change project name and path in one form
-  - Display renamed files in diff views (Vinnie Okada)
-  - Fix raw view for public snippets
-  - Use secret token with GitLab internal API.
-  - Add missing timestamps to 'members' table
-
-v 7.4.5
-  - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
-
-v 7.4.4
-  - No changes
-
-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
-  - 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
-  - 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
-  - Internal ForkService: Prepare support for fork to a given namespace
-  - 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
-  - Fix error 500 when viewing diff on a file with changed permissions
-  - Fix adding comments to MR when source branch is master
-  - Fix error 500 when searching description contains relative link
-
-v 7.3.0
-  - Always set the 'origin' remote in satellite actions
-  - Write authorized_keys in tmp/ during tests
-  - Use sockets to connect to Redis
-  - Add dormant New Relic gem (can be enabled via environment variables)
-  - Expire Rack sessions after 1 week
-  - Cleaner signin/signup pages
-  - Improved comments UI
-  - Better search with filtering, pagination etc
-  - Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov)
-  - Prevent project stars duplication when fork project
-  - Use the default Unicorn socket backlog value of 1024
-  - Support Unix domain sockets for Redis
-  - Store session Redis keys in 'session:gitlab:' namespace
-  - Deprecate LDAP account takeover based on partial LDAP email / GitLab username match
-  - Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy)
-  - Keyboard shortcuts for productivity (Robert Schilling)
-  - API: filter issues by state (Julien Bianchi)
-  - API: filter issues by labels (Julien Bianchi)
-  - Add system hook for ssh key changes
-  - Add blob permalink link (Ciro Santilli)
-  - Create annotated tags through UI and API (Sean Edge)
-  - Snippets search (Charles Bushong)
-  - Comment new push to existing MR
-  - Add 'ci' to the blacklist of forbidden names
-  - Improve text filtering on issues page
-  - Comment & Close button
-  - Process git push --all much faster
-  - Don't allow edit of system notes
-  - Project wiki search (Ralf Seidler)
-  - Enabled Shibboleth authentication support (Matus Banas)
-  - Zen mode (fullscreen) for issues/MR/notes (Robert Schilling)
-  - Add ability to configure webhook timeout via gitlab.yml (Wes Gurney)
-  - Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media)
-  - Add Redis socket support to 'rake gitlab:shell:install'
-
-v 7.2.1
-  - Delete orphaned labels during label migration (James Brooks)
-  - Security: prevent XSS with stricter MIME types for raw repo files
-
-v 7.2.0
-  - Explore page
-  - Add project stars (Ciro Santilli)
-  - Log Sidekiq arguments
-  - Better labels: colors, ability to rename and remove
-  - Improve the way merge request collects diffs
-  - Improve compare page for large diffs
-  - Expose the full commit message via API
-  - Fix 500 error on repository rename
-  - Fix bug when MR download patch return invalid diff
-  - Test gitlab-shell integration
-  - Repository import timeout increased from 2 to 4 minutes allowing larger repos to be imported
-  - API for labels (Robert Schilling)
-  - API: ability to set an import url when creating project for specific user
-
-v 7.1.1
-  - Fix cpu usage issue in Firefox
-  - Fix redirect loop when changing password by new user
-  - Fix 500 error on new merge request page
-
-v 7.1.0
-  - Remove observers
-  - Improve MR discussions
-  - Filter by description on Issues#index page
-  - Fix bug with namespace select when create new project page
-  - Show README link after description for non-master members
-  - Add @all mention for comments
-  - Dont show reply button if user is not signed in
-  - Expose more information for issues with webhook
-  - Add a mention of the merge request into the default merge request commit message
-  - Improve code highlight, introduce support for more languages like Go, Clojure, Erlang etc
-  - Fix concurrency issue in repository download
-  - Dont allow repository name start with ?
-  - Improve email threading (Pierre de La Morinerie)
-  - Cleaner help page
-  - Group milestones
-  - Improved email notifications
-  - Contributors API (sponsored by Mobbr)
-  - Fix LDAP TLS authentication (Boris HUISGEN)
-  - Show VERSION information on project sidebar
-  - Improve branch removal logic when accept MR
-  - Fix bug where comment form is spawned inside the Reply button
-  - Remove Dir.chdir from Satellite#lock for thread-safety
-  - Increased default git max_size value from 5MB to 20MB in gitlab.yml. Please update your configs!
-  - Show error message in case of timeout in satellite when create MR
-  - Show first 100 files for huge diff instead of hiding all
-  - Change default admin email from admin at local.host to admin at example.com
-
-v 7.0.0
-  - The CPU no longer overheats when you hold down the spacebar
-  - Improve edit file UI
-  - Add ability to upload group avatar when create
-  - Protected branch cannot be removed
-  - Developers can remove normal branches with UI
-  - Remove branch via API (sponsored by O'Reilly Media)
-  - Move protected branches page to Project settings area
-  - Redirect to Files view when create new branch via UI
-  - Drag and drop upload of image in every markdown-area (Earle Randolph Bunao and Neil Francis Calabroso)
-  - Refactor the markdown relative links processing
-  - Make it easier to implement other CI services for GitLab
-  - Group masters can create projects in group
-  - Deprecate ruby 1.9.3 support
-  - Only masters can rewrite/remove git tags
-  - Add X-Frame-Options SAMEORIGIN to Nginx config so Sidekiq admin is visible
-  - UI improvements
-  - Case-insensetive search for issues
-  - Update to rails 4.1
-  - Improve performance of application for projects and groups with a lot of members
-  - Formally support Ruby 2.1
-  - Include Nginx gitlab-ssl config
-  - Add manual language detection for highlight.js
-  - Added example.com/:username routing
-  - Show notice if your profile is public
-  - UI improvements for mobile devices
-  - Improve diff rendering performance
-  - Drag-n-drop for issues and merge requests between states at milestone page
-  - Fix '0 commits' message for huge repositories on project home page
-  - Prevent 500 error page when visit commit page from large repo
-  - Add notice about huge push over http to unicorn config
-  - File action in satellites uses default 30 seconds timeout instead of old 10 seconds one
-  - Overall performance improvements
-  - Skip init script check on omnibus-gitlab
-  - Be more selective when killing stray Sidekiqs
-  - Check LDAP user filter during sign-in
-  - Remove wall feature (no data loss - you can take it from database)
-  - Dont expose user emails via API unless you are admin
-  - Detect issues closed by Merge Request description
-  - Better email subject lines from email on push service (Alex Elman)
-  - Enable identicon for gravatar be default
-
-v 6.9.2
-  - Revert the commit that broke the LDAP user filter
-
-v 6.9.1
-  - Fix scroll to highlighted line
-  - Fix the pagination on load for commits page
-
-v 6.9.0
-  - Store Rails cache data in the Redis `cache:gitlab` namespace
-  - Adjust MySQL limits for existing installations
-  - Add db index on project_id+iid column. This prevents duplicate on iid (During migration duplicates will be removed)
-  - Markdown preview or diff during editing via web editor (Evgeniy Sokovikov)
-  - Give the Rails cache its own Redis namespace
-  - Add ability to set different ssh host, if different from http/https
-  - Fix syntax highlighting for code comments blocks
-  - Improve comments loading logic
-  - Stop refreshing comments when the tab is hidden
-  - Improve issue and merge request mobile UI (Drew Blessing)
-  - Document how to convert a backup to PostgreSQL
-  - Fix locale bug in backup manager
-  - Fix can not automerge when MR description is too long
-  - Fix wiki backup skip bug
-  - Two Step MR creation process
-  - Remove unwanted files from satellite working directory with git clean -fdx
-  - Accept merge request via API (sponsored by O'Reilly Media)
-  - Add more access checks during API calls
-  - Block SSH access for 'disabled' Active Directory users
-  - Labels for merge requests (Drew Blessing)
-  - Threaded emails by setting a Message-ID (Philip Blatter)
-
-v 6.8.0
-  - Ability to at mention users that are participating in issue and merge req. discussion
-  - Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu)
-  - Make user search case-insensitive (Christopher Arnold)
-  - Remove omniauth-ldap nickname bug workaround
-  - Drop all tables before restoring a Postgres backup
-  - Make the repository downloads path configurable
-  - Create branches via API (sponsored by O'Reilly Media)
-  - Changed permission of gitlab-satellites directory not to be world accessible
-  - Protected branch does not allow force push
-  - Fix popen bug in `rake gitlab:satellites:create`
-  - Disable connection reaping for MySQL
-  - Allow oauth signup without email for twitter and github
-  - Fix faulty namespace names that caused 500 on user creation
-  - Option to disable standard login
-  - Clean old created archives from repository downloads directory
-  - Fix download link for huge MR diffs
-  - Expose event and mergerequest timestamps in API
-  - Fix emails on push service when only one commit is pushed
-
-v 6.7.3
-  - Fix the merge notification email not being sent (Pierre de La Morinerie)
-  - Drop all tables before restoring a Postgres backup
-  - Remove yanked modernizr gem
-
-v 6.7.2
-  - Fix upgrader script
-
-v 6.7.1
-  - Fix GitLab CI integration
-
-v 6.7.0
-  - Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations
-  - Add support for Gemnasium as a Project Service (Olivier Gonzalez)
-  - Add edit file button to MergeRequest diff
-  - Public groups (Jason Hollingsworth)
-  - Cleaner headers in Notification Emails (Pierre de La Morinerie)
-  - Blob and tree gfm links to anchors work
-  - Piwik Integration (Sebastian Winkler)
-  - Show contribution guide link for new issue form (Jeroen van Baarsen)
-  - Fix CI status for merge requests from fork
-  - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
-  - New page load indicator that includes a spinner that scrolls with the page
-  - Converted all the help sections into markdown
-  - LDAP user filters
-  - Streamline the content of notification emails (Pierre de La Morinerie)
-  - Fixes a bug with group member administration (Matt DeTullio)
-  - Sort tag names using VersionSorter (Robert Speicher)
-  - Add GFM autocompletion for MergeRequests (Robert Speicher)
-  - Add webhook when a new tag is pushed (Jeroen van Baarsen)
-  - Add button for toggling inline comments in diff view
-  - Add retry feature for repository import
-  - Reuse the GitLab LDAP connection within each request
-  - Changed markdown new line behaviour to conform to markdown standards
-  - Fix global search
-  - Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5)
-  - Create and Update MR calls now support the description parameter (Greg Messner)
-  - Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository
-  - Added Slack service integration (Federico Ravasio)
-  - Better API responses for access_levels (sponsored by O'Reilly Media)
-  - Requires at least 2 unicorn workers
-  - Requires gitlab-shell v1.9+
-  - Replaced gemoji(due to closed licencing problem) with Phantom Open Emoji library(combined SIL Open Font License, MIT License and the CC 3.0 License)
-  - Fix `/:username.keys` response content type (Dmitry Medvinsky)
-
-v 6.6.5
-  - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
-  - Hide mr close button for comment form if merge request was closed or inline comment
-  - Adds ability to reopen closed merge request
-
-v 6.6.4
-  - Add missing html escape for highlighted code blocks in comments, issues
-
-v 6.6.3
-  - Fix 500 error when edit yourself from admin area
-  - Hide private groups for public profiles
-
-v 6.6.2
-  - Fix 500 error on branch/tag create or remove via UI
-
-v 6.6.1
-  - Fix 500 error on files tab if submodules presents
-
-v 6.6.0
-  - Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys
-  - Permissions: Developer now can manage issue tracker (modify any issue)
-  - Improve Code Compare page performance
-  - Group avatar
-  - Pygments.rb replaced with highlight.js
-  - Improve Merge request diff store logic
-  - Improve render performnace for MR show page
-  - Fixed Assembla hardcoded project name
-  - Jira integration documentation
-  - Refactored app/services
-  - Remove snippet expiration
-  - Mobile UI improvements (Drew Blessing)
-  - Fix block/remove UI for admin::users#show page
-  - Show users' group membership on users' activity page (Robert Djurasaj)
-  - User pages are visible without login if user is authorized to a public project
-  - Markdown rendered headers have id derived from their name and link to their id
-  - Improve application to work faster with large groups (100+ members)
-  - Multiple emails per user
-  - Show last commit for file when view file source
-  - Restyle Issue#show page and MR#show page
-  - Ability to filter by multiple labels for Issues page
-  - Rails version to 4.0.3
-  - Fixed attachment identifier displaying underneath note text (Jason Blanchard)
-
-v 6.5.1
-  - Fix branch selectbox when create merge request from fork
-
-v 6.5.0
-  - Dropdown menus on issue#show page for assignee and milestone (Jason Blanchard)
-  - Add color custimization and previewing to broadcast messages
-  - Fixed notes anchors
-  - Load new comments in issues dynamically
-  - Added sort options to Public page
-  - New filters (assigned/authored/all) for Dashboard#issues/merge_requests (sponsored by Say Media)
-  - Add project visibility icons to dashboard
-  - Enable secure cookies if https used
-  - Protect users/confirmation with rack_attack
-  - Default HTTP headers to protect against MIME-sniffing, force https if enabled
-  - Bootstrap 3 with responsive UI
-  - New repository download formats: tar.bz2, zip, tar (Jason Hollingsworth)
-  - Restyled accept widgets for MR
-  - SCSS refactored
-  - Use jquery timeago plugin
-  - Fix 500 error for rdoc files
-  - Ability to customize merge commit message (sponsored by Say Media)
-  - Search autocomplete via ajax
-  - Add website url to user profile
-  - Files API supports base64 encoded content (sponsored by O'Reilly Media)
-  - Added support for Go's repository retrieval (Bruno Albuquerque)
-
-v 6.4.3
-  - Don't use unicorn worker killer if PhusionPassenger is defined
-
-v 6.4.2
-  - Fixed wrong behaviour of script/upgrade.rb
-
-v 6.4.1
-  - Fixed bug with repository rename
-  - Fixed bug with project transfer
-
-v 6.4.0
-  - Added sorting to project issues page (Jason Blanchard)
-  - Assembla integration (Carlos Paramio)
-  - Fixed another 500 error with submodules
-  - UI: More compact issues page
-  - Minimal password length increased to 8 symbols
-  - Side-by-side diff view (Steven Thonus)
-  - Internal projects (Jason Hollingsworth)
-  - Allow removal of avatar (Drew Blessing)
-  - Project webhooks now support issues and merge request events
-  - Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth)
-  - Expire event cache on avatar creation/removal (Drew Blessing)
-  - Archiving old projects (Steven Thonus)
-  - Rails 4
-  - Add time ago tooltips to show actual date/time
-  - UI: Fixed UI for admin system hooks
-  - Ruby script for easier GitLab upgrade
-  - Do not remove Merge requests if fork project was removed
-  - Improve sign-in/signup UX
-  - Add resend confirmation link to sign-in page
-  - Set noreply at HOSTNAME for reply_to field in all emails
-  - Show GitLab API version on Admin#dashboard
-  - API Cross-origin resource sharing
-  - Show READMe link at project home page
-  - Show repo size for projects in Admin area
-
-v 6.3.0
-  - API for adding gitlab-ci service
-  - Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey)
-  - Restyle project home page
-  - Grammar fixes
-  - Show branches list (which branches contains commit) on commit page (Andrew Kumanyaev)
-  - Security improvements
-  - Added support for GitLab CI 4.0
-  - Fixed issue with 500 error when group did not exist
-  - Ability to leave project
-  - You can create file in repo using UI
-  - You can remove file from repo using UI
-  - API: dropped default_branch attribute from project during creation
-  - Project default_branch is not stored in db any more. It takes from repo now.
-  - Admin broadcast messages
-  - UI improvements
-  - Dont show last push widget if user removed this branch
-  - Fix 500 error for repos with newline in file name
-  - Extended html titles
-  - API: create/update/delete repo files
-  - Admin can transfer project to any namespace
-  - API: projects/all for admin users
-  - Fix recent branches order
-
-v 6.2.4
-  - Security: Cast API private_token to string (CVE-2013-4580)
-  - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
-  - Fix for Git SSH access for LDAP users
-
-v 6.2.3
-  - Security: More protection against CVE-2013-4489
-  - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
-  - Fix sidekiq rake tasks
-
-v 6.2.2
-  - Security: Update gitlab_git (CVE-2013-4489)
-
-v 6.2.1
-  - Security: Fix issue with generated passwords for new users
-
-v 6.2.0
-  - Public project pages are now visible to everyone (files, issues, wik, etc.)
-    THIS MEANS YOUR ISSUES AND WIKI FOR PUBLIC PROJECTS ARE PUBLICLY VISIBLE AFTER THE UPGRADE
-  - Add group access to permissions page
-  - Require current password to change one
-  - Group owner or admin can remove other group owners
-  - Remove group transfer since we have multiple owners
-  - Respect authorization in Repository API
-  - Improve UI for Project#files page
-  - Add more security specs
-  - Added search for projects by name to api (Izaak Alpert)
-  - Make default user theme configurable (Izaak Alpert)
-  - Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev)
-  - Rake tasks for webhooks management (Jonhnny Weslley)
-  - Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov)
-  - API: Remove group
-  - API: Remove project
-  - Avatar upload on profile page with a maximum of 100KB (Steven Thonus)
-  - Store the sessions in Redis instead of the cookie store
-  - Fixed relative links in markdown
-  - User must confirm their email if signup enabled
-  - User must confirm changed email
-
-v 6.1.0
-  - Project specific IDs for issues, mr, milestones
-    Above items will get a new id and for example all bookmarked issue urls will change.
-    Old issue urls are redirected to the new one if the issue id is too high for an internal id.
-  - Description field added to Merge Request
-  - API: Sudo api calls (Izaak Alpert)
-  - API: Group membership api (Izaak Alpert)
-  - Improved commit diff
-  - Improved large commit handling (Boyan Tabakov)
-  - Rewrite: Init script now less prone to errors and keeps better track of the service (Rovanion Luckey)
-  - Link issues, merge requests, and commits when they reference each other with GFM (Ash Wilson)
-  - Close issues automatically when pushing commits with a special message
-  - Improve user removal from admin area
-  - Invalidate events cache when project was moved
-  - Remove deprecated classes and rake tasks
-  - Add event filter for group and project show pages
-  - Add links to create branch/tag from project home page
-  - Add public-project? checkbox to new-project view
-  - Improved compare page. Added link to proceed into Merge Request
-  - Send an email to a user when they are added to group
-  - New landing page when you have 0 projects
-
-v 6.0.0
-  - Feature: Replace teams with group membership
-    We introduce group membership in 6.0 as a replacement for teams.
-    The old combination of groups and teams was confusing for a lot of people.
-    And when the members of a team where changed this wasn't reflected in the project permissions.
-    In GitLab 6.0 you will be able to add members to a group with a permission level for each member.
-    These group members will have access to the projects in that group.
-    Any changes to group members will immediately be reflected in the project permissions.
-    You can even have multiple owners for a group, greatly simplifying administration.
-  - Feature: Ability to have multiple owners for group
-  - Feature: Merge Requests between fork and project (Izaak Alpert)
-  - Feature: Generate fingerprint for ssh keys
-  - Feature: Ability to create and remove branches with UI
-  - Feature: Ability to create and remove git tags with UI
-  - Feature: Groups page in profile. You can leave group there
-  - API: Allow login with LDAP credentials
-  - Redesign: project settings navigation
-  - Redesign: snippets area
-  - Redesign: ssh keys page
-  - Redesign: buttons, blocks and other ui elements
-  - Add comment title to rss feed
-  - You can use arrows to navigate at tree view
-  - Add project filter on dashboard
-  - Cache project graph
-  - Drop support of root namespaces
-  - Default theme is classic now
-  - Cache result of methods like authorize_projects, project.team.members etc
-  - Remove $.ready events
-  - Fix onclick events being double binded
-  - Add notification level to group membership
-  - Move all project controllers/views under Projects:: module
-  - Move all profile controllers/views under Profiles:: module
-  - Apply user project limit only for personal projects
-  - Unicorn is default web server again
-  - Store satellites lock files inside satellites dir
-  - Disabled threadsafety mode in rails
-  - Fixed bug with loosing MR comments
-  - Improved MR comments logic
-  - Render readme file for projects in public area
-
-v 5.4.2
-  - Security: Cast API private_token to string (CVE-2013-4580)
-  - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
-
-v 5.4.1
-  - Security: Fixes for CVE-2013-4489
-  - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
-
-v 5.4.0
-  - Ability to edit own comments
-  - Documentation improvements
-  - Improve dashboard projects page
-  - Fixed nav for empty repos
-  - GitLab Markdown help page
-  - Misspelling fixes
-  - Added support of unicorn and fog gems
-  - Added client list to API doc
-  - Fix PostgreSQL database restoration problem
-  - Increase snippet content column size
-  - allow project import via git:// url
-  - Show participants on issues, including mentions
-  - Notify mentioned users with email
-
-v 5.3.0
-  - Refactored services
-  - Campfire service added
-  - HipChat service added
-  - Fixed bug with LDAP + git over http
-  - Fixed bug with google analytics code being ignored
-  - Improve sign-in page if ldap enabled
-  - Respect newlines in wall messages
-  - Generate the Rails secret token on first run
-  - Rename repo feature
-  - Init.d: remove gitlab.socket on service start
-  - Api: added teams api
-  - Api: Prevent blob content being escaped
-  - Api: Smart deploy key add behaviour
-  - Api: projects/owned.json return user owned project
-  - Fix bug with team assignation on project from #4109
-  - Advanced snippets: public/private, project/personal (Andrew Kulakov)
-  - Repository Graphs (Karlo Nicholas T. Soriano)
-  - Fix dashboard lost if comment on commit
-  - Update gitlab-grack. Fixes issue with --depth option
-  - Fix project events duplicate on project page
-  - Fix postgres error when displaying network graph.
-  - Fix dashboard event filter when navigate via turbolinks
-  - init.d: Ensure socket is removed before starting service
-  - Admin area: Style teams:index, group:show pages
-  - Own page for failed forking
-  - Scrum view for milestone
-
-v 5.2.0
-  - Turbolinks
-  - Git over http with ldap credentials
-  - Diff with better colors and some spacing on the corners
-  - Default values for project features
-  - Fixed huge_commit view
-  - Restyle project clone panel
-  - Move Gitlab::Git code to gitlab_git gem
-  - Move update docs in repo
-  - Requires gitlab-shell v1.4.0
-  - Fixed submodules listing under file tab
-  - Fork feature (Angus MacArthur)
-  - git version check in gitlab:check
-  - Shared deploy keys feature
-  - Ability to generate default labels set for issues
-  - Improve gfm autocomplete (Harold Luo)
-  - Added support for Google Analytics
-  - Code search feature (Javier Castro)
-
-v 5.1.0
-  - You can login with email or username now
-  - Corrected project transfer rollback when repository cannot be moved
-  - Move both repo and wiki when project transfer requested
-  - Admin area: project editing was removed from admin namespace
-  - Access: admin user has now access to any project.
-  - Notification settings
-  - Gitlab::Git set of objects to abstract from grit library
-  - Replace Unicorn web server with Puma
-  - Backup/Restore refactored. Backup dump project wiki too now
-  - Restyled Issues list. Show milestone version in issue row
-  - Restyled Merge Request list
-  - Backup now dump/restore uploads
-  - Improved performance of dashboard (Andrew Kumanyaev)
-  - File history now tracks renames (Akzhan Abdulin)
-  - Drop wiki migration tools
-  - Drop sqlite migration tools
-  - project tagging
-  - Paginate users in API
-  - Restyled network graph (Hiroyuki Sato)
-
-v 5.0.1
-  - Fixed issue with gitlab-grit being overridden by grit
-
-v 5.0.0
-  - Replaced gitolite with gitlab-shell
-  - Removed gitolite-related libraries
-  - State machine added
-  - Setup gitlab as git user
-  - Internal API
-  - Show team tab for empty projects
-  - Import repository feature
-  - Updated rails
-  - Use lambda for scopes
-  - Redesign admin area -> users
-  - Redesign admin area -> user
-  - Secure link to file attachments
-  - Add validations for Group and Team names
-  - Restyle team page for project
-  - Update capybara, rspec-rails, poltergeist to recent versions
-  - Wiki on git using Gollum
-  - Added Solarized Dark theme for code review
-  - Don't show user emails in autocomplete lists, profile pages
-  - Added settings tab for group, team, project
-  - Replace user popup with icons in header
-  - Handle project moving with gitlab-shell
-  - Added select2-rails for selectboxes with ajax data load
-  - Fixed search field on projects page
-  - Added teams to search autocomplete
-  - Move groups and teams on dashboard sidebar to sub-tabs
-  - API: improved return codes and docs. (Felix Gilcher, Sebastian Ziebell)
-  - Redesign wall to be more like chat
-  - Snippets, Wall features are disabled by default for new projects
-
-v 4.2.0
-  - Teams
-  - User show page. Via /u/username
-  - Show help contents on pages for better navigation
-  - Async gitolite calls
-  - added satellites logs
-  - can_create_group, can_create_team booleans for User
-  - Process webhooks async
-  - GFM: Fix images escaped inside links
-  - Network graph improved
-  - Switchable branches for network graph
-  - API: Groups
-  - Fixed project download
-
-v 4.1.0
-  - Optional Sign-Up
-  - Discussions
-  - Satellites outside of tmp
-  - Line numbers for blame
-  - Project public mode
-  - Public area with unauthorized access
-  - Load dashboard events with ajax
-  - remember dashboard filter in cookies
-  - replace resque with sidekiq
-  - fix routing issues
-  - cleanup rake tasks
-  - fix backup/restore
-  - scss cleanup
-  - show preview for note images
-  - improved network-graph
-  - get rid of app/roles/
-  - added new classes Team, Repository
-  - Reduce amount of gitolite calls
-  - Ability to add user in all group projects
-  - remove deprecated configs
-  - replaced Korolev font with open font
-  - restyled admin/dashboard page
-  - restyled admin/projects page
-
-v 4.0.0
-  - Remove project code and path from API. Use id instead
-  - Return valid cloneable url to repo for webhook
-  - Fixed backup issue
-  - Reorganized settings
-  - Fixed commits compare
-  - Refactored scss
-  - Improve status checks
-  - Validates presence of User#name
-  - Fixed postgres support
-  - Removed sqlite support
-  - Modified post-receive hook
-  - Milestones can be closed now
-  - Show comment events on dashboard
-  - Quick add team members via group#people page
-  - [API] expose created date for hooks and SSH keys
-  - [API] list, create issue notes
-  - [API] list, create snippet notes
-  - [API] list, create wall notes
-  - Remove project code - use path instead
-  - added username field to user
-  - rake task to fill usernames based on emails create namespaces for users
-  - STI Group < Namespace
-  - Project has namespace_id
-  - Projects with namespaces also namespaced in gitolite and stored in subdir
-  - Moving project to group will move it under group namespace
-  - Ability to move project from namespaces to another
-  - Fixes commit patches getting escaped (see #2036)
-  - Support diff and patch generation for commits and merge request
-  - MergeReqest doesn't generate a temporary file for the patch any more
-  - Update the UI to allow downloading Patch or Diff
-
-v 3.1.0
-  - Updated gems
-  - Services: Gitlab CI integration
-  - Events filter on dashboard
-  - Own namespace for redis/resque
-  - Optimized commit diff views
-  - add alphabetical order for projects admin page
-  - Improved web editor
-  - Commit stats page
-  - Documentation split and cleanup
-  - Link to commit authors everywhere
-  - Restyled milestones list
-  - added Milestone to Merge Request
-  - Restyled Top panel
-  - Refactored Satellite Code
-  - Added file line links
-  - moved from capybara-webkit to poltergeist + phantomjs
-
-v 3.0.3
-  - Fixed bug with issues list in Chrome
-  - New Feature: Import team from another project
-
-v 3.0.2
-  - Fixed gitlab:app:setup
-  - Fixed application error on empty project in admin area
-  - Restyled last push widget
-
-v 3.0.1
-  - Fixed git over http
-
-v 3.0.0
-  - Projects groups
-  - Web Editor
-  - Fixed bug with gitolite keys
-  - UI improved
-  - Increased performance of application
-  - Show user avatar in last commit when browsing Files
-  - Refactored Gitlab::Merge
-  - Use Font Awesome for icons
-  - Separate observing of Note and MergeRequests
-  - Milestone "All Issues" filter
-  - Fix issue close and reopen button text and styles
-  - Fix forward/back while browsing Tree hierarchy
-  - Show number of notes for commits and merge requests
-  - Added support pg from box and update installation doc
-  - Reject ssh keys that break gitolite
-  - [API] list one project hook
-  - [API] edit project hook
-  - [API] list project snippets
-  - [API] allow to authorize using private token in HTTP header
-  - [API] add user creation
-
-v 2.9.1
-  - Fixed resque custom config init
-
-v 2.9.0
-  - fixed inline notes bugs
-  - refactored rspecs
-  - refactored gitolite backend
-  - added factory_girl
-  - restyled projects list on dashboard
-  - ssh keys validation to prevent gitolite crash
-  - send notifications if changed permission in project
-  - scss refactoring. gitlab_bootstrap/ dir
-  - fix git push http body bigger than 112k problem
-  - list of labels  page under issues tab
-  - API for milestones, keys
-  - restyled buttons
-  - OAuth
-  - Comment order changed
-
-v 2.8.1
-  - ability to disable gravatars
-  - improved MR diff logic
-  - ssh key help page
-
-v 2.8.0
-  - Gitlab Flavored Markdown
-  - Bulk issues update
-  - Issues API
-  - Cucumber coverage increased
-  - Post-receive files fixed
-  - UI improved
-  - Application cleanup
-  - more cucumber
-  - capybara-webkit + headless
-
-v 2.7.0
-  - Issue Labels
-  - Inline diff
-  - Git HTTP
-  - API
-  - UI improved
-  - System hooks
-  - UI improved
-  - Dashboard events endless scroll
-  - Source performance increased
-
-v 2.6.0
-  - UI polished
-  - Improved network graph + keyboard nav
-  - Handle huge commits
-  - Last Push widget
-  - Bugfix
-  - Better performance
-  - Email in resque
-  - Increased test coverage
-  - Ability to remove branch with MR accept
-  - a lot of code refactored
-
-v 2.5.0
-  - UI polished
-  - Git blame for file
-  - Bugfix
-  - Email in resque
-  - Better test coverage
-
-v 2.4.0
-  - Admin area stats page
-  - Ability to block user
-  - Simplified dashboard area
-  - Improved admin area
-  - Bootstrap 2.0
-  - Responsive layout
-  - Big commits handling
-  - Performance improved
-  - Milestones
-
-v 2.3.1
-  - Issues pagination
-  - ssl fixes
-  - Merge Request pagination
-
-v 2.3.0
-  - Dashboard r1
-  - Search r1
-  - Project page
-  - Close merge request on push
-  - Persist MR diff after merge
-  - mysql support
-  - Documentation
-
-v 2.2.0
-  - We’ve added support of LDAP auth
-  - Improved permission logic (4 roles system)
-  - Protected branches (now only masters can push to protected branches)
-  - Usability improved
-  - twitter bootstrap integrated
-  - compare view between commits
-  - wiki feature
-  - now you can enable/disable issues, wiki, wall features per project
-  - security fixes
-  - improved code browsing (ajax branch switch etc)
-  - improved per-line commenting
-  - git submodules displayed
-  - moved to rails 3.2
-  - help section improved
-
-v 2.1.0
-  - Project tab r1
-  - List branches/tags
-  - per line comments
-  - mass user import
-
-v 2.0.0
-  - gitolite as main git host system
-  - merge requests
-  - project/repo access
-  - link to commit/issue feed
-  - design tab
-  - improved email notifications
-  - restyled dashboard
-  - bugfix
-
-v 1.2.2
-  - common config file gitlab.yml
-  - issues restyle
-  - snippets restyle
-  - clickable news feed header on dashboard
-  - bugfix
-
-v 1.2.1
-  - bugfix
-
-v 1.2.0
-  - new design
-  - user dashboard
-  - network graph
-  - markdown support for comments
-  - encoding issues
-  - wall like twitter timeline
-
-v 1.1.0
-  - project dashboard
-  - wall redesigned
-  - feature: code snippets
-  - fixed horizontal scroll on file preview
-  - fixed app crash if commit message has invalid chars
-  - bugfix & code cleaning
-
-v 1.0.2
-  - fixed bug with empty project
-  - added adv validation for project path & code
-  - feature: issues can be sortable
-  - bugfix
-  - username displayed on top panel
-
-v 1.0.1
-  - fixed: with invalid source code for commit
-  - fixed: lose branch/tag selection when use tree navigation
-  - when history clicked - display path
-  - bug fix & code cleaning
-
-v 1.0.0
-  - bug fix
-  - projects preview mode
-
-v 0.9.6
-  - css fix
-  - new repo empty tree until restart server - fixed
-
-v 0.9.4
-  - security improved
-  - authorization improved
-  - html escaping
-  - bug fix
-  - increased test coverage
-  - design improvements
-
-v 0.9.1
-  - increased test coverage
-  - design improvements
-  - new issue email notification
-  - updated app name
-  - issue redesigned
-  - issue can be edit
-
-v 0.8.0
-  - syntax highlight for main file types
-  - redesign
-  - stability
-  - security fixes
-  - increased test coverage
-  - email notification
+v 7.14.3 through 0.8.0
+  - See changelogs/archive.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fbc8e15..d5e15bf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -91,19 +91,7 @@ This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
 
 ## Implement design & UI elements
 
-### Design reference
-
-The GitLab design reference can be found in the [gitlab-design] project.
-The designs are made using Antetype (`.atype` files). You can use the
-[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
-(the PNG is 1:1).
-
-The current designs can be found in the [`gitlab8.atype` file].
-
-### UI development kit
-
-Implemented UI elements can also be found at https://gitlab.com/help/ui. Please
-note that this page isn't comprehensive at this time.
+Please see the [UI Guide for building GitLab].
 
 ## Issue tracker
 
@@ -129,7 +117,7 @@ request that potentially fixes it.
 
 ### Feature proposals
 
-To create a feature proposal for CE and CI, open an issue on the
+To create a feature proposal for CE, open an issue on the
 [issue tracker of CE][ce-tracker].
 
 For feature proposals for EE, open an issue on the
@@ -144,16 +132,7 @@ code snippet right after your description in a new line: `~"feature proposal"`.
 Please keep feature proposals as small and simple as possible, complex ones
 might be edited to make them small and simple.
 
-You are encouraged to use the template below for feature proposals.
-
-```
-## Description
-Include problem, use cases, benefits, and/or goals
-
-## Proposal
-
-## Links / references
-```
+Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
 
 For changes in the interface, it can be helpful to create a mockup first.
 If you want to create something yourself, consider opening an issue first to
@@ -166,55 +145,11 @@ submitting your own, there's a good chance somebody else had the same issue or
 feature proposal. Show your support with an award emoji and/or join the
 discussion.
 
-Please submit bugs using the following template in the issue description area.
+Please submit bugs using the ['Bug' issue template](.gitlab/issue_templates/Bug.md) provided on the issue tracker.
 The text in the parenthesis is there to help you with what to include. Omit it
 when submitting the actual issue. You can copy-paste it and then edit as you
 see fit.
 
-```
-## Summary
-
-(Summarize your issue in one sentence - what goes wrong, what did you expect to happen)
-
-## Steps to reproduce
-
-(How one can reproduce the issue - this is very important)
-
-## Expected behavior
-
-(What you should see instead)
-
-## Relevant logs and/or screenshots
-
-(Paste any relevant logs - please use code blocks (```) to format console output,
-logs, and code as it's very hard to read otherwise.)
-
-## Output of checks
-
-### Results of GitLab Application Check
-
-(For installations with omnibus-gitlab package run and paste the output of:
-sudo gitlab-rake gitlab:check SANITIZE=true)
-
-(For installations from source run and paste the output of:
-sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)
-
-(we will only investigate if the tests are passing)
-
-### Results of GitLab Environment Info
-
-(For installations with omnibus-gitlab package run and paste the output of:
-sudo gitlab-rake gitlab:env:info)
-
-(For installations from source run and paste the output of:
-sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production)
-
-## Possible fixes
-
-(If you can, link to the line of code that might be responsible for the problem)
-
-```
-
 ### Issue weight
 
 Issue weight allows us to get an idea of the amount of work required to solve
@@ -340,6 +275,10 @@ request is as follows:
    migrations on a fresh database before the MR is reviewed. If the review leads
    to large changes in the MR, do this again once the review is complete.
 1. For more complex migrations, write tests.
+1. Merge requests **must** adhere to the [merge request performance
+   guidelines](doc/development/merge_request_performance_guidelines.md).
+1. For tests that use Capybara or PhantomJS, see this [article on how
+   to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
 
 The **official merge window** is in the beginning of the month from the 1st to
 the 7th day of the month. This is the best time to submit an MR and get
@@ -387,7 +326,8 @@ description area. Copy-paste it to retain the markdown format.
 
 1. The change is as small as possible
 1. Include proper tests and make all tests pass (unless it contains a test
-   exposing a bug in existing code)
+   exposing a bug in existing code). Every new class should have corresponding
+   unit tests, even if the class is exercised at a higher level, such as a feature test.
 1. If you suspect a failing CI build is unrelated to your contribution, you may
    try and restart the failing CI job or ask a developer to fix the
    aforementioned failing test
@@ -539,7 +479,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
 [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
 [scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
 [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
-[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
-[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
-[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
+[UI Guide for building GitLab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/ui_guide.md
 [license-finder-doc]: doc/development/licensing.md
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 1809198..40c341b 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-3.4.0
+3.6.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index b4d6d12..100435b 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.7.11
+0.8.2
diff --git a/Gemfile b/Gemfile
index 194379d..54d3170 100644
--- a/Gemfile
+++ b/Gemfile
@@ -26,7 +26,7 @@ gem 'omniauth-auth0',         '~> 1.4.1'
 gem 'omniauth-azure-oauth2',  '~> 0.0.6'
 gem 'omniauth-bitbucket',     '~> 0.0.2'
 gem 'omniauth-cas3',          '~> 1.1.2'
-gem 'omniauth-facebook',      '~> 3.0.0'
+gem 'omniauth-facebook',      '~> 4.0.0'
 gem 'omniauth-github',        '~> 1.1.1'
 gem 'omniauth-gitlab',        '~> 1.0.0'
 gem 'omniauth-google-oauth2', '~> 0.4.1'
@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2'
 
 # Extracting information from a git repository
 # Provide access to Gitlab::Git library
-gem 'gitlab_git', '~> 10.4.7'
+gem 'gitlab_git', '~> 10.6.6'
 
 # LDAP Auth
 # GitLab fork with several improvements to original library. For full list of changes
@@ -97,9 +97,6 @@ gem 'fog-rackspace', '~> 0.1.1'
 # for aws storage
 gem 'unf', '~> 0.1.4'
 
-# Authorization
-gem 'six', '~> 0.2.0'
-
 # Seed data
 gem 'seed-fu', '~> 2.3.5'
 
@@ -209,6 +206,9 @@ gem 'mousetrap-rails', '~> 1.4.6'
 # Detect and convert string character encoding
 gem 'charlock_holmes', '~> 0.7.3'
 
+# Faster JSON
+gem 'oj', '~> 2.17.4'
+
 # Parse time & duration
 gem 'chronic', '~> 0.10.2'
 gem 'chronic_duration', '~> 0.10.6'
@@ -298,9 +298,10 @@ group :development, :test do
   gem 'spring-commands-spinach',  '~> 1.1.0'
   gem 'spring-commands-teaspoon', '~> 0.0.2'
 
-  gem 'rubocop', '~> 0.41.2', require: false
+  gem 'rubocop', '~> 0.42.0', require: false
   gem 'rubocop-rspec', '~> 1.5.0', require: false
   gem 'scss_lint', '~> 0.47.0', require: false
+  gem 'haml_lint', '~> 0.18.2', require: false
   gem 'simplecov', '0.12.0', require: false
   gem 'flog', '~> 4.3.2', require: false
   gem 'flay', '~> 2.6.1', require: false
@@ -319,6 +320,7 @@ group :test do
   gem 'webmock', '~> 1.21.0'
   gem 'test_after_commit', '~> 0.4.2'
   gem 'sham_rack', '~> 1.3.6'
+  gem 'timecop', '~> 0.8.0'
 end
 
 group :production do
diff --git a/Gemfile.lock b/Gemfile.lock
index 0c28975..3b15f36 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -189,7 +189,7 @@ GEM
     erubis (2.7.0)
     escape_utils (1.1.1)
     eventmachine (1.0.8)
-    excon (0.49.0)
+    excon (0.52.0)
     execjs (2.6.0)
     expression_parser (0.9.0)
     factory_girl (4.5.0)
@@ -215,8 +215,8 @@ GEM
     flowdock (0.7.1)
       httparty (~> 0.7)
       multi_json
-    fog-aws (0.9.2)
-      fog-core (~> 1.27)
+    fog-aws (0.11.0)
+      fog-core (~> 1.38)
       fog-json (~> 1.0)
       fog-xml (~> 0.1)
       ipaddress (~> 0.8)
@@ -225,7 +225,7 @@ GEM
       fog-core (~> 1.27)
       fog-json (~> 1.0)
       fog-xml (~> 0.1)
-    fog-core (1.40.0)
+    fog-core (1.42.0)
       builder
       excon (~> 0.49)
       formatador (~> 0.2)
@@ -279,7 +279,7 @@ GEM
       diff-lcs (~> 1.1)
       mime-types (>= 1.16, < 3)
       posix-spawn (~> 0.3)
-    gitlab_git (10.4.7)
+    gitlab_git (10.6.6)
       activesupport (~> 4.0)
       charlock_holmes (~> 0.7.3)
       github-linguist (~> 4.7.0)
@@ -322,11 +322,18 @@ GEM
     grape-entity (0.4.8)
       activesupport
       multi_json (>= 1.3.2)
+    haml (4.0.7)
+      tilt
+    haml_lint (0.18.2)
+      haml (~> 4.0)
+      rake (>= 10, < 12)
+      rubocop (>= 0.36.0)
+      sysexits (~> 1.1)
     hamlit (2.6.1)
       temple (~> 0.7.6)
       thor
       tilt
-    hashie (3.4.3)
+    hashie (3.4.4)
     health_check (2.1.0)
       rails (>= 4.0)
     hipchat (1.5.2)
@@ -394,7 +401,7 @@ GEM
       mime-types (>= 1.16, < 4)
     mail_room (0.8.0)
     method_source (0.8.2)
-    mime-types (2.99.2)
+    mime-types (2.99.3)
     mimemagic (0.3.0)
     mini_portile2 (2.1.0)
     minitest (5.7.0)
@@ -420,6 +427,7 @@ GEM
       rack (>= 1.2, < 3)
     octokit (4.3.0)
       sawyer (~> 0.7.0, >= 0.5.3)
+    oj (2.17.4)
     omniauth (1.3.1)
       hashie (>= 1.2, < 4)
       rack (>= 1.0, < 3)
@@ -437,7 +445,7 @@ GEM
       addressable (~> 2.3)
       nokogiri (~> 1.6.6)
       omniauth (~> 1.2)
-    omniauth-facebook (3.0.0)
+    omniauth-facebook (4.0.0)
       omniauth-oauth2 (~> 1.2)
     omniauth-github (1.1.2)
       omniauth (~> 1.0)
@@ -584,7 +592,7 @@ GEM
       railties (>= 4.2.0, < 5.1)
     rinku (2.0.0)
     rotp (2.1.2)
-    rouge (2.0.5)
+    rouge (2.0.6)
     rqrcode (0.7.0)
       chunky_png
     rqrcode-rails3 (0.1.7)
@@ -612,7 +620,7 @@ GEM
     rspec-retry (0.4.5)
       rspec-core
     rspec-support (3.5.0)
-    rubocop (0.41.2)
+    rubocop (0.42.0)
       parser (>= 2.3.1.1, < 3.0)
       powerpack (~> 0.1)
       rainbow (>= 1.99.1, < 3.0)
@@ -683,7 +691,6 @@ GEM
       rack (~> 1.5)
       rack-protection (~> 1.4)
       tilt (>= 1.3, < 3)
-    six (0.2.0)
     slack-notifier (1.2.1)
     slop (3.6.0)
     spinach (0.8.10)
@@ -724,6 +731,7 @@ GEM
     stringex (2.5.2)
     sys-filesystem (1.1.6)
       ffi
+    sysexits (1.2.0)
     systemu (2.6.5)
     task_list (1.0.2)
       html-pipeline
@@ -755,7 +763,7 @@ GEM
     unf (0.1.4)
       unf_ext
     unf_ext (0.0.7.2)
-    unicode-display_width (1.1.0)
+    unicode-display_width (1.1.1)
     unicorn (4.9.0)
       kgio (~> 2.6)
       rack
@@ -859,7 +867,7 @@ DEPENDENCIES
   github-linguist (~> 4.7.0)
   github-markup (~> 1.4)
   gitlab-flowdock-git-hook (~> 1.0.1)
-  gitlab_git (~> 10.4.7)
+  gitlab_git (~> 10.6.6)
   gitlab_meta (= 7.0)
   gitlab_omniauth-ldap (~> 1.2.1)
   gollum-lib (~> 4.2)
@@ -867,6 +875,7 @@ DEPENDENCIES
   gon (~> 6.1.0)
   grape (~> 0.15.0)
   grape-entity (~> 0.4.2)
+  haml_lint (~> 0.18.2)
   hamlit (~> 2.6.1)
   health_check (~> 2.1.0)
   hipchat (~> 1.5.0)
@@ -896,12 +905,13 @@ DEPENDENCIES
   nokogiri (~> 1.6.7, >= 1.6.7.2)
   oauth2 (~> 1.2.0)
   octokit (~> 4.3.0)
+  oj (~> 2.17.4)
   omniauth (~> 1.3.1)
   omniauth-auth0 (~> 1.4.1)
   omniauth-azure-oauth2 (~> 0.0.6)
   omniauth-bitbucket (~> 0.0.2)
   omniauth-cas3 (~> 1.1.2)
-  omniauth-facebook (~> 3.0.0)
+  omniauth-facebook (~> 4.0.0)
   omniauth-github (~> 1.1.1)
   omniauth-gitlab (~> 1.0.0)
   omniauth-google-oauth2 (~> 0.4.1)
@@ -936,7 +946,7 @@ DEPENDENCIES
   rqrcode-rails3 (~> 0.1.7)
   rspec-rails (~> 3.5.0)
   rspec-retry (~> 0.4.5)
-  rubocop (~> 0.41.2)
+  rubocop (~> 0.42.0)
   rubocop-rspec (~> 1.5.0)
   ruby-fogbugz (~> 0.2.1)
   ruby-prof (~> 0.15.9)
@@ -954,7 +964,6 @@ DEPENDENCIES
   sidekiq-cron (~> 0.4.0)
   simplecov (= 0.12.0)
   sinatra (~> 1.4.4)
-  six (~> 0.2.0)
   slack-notifier (~> 1.2.0)
   spinach-rails (~> 0.2.1)
   spinach-rerun-reporter (~> 0.0.2)
@@ -971,6 +980,7 @@ DEPENDENCIES
   teaspoon-jasmine (~> 2.2.0)
   test_after_commit (~> 0.4.2)
   thin (~> 1.7.0)
+  timecop (~> 0.8.0)
   turbolinks (~> 2.5.0)
   u2f (~> 0.2.1)
   uglifier (~> 2.7.2)
diff --git a/PROCESS.md b/PROCESS.md
index 8e1a3f7..8af660f 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -50,7 +50,7 @@ etc.).
 
 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
+on those issues. Please select someone with relevant experience from
 [GitLab core team][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
diff --git a/README.md b/README.md
index fee93d5..3df8bfa 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
 # GitLab
 
 [![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
+[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
 [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
 
 ## Canonical source
@@ -69,7 +70,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
 GitLab is a Ruby on Rails application that runs on the following software:
 
 - Ubuntu/Debian/CentOS/RHEL
-- Ruby (MRI) 2.1
+- Ruby (MRI) 2.3
 - Git 2.7.4+
 - Redis 2.8+
 - MySQL or PostgreSQL
diff --git a/VERSION b/VERSION
index dba04c1..fd32ca6 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.11.3
+8.12.1
diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png
deleted file mode 100644
index 5b55e12..0000000
Binary files a/app/assets/images/icon-link.png and /dev/null differ
diff --git a/app/assets/images/icon_anchor.svg b/app/assets/images/icon_anchor.svg
new file mode 100644
index 0000000..7e24258
--- /dev/null
+++ b/app/assets/images/icon_anchor.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#333" fill-rule="evenodd" d="M9.683 6.676l-.047-.048C8.27 5.26 6.07 5.243 4.726 6.588l-2.29 2.29c-1.344 1.344-1.328 3.544.04 4.91 1.366 1.368 3.564 1.385 4.908.04l1.753-1.752c-.695.074-1.457-.078-2.176-.444L5.934 12.66c-.634.634-1.67.625-2.312-.017-.642-.643-.65-1.677-.017-2.312L6.035 7.9c.634-.634 1.67-.625 2.312.017.024.024.048.05.07.075l.003-.002c.36.36.943.366 1.3.01.355-.356.35-.938-.01-1. [...]
\ No newline at end of file
diff --git a/app/assets/javascripts/LabelManager.js b/app/assets/javascripts/LabelManager.js
index 151455c..d4a4c7a 100644
--- a/app/assets/javascripts/LabelManager.js
+++ b/app/assets/javascripts/LabelManager.js
@@ -3,6 +3,7 @@
     LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time';
 
     function LabelManager(opts) {
+      // Defaults
       var ref, ref1, ref2;
       if (opts == null) {
         opts = {};
@@ -28,6 +29,7 @@
       $btn = $(e.currentTarget);
       $label = $("#" + ($btn.data('domId')));
       action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
+      // Make sure tooltip will hide
       $tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby')));
       $tooltip.tooltip('destroy');
       return _this.toggleLabelPriority($label, action);
@@ -42,6 +44,7 @@
       url = $label.find('.js-toggle-priority').data('url');
       $target = this.prioritizedLabels;
       $from = this.otherLabels;
+      // Optimistic update
       if (action === 'remove') {
         $target = this.otherLabels;
         $from = this.prioritizedLabels;
@@ -53,6 +56,7 @@
         $target.find('.empty-message').addClass('hidden');
       }
       $label.detach().appendTo($target);
+      // Return if we are not persisting state
       if (!persistState) {
         return;
       }
@@ -61,6 +65,7 @@
           url: url,
           type: 'DELETE'
         });
+        // Restore empty message
         if (!$from.find('li').length) {
           $from.find('.empty-message').removeClass('hidden');
         }
diff --git a/app/assets/javascripts/abuse_reports.js.es6 b/app/assets/javascripts/abuse_reports.js.es6
new file mode 100644
index 0000000..2fe46b9
--- /dev/null
+++ b/app/assets/javascripts/abuse_reports.js.es6
@@ -0,0 +1,38 @@
+((global) => {
+  const MAX_MESSAGE_LENGTH = 500;
+  const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
+
+  class AbuseReports {
+    constructor() {
+      $(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage);
+      $(document)
+        .off('click', MESSAGE_CELL_SELECTOR)
+        .on('click', MESSAGE_CELL_SELECTOR, this.toggleMessageTruncation);
+    }
+
+    truncateLongMessage() {
+      const $messageCellElement = $(this);
+      const reportMessage = $messageCellElement.text();
+      if (reportMessage.length > MAX_MESSAGE_LENGTH) {
+        $messageCellElement.data('original-message', reportMessage);
+        $messageCellElement.data('message-truncated', 'true');
+        $messageCellElement.text(global.text.truncate(reportMessage, MAX_MESSAGE_LENGTH));
+      }
+    }
+
+    toggleMessageTruncation() {
+      const $messageCellElement = $(this);
+      const originalMessage = $messageCellElement.data('original-message');
+      if (!originalMessage) return;
+      if ($messageCellElement.data('message-truncated') === 'true') {
+        $messageCellElement.data('message-truncated', 'false');
+        $messageCellElement.text(originalMessage);
+      } else {
+        $messageCellElement.data('message-truncated', 'true');
+        $messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`);
+      }
+    }
+  }
+
+  global.AbuseReports = AbuseReports;
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index 1ab3c21..d5e11e2 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -12,7 +12,7 @@
     }
 
     Activities.prototype.updateTooltips = function() {
-      return gl.utils.localTimeAgo($('.js-timeago', '#activity'));
+      return gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
     };
 
     Activities.prototype.reloadActivities = function() {
@@ -26,7 +26,7 @@
       event_filters = $.cookie("event_filter");
       filter = sender.attr("id").split("_")[0];
       $.cookie("event_filter", (event_filters !== filter ? filter : ""), {
-        path: '/'
+        path: gon.relative_url_root || '/'
       });
       if (event_filters !== filter) {
         return sender.closest('li').toggleClass("active");
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 84b292e..1cd2302 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -16,20 +16,18 @@
         .replace(':id', group_id);
       return $.ajax({
         url: url,
-        data: {
-          private_token: gon.api_token
-        },
         dataType: "json"
       }).done(function(group) {
         return callback(group);
       });
     },
+    // Return groups list. Filtered by query
+    // Only active groups retrieved
     groups: function(query, skip_ldap, callback) {
       var url = Api.buildUrl(Api.groupsPath);
       return $.ajax({
         url: url,
         data: {
-          private_token: gon.api_token,
           search: query,
           per_page: 20
         },
@@ -38,12 +36,12 @@
         return callback(groups);
       });
     },
+    // Return namespaces list. Filtered by query
     namespaces: function(query, callback) {
       var url = Api.buildUrl(Api.namespacesPath);
       return $.ajax({
         url: url,
         data: {
-          private_token: gon.api_token,
           search: query,
           per_page: 20
         },
@@ -52,12 +50,12 @@
         return callback(namespaces);
       });
     },
+    // Return projects list. Filtered by query
     projects: function(query, order, callback) {
       var url = Api.buildUrl(Api.projectsPath);
       return $.ajax({
         url: url,
         data: {
-          private_token: gon.api_token,
           search: query,
           order_by: order,
           per_page: 20
@@ -70,7 +68,6 @@
     newLabel: function(project_id, data, callback) {
       var url = Api.buildUrl(Api.labelsPath)
         .replace(':id', project_id);
-      data.private_token = gon.api_token;
       return $.ajax({
         url: url,
         type: "POST",
@@ -82,13 +79,13 @@
         return callback(message.responseJSON);
       });
     },
+    // Return group projects list. Filtered by query
     groupProjects: function(group_id, query, callback) {
       var url = Api.buildUrl(Api.groupProjectsPath)
         .replace(':id', group_id);
       return $.ajax({
         url: url,
         data: {
-          private_token: gon.api_token,
           search: query,
           per_page: 20
         },
@@ -97,6 +94,7 @@
         return callback(projects);
       });
     },
+    // Return text for a specific license
     licenseText: function(key, data, callback) {
       var url = Api.buildUrl(Api.licensePath)
         .replace(':key', key);
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index ce9cbb6..c029bf3 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -1,3 +1,9 @@
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
 /*= require jquery2 */
 /*= require jquery-ui/autocomplete */
 /*= require jquery-ui/datepicker */
@@ -76,6 +82,7 @@
     }
   };
 
+  // Disable button if text field is empty
   window.disableButtonIfEmptyField = function(field_selector, button_selector) {
     var closest_submit, field;
     field = $(field_selector);
@@ -92,6 +99,7 @@
     });
   };
 
+  // Disable button if any input field with given selector is empty
   window.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
     var closest_submit, updateButtons;
     closest_submit = form.find(button_selector);
@@ -128,6 +136,8 @@
   window.addEventListener("hashchange", shiftWindow);
 
   window.onload = function() {
+    // Scroll the window to avoid the topnav bar
+    // https://github.com/twitter/bootstrap/issues/1768
     if (location.hash) {
       return setTimeout(shiftWindow, 100);
     }
@@ -149,9 +159,13 @@
       return $(this).select().one('mouseup', function(e) {
         return e.preventDefault();
       });
+    // Click a .js-select-on-focus field, select the contents
+    // Prevent a mouseup event from deselecting the input
     });
     $('.remove-row').bind('ajax:success', function() {
-      return $(this).closest('li').fadeOut();
+      $(this).tooltip('destroy')
+        .closest('li')
+        .fadeOut();
     });
     $('.js-remove-tr').bind('ajax:before', function() {
       return $(this).hide();
@@ -161,6 +175,7 @@
     });
     $('select.select2').select2({
       width: 'resolve',
+      // Initialize select2 selects
       dropdownAutoWidth: true
     });
     $('.js-select2').bind('select2-close', function() {
@@ -168,25 +183,28 @@
         $('.select2-container-active').removeClass('select2-container-active');
         return $(':focus').blur();
       }), 1);
+    // Close select2 on escape
     });
+    // Initialize tooltips
     $body.tooltip({
       selector: '.has-tooltip, [data-toggle="tooltip"]',
       placement: function(_, el) {
-        var $el;
-        $el = $(el);
-        return $el.data('placement') || 'bottom';
+        return $(el).data('placement') || 'bottom';
       }
     });
     $('.trigger-submit').on('change', function() {
       return $(this).parents('form').submit();
+    // Form submitter
     });
     gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
+    // Flash
     if ((flash = $(".flash-container")).length > 0) {
       flash.click(function() {
         return $(this).fadeOut();
       });
       flash.show();
     }
+    // Disable form buttons while a form is submitting
     $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function(e) {
       var buttons;
       buttons = $('[type="submit"]', this);
@@ -207,6 +225,7 @@
       }
     });
     $('.account-box').hover(function() {
+      // Show/Hide the profile menu when hovering the account box
       return $(this).toggleClass('hover');
     });
     $document.on('click', '.diff-content .js-show-suppressed-diff', function() {
@@ -214,6 +233,7 @@
       $container = $(this).parent();
       $container.next('table').show();
       return $container.remove();
+    // Commit show suppressed diff
     });
     $('.navbar-toggle').on('click', function() {
       $('.header-content .title').toggle();
@@ -221,6 +241,7 @@
       $('.header-content .navbar-collapse').toggle();
       return $('.navbar-toggle').toggleClass('active');
     });
+    // Show/hide comments on diff
     $body.on("click", ".js-toggle-diff-comments", function(e) {
       var $this = $(this);
       $this.toggleClass('active');
@@ -230,6 +251,7 @@
       } else {
         notesHolders.hide();
       }
+      $this.trigger('blur');
       return e.preventDefault();
     });
     $document.off("click", '.js-confirm-danger');
@@ -284,42 +306,9 @@
     gl.awardsHandler = new AwardsHandler();
     checkInitialSidebarSize();
     new Aside();
-    if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') {
-      $.cookie('pin_nav', 'false', {
-        path: '/',
-        expires: 365 * 10
-      });
-      $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
-      $('.navbar-fixed-top').removeClass('header-pinned-nav');
-    }
-    $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
-      var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
-      e.preventDefault();
-      $pinBtn = $(e.currentTarget);
-      $page = $('.page-with-sidebar');
-      $topNav = $('.navbar-fixed-top');
-      $tooltip = $("#" + ($pinBtn.attr('aria-describedby')));
-      doPinNav = !$page.is('.page-sidebar-pinned');
-      tooltipText = 'Pin navigation';
-      $(this).toggleClass('is-active');
-      if (doPinNav) {
-        $page.addClass('page-sidebar-pinned');
-        $topNav.addClass('header-pinned-nav');
-      } else {
-        $tooltip.remove();
-        $page.removeClass('page-sidebar-pinned').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
-        $topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded');
-      }
-      $.cookie('pin_nav', doPinNav, {
-        path: '/',
-        expires: 365 * 10
-      });
-      if ($.cookie('pin_nav') === 'true' || doPinNav) {
-        tooltipText = 'Unpin navigation';
-      }
-      $tooltip.find('.tooltip-inner').text(tooltipText);
-      return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
-    });
+
+    // bind sidebar events
+    new gl.Sidebar();
 
     // Custom time ago
     gl.utils.shortTimeAgo($('.js-short-timeago'));
diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js
index 7116512..a9aec6e 100644
--- a/app/assets/javascripts/autosave.js
+++ b/app/assets/javascripts/autosave.js
@@ -16,7 +16,7 @@
     }
 
     Autosave.prototype.restore = function() {
-      var e, error, text;
+      var e, text;
       if (window.localStorage == null) {
         return;
       }
@@ -41,7 +41,7 @@
       if ((text != null ? text.length : void 0) > 0) {
         try {
           return window.localStorage.setItem(this.key, text);
-        } catch (undefined) {}
+        } catch (error) {}
       } else {
         return this.reset();
       }
@@ -53,7 +53,7 @@
       }
       try {
         return window.localStorage.removeItem(this.key);
-      } catch (undefined) {}
+      } catch (error) {}
     };
 
     return Autosave;
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 2c5b83e..0decc6d 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,5 +1,6 @@
 (function() {
   this.AwardsHandler = (function() {
+    const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence
     function AwardsHandler() {
       this.aliases = gl.emojiAliases();
       $(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
@@ -85,6 +86,8 @@
     AwardsHandler.prototype.positionMenu = function($menu, $addBtn) {
       var css, position;
       position = $addBtn.data('position');
+      // The menu could potentially be off-screen or in a hidden overflow element
+      // So we position the element absolute in the body
       css = {
         top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px"
       };
@@ -130,7 +133,7 @@
           counter = $emojiButton.find('.js-counter');
           counter.text(parseInt(counter.text()) + 1);
           $emojiButton.addClass('active');
-          this.addMeToUserList(votesBlock, emoji);
+          this.addYouToUserList(votesBlock, emoji);
           return this.animateEmoji($emojiButton);
         }
       } else {
@@ -176,11 +179,11 @@
       counterNumber = parseInt(counter.text(), 10);
       if (counterNumber > 1) {
         counter.text(counterNumber - 1);
-        this.removeMeFromUserList($emojiButton, emoji);
+        this.removeYouFromUserList($emojiButton, emoji);
       } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
         $emojiButton.tooltip('destroy');
         counter.text('0');
-        this.removeMeFromUserList($emojiButton, emoji);
+        this.removeYouFromUserList($emojiButton, emoji);
         if ($emojiButton.parents('.note').length) {
           this.removeEmoji($emojiButton);
         }
@@ -204,43 +207,48 @@
       return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
     };
 
-    AwardsHandler.prototype.removeMeFromUserList = function($emojiButton, emoji) {
+    AwardsHandler.prototype.toSentence = function(list) {
+      if(list.length <= 2){
+        return list.join(' and ');
+      }
+      else{
+        return list.slice(0, -1).join(', ') + ', and ' + list[list.length - 1];
+      }
+    };
+
+    AwardsHandler.prototype.removeYouFromUserList = function($emojiButton, emoji) {
       var authors, awardBlock, newAuthors, originalTitle;
       awardBlock = $emojiButton;
       originalTitle = this.getAwardTooltip(awardBlock);
-      authors = originalTitle.split(', ');
-      authors.splice(authors.indexOf('me'), 1);
-      newAuthors = authors.join(', ');
-      awardBlock.closest('.js-emoji-btn').removeData('original-title').attr('data-original-title', newAuthors);
-      return this.resetTooltip(awardBlock);
+      authors = originalTitle.split(FROM_SENTENCE_REGEX);
+      authors.splice(authors.indexOf('You'), 1);
+      return awardBlock
+        .closest('.js-emoji-btn')
+        .removeData('title')
+        .removeAttr('data-title')
+        .removeAttr('data-original-title')
+        .attr('title', this.toSentence(authors))
+        .tooltip('fixTitle');
     };
 
-    AwardsHandler.prototype.addMeToUserList = function(votesBlock, emoji) {
+    AwardsHandler.prototype.addYouToUserList = function(votesBlock, emoji) {
       var awardBlock, origTitle, users;
       awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
       origTitle = this.getAwardTooltip(awardBlock);
       users = [];
       if (origTitle) {
-        users = origTitle.trim().split(', ');
+        users = origTitle.trim().split(FROM_SENTENCE_REGEX);
       }
-      users.push('me');
-      awardBlock.attr('title', users.join(', '));
-      return this.resetTooltip(awardBlock);
-    };
-
-    AwardsHandler.prototype.resetTooltip = function(award) {
-      var cb;
-      award.tooltip('destroy');
-      cb = function() {
-        return award.tooltip();
-      };
-      return setTimeout(cb, 200);
+      users.unshift('You');
+      return awardBlock
+        .attr('title', this.toSentence(users))
+        .tooltip('fixTitle');
     };
 
     AwardsHandler.prototype.createEmoji_ = function(votesBlock, emoji) {
       var $emojiButton, buttonHtml, emojiCssClass;
       emojiCssClass = this.resolveNameToCssClass(emoji);
-      buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'> <div class='icon emoji-icon " + emojiCssClass + "' data-emoji='" + emoji + "'></div> <span class='award-control-text js-counter'>1</span> </button>";
+      buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='You' data-placement='bottom'> <div class='icon emoji-icon " + emojiCssClass + "' data-emoji='" + emoji + "'></div> <span class='award-control-text js-counter'>1</span> </button>";
       $emojiButton = $(buttonHtml);
       $emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('emoji', emoji);
       this.animateEmoji($emojiButton);
@@ -249,12 +257,12 @@
     };
 
     AwardsHandler.prototype.animateEmoji = function($emoji) {
-      var className;
-      className = 'pulse animated';
+      var className = 'pulse animated once short';
       $emoji.addClass(className);
-      return setTimeout((function() {
-        return $emoji.removeClass(className);
-      }), 321);
+
+      $emoji.on('webkitAnimationEnd animationEnd', function() {
+        $(this).removeClass(className);
+      });
     };
 
     AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) {
@@ -278,6 +286,7 @@
       if (emojiIcon.length > 0) {
         unicodeName = emojiIcon.data('unicode-name');
       } else {
+        // Find by alias
         unicodeName = $(".emoji-menu-content [data-aliases*=':" + emoji + ":']").data('unicode-name');
       }
       return "emoji-" + unicodeName;
@@ -314,6 +323,7 @@
       frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
       frequentlyUsedEmojis.push(emoji);
       return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), {
+        path: gon.relative_url_root || '/',
         expires: 365
       });
     };
@@ -343,8 +353,10 @@
         return function(ev) {
           var found_emojis, h5, term, ul;
           term = $(ev.target).val();
+          // Clean previous search results
           $('ul.emoji-menu-search, h5.emoji-search').remove();
           if (term) {
+            // Generate a search result block
             h5 = $('<h5>').text('Search results');
             found_emojis = _this.searchEmojis(term).show();
             ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index f977a1e..dc8ae60 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,7 +1,5 @@
 
 /*= require jquery.ba-resize */
-
-
 /*= require autosize */
 
 (function() {
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
index 3631d1b..1df681a 100644
--- a/app/assets/javascripts/behaviors/details_behavior.js
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -5,6 +5,12 @@
       container = $(this).closest(".js-details-container");
       return container.toggleClass("open");
     });
+    // Show details content. Hides link after click.
+    //
+    // %div
+    //   %a.js-details-expand
+    //   %div.js-details-content
+    //
     return $("body").on("click", ".js-details-expand", function(e) {
       $(this).next('.js-details-content').removeClass("hide");
       $(this).hide();
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index 3527d0a..54b7360 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -1,6 +1,20 @@
-
+// Quick Submit behavior
+//
+// When a child field of a form with a `js-quick-submit` class receives a
+// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
+// is submitted.
+//
 /*= require extensions/jquery */
 
+//
+// ### Example Markup
+//
+//   <form action="/foo" class="js-quick-submit">
+//     <input type="text" />
+//     <textarea></textarea>
+//     <input type="submit" value="Submit" />
+//   </form>
+//
 (function() {
   var isMac, keyCodeIs;
 
@@ -17,6 +31,7 @@
 
   $(document).on('keydown.quick_submit', '.js-quick-submit', function(e) {
     var $form, $submit_button;
+    // Enter
     if (!keyCodeIs(e, 13)) {
       return;
     }
@@ -33,8 +48,11 @@
     return $form.submit();
   });
 
+  // If the user tabs to a submit button on a `js-quick-submit` form, display a
+  // tooltip to let them know they could've used the hotkey
   $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function(e) {
     var $this, title;
+    // Tab
     if (!keyCodeIs(e, 9)) {
       return;
     }
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index db0b36b..894034b 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -1,6 +1,18 @@
-
+// Requires Input behavior
+//
+// When called on a form with input fields with the `required` attribute, the
+// form's submit button will be disabled until all required fields have values.
+//
 /*= require extensions/jquery */
 
+//
+// ### Example Markup
+//
+//   <form class="js-requires-input">
+//     <input type="text" required="required">
+//     <input type="submit" value="Submit">
+//   </form>
+//
 (function() {
   $.fn.requiresInput = function() {
     var $button, $form, fieldSelector, requireInput, required;
@@ -11,14 +23,17 @@
     requireInput = function() {
       var values;
       values = _.map($(fieldSelector, $form), function(field) {
+        // Collect the input values of *all* required fields
         return field.value;
       });
+      // Disable the button if any required fields are empty
       if (values.length && _.any(values, _.isEmpty)) {
         return $button.disable();
       } else {
         return $button.enable();
       }
     };
+    // Set initial button state
     requireInput();
     return $form.on('change input', fieldSelector, requireInput);
   };
@@ -27,6 +42,8 @@
     var $form, hideOrShowHelpBlock;
     $form = $('form.js-requires-input');
     $form.requiresInput();
+    // Hide or Show the help block when creating a new project
+    // based on the option selected
     hideOrShowHelpBlock = function(form) {
       var selected;
       selected = $('.js-select-namespace option:selected');
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 8ac1ba7..a6ce378 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,6 +1,13 @@
 (function(w) {
   $(function() {
-    $('.js-toggle-button').on('click', function(e) {
+    // Toggle button. Show/hide content inside parent container.
+    // Button does not change visibility. If button has icon - it changes chevron style.
+    //
+    // %div.js-toggle-container
+    //   %a.js-toggle-button
+    //   %div.js-toggle-content
+    //
+    $('body').on('click', '.js-toggle-button', function(e) {
       e.preventDefault();
       $(this)
         .find('.fa')
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index f4044f2..8cca1aa 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -8,6 +8,8 @@
         autoDiscover: false,
         autoProcessQueue: false,
         url: form.attr('action'),
+        // Rails uses a hidden input field for PUT
+        // http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
         method: method,
         clickable: true,
         uploadMultiple: false,
@@ -36,6 +38,7 @@
             formData.append('commit_message', form.find('.js-commit-message').val());
           });
         },
+        // Override behavior of adding error underneath preview
         error: function(file, errorMessage) {
           var stripped;
           stripped = $("<div/>").html(errorMessage).text();
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index b0a37ef..9535216 100644
--- a/app/assets/javascripts/blob/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -13,6 +13,9 @@
       this.buildDropdown();
       this.bindEvents();
       this.onFilenameUpdate();
+
+      this.autosizeUpdateEvent = document.createEvent('Event');
+      this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
     }
 
     TemplateSelector.prototype.buildDropdown = function() {
@@ -66,9 +69,16 @@
       // be added by all subclasses.
     };
 
+    // To be implemented on the extending class
+    // e.g.
+    // Api.gitignoreText item.name, @requestFileSuccess.bind(@)
     TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
       this.editor.setValue(file.content, 1);
       if (!skipFocus) this.editor.focus();
+
+      if (this.editor instanceof jQuery) {
+        this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
+      }
     };
 
     TemplateSelector.prototype.startLoadingSpinner = function() {
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index 649c79d..b846bab 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -18,6 +18,8 @@
         return function() {
           return $("#file-content").val(_this.editor.getValue());
         };
+      // Before a form submission, move the content from the Ace editor into the
+      // submitted textarea
       })(this));
       this.initModePanesAndLinks();
       new BlobLicenseSelectors({
diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6
index a612cf0..91c1257 100644
--- a/app/assets/javascripts/boards/boards_bundle.js.es6
+++ b/app/assets/javascripts/boards/boards_bundle.js.es6
@@ -54,4 +54,11 @@ $(() => {
         });
     }
   });
+
+  gl.IssueBoardsSearch = new Vue({
+    el: '#js-boards-seach',
+    data: {
+      filters: Store.state.filters
+    }
+  });
 });
diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6
index d7f4107..7e86f00 100644
--- a/app/assets/javascripts/boards/components/board.js.es6
+++ b/app/assets/javascripts/boards/components/board.js.es6
@@ -21,15 +21,10 @@
     },
     data () {
       return {
-        query: '',
         filters: Store.state.filters
       };
     },
     watch: {
-      query () {
-        this.list.filters = this.getFilterData();
-        this.list.getIssues(true);
-      },
       filters: {
         handler () {
           this.list.page = 1;
@@ -38,16 +33,6 @@
         deep: true
       }
     },
-    methods: {
-      getFilterData () {
-        const filters = this.filters;
-        let queryData = { search: this.query };
-
-        Object.keys(filters).forEach((key) => { queryData[key] = filters[key]; });
-
-        return queryData;
-      }
-    },
     ready () {
       const options = gl.issueBoards.getBoardSortableDefaultOptions({
         disabled: this.disabled,
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index a6644e9..474805c 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -20,7 +20,8 @@
     data () {
       return {
         scrollOffset: 250,
-        filters: Store.state.filters
+        filters: Store.state.filters,
+        showCount: false
       };
     },
     watch: {
@@ -30,6 +31,20 @@
           this.$els.list.scrollTop = 0;
         },
         deep: true
+      },
+      issues () {
+        this.$nextTick(() => {
+          if (this.scrollHeight() <= this.listHeight() && this.list.issuesSize > this.list.issues.length) {
+            this.list.page++;
+            this.list.getIssues(false);
+          }
+
+          if (this.scrollHeight() > this.listHeight()) {
+            this.showCount = true;
+          } else {
+            this.showCount = false;
+          }
+        });
       }
     },
     methods: {
@@ -58,6 +73,7 @@
         group: 'issues',
         sort: false,
         disabled: this.disabled,
+        filter: '.board-list-count',
         onStart: (e) => {
           const card = this.$refs.issue[e.oldIndex];
 
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index be2b8c5..91fd620 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -11,6 +11,7 @@ class List {
     this.loading = true;
     this.loadingMore = false;
     this.issues = [];
+    this.issuesSize = 0;
 
     if (obj.label) {
       this.label = new ListLabel(obj.label);
@@ -51,17 +52,13 @@ class List {
   }
 
   nextPage () {
-    if (Math.floor(this.issues.length / 20) === this.page) {
+    if (this.issuesSize > this.issues.length) {
       this.page++;
 
       return this.getIssues(false);
     }
   }
 
-  canSearch () {
-    return this.type === 'backlog';
-  }
-
   getIssues (emptyIssues = true) {
     const filters = this.filters;
     let data = { page: this.page };
@@ -80,12 +77,13 @@ class List {
       .then((resp) => {
         const data = resp.json();
         this.loading = false;
+        this.issuesSize = data.size;
 
         if (emptyIssues) {
           this.issues = [];
         }
 
-        this.createIssues(data);
+        this.createIssues(data.issues);
       });
   }
 
@@ -96,14 +94,20 @@ class List {
   }
 
   addIssue (issue, listFrom) {
-    this.issues.push(issue);
+    if (!this.findIssue(issue.id)) {
+      this.issues.push(issue);
 
-    if (this.label) {
-      issue.addLabel(this.label);
-    }
+      if (this.label) {
+        issue.addLabel(this.label);
+      }
 
-    if (listFrom) {
-      gl.boardService.moveIssue(issue.id, listFrom.id, this.id);
+      if (listFrom) {
+        this.issuesSize++;
+        gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
+          .then(() => {
+            listFrom.getIssues(false);
+          });
+      }
     }
   }
 
@@ -116,6 +120,7 @@ class List {
       const matchesRemove = removeIssue.id === issue.id;
 
       if (matchesRemove) {
+        this.issuesSize--;
         issue.removeLabel(this.label);
       }
 
diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6
index 18f26a1..bd07ee0 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js.es6
+++ b/app/assets/javascripts/boards/stores/boards_store.js.es6
@@ -15,7 +15,8 @@
         author_id: gl.utils.getParameterValues('author_id')[0],
         assignee_id: gl.utils.getParameterValues('assignee_id')[0],
         milestone_title: gl.utils.getParameterValues('milestone_title')[0],
-        label_name: gl.utils.getParameterValues('label_name[]')
+        label_name: gl.utils.getParameterValues('label_name[]'),
+        search: ''
       };
     },
     addList (listObj) {
diff --git a/app/assets/javascripts/boards/test_utils/simulate_drag.js b/app/assets/javascripts/boards/test_utils/simulate_drag.js
old mode 100755
new mode 100644
diff --git a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6
index f9f9f79..b5ff3a8 100644
--- a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6
+++ b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6
@@ -1,10 +1,7 @@
-Vue.http.interceptors.push((request, next)  => {
+Vue.http.interceptors.push((request, next) => {
   Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
 
-  Vue.nextTick(() => {
-    setTimeout(() => {
-      Vue.activeResources--;
-    }, 500);
+  next(function (response) {
+    Vue.activeResources--;
   });
-  next();
 });
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
index 1e0148e..5fef972 100644
--- a/app/assets/javascripts/breakpoints.js
+++ b/app/assets/javascripts/breakpoints.js
@@ -23,6 +23,7 @@
         if ($(allDeviceSelector.join(",")).length) {
           return;
         }
+        // Create all the elements
         els = $.map(BREAKPOINTS, function(breakpoint) {
           return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
         });
@@ -40,6 +41,7 @@
       BreakpointInstance.prototype.getBreakpointSize = function() {
         var $visibleDevice;
         $visibleDevice = this.visibleDevice;
+        // the page refreshed via turbolinks
         if (!$visibleDevice().length) {
           this.setup();
         }
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 0d7d29b..78d21c0 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -16,6 +16,7 @@
       this.toggleSidebar = bind(this.toggleSidebar, this);
       this.updateDropdown = bind(this.updateDropdown, this);
       clearInterval(Build.interval);
+      // Init breakpoint checker
       this.bp = Breakpoints.get();
       $('.js-build-sidebar').niceScroll();
 
@@ -26,10 +27,11 @@
       $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
       $(window).off('resize.build').on('resize.build', this.hideSidebar);
       $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
+      $('#js-build-scroll > a').off('click').on('click', this.stepTrace);
       this.updateArtifactRemoveDate();
       if ($('#build-trace').length) {
         this.getInitialBuildTrace();
-        this.initScrollButtonAffix();
+        this.initScrollButtons();
       }
       if (this.build_status === "running" || this.build_status === "pending") {
         $('#autoscroll-button').on('click', function() {
@@ -42,6 +44,9 @@
             $(this).data("state", "enabled");
             return $(this).text("disable autoscroll");
           }
+        //
+        // Bind autoscroll button to follow build output
+        //
         });
         Build.interval = setInterval((function(_this) {
           return function() {
@@ -49,17 +54,23 @@
               return _this.getBuildTrace();
             }
           };
+        //
+        // Check for new build output if user still watching build page
+        // Only valid for runnig build when output changes during time
+        //
         })(this), 4000);
       }
     }
 
     Build.prototype.getInitialBuildTrace = function() {
+      var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped']
+
       return $.ajax({
         url: this.build_url,
         dataType: 'json',
         success: function(build_data) {
           $('.js-build-output').html(build_data.trace_html);
-          if (build_data.status === 'success' || build_data.status === 'failed') {
+          if (removeRefreshStatuses.indexOf(build_data.status) >= 0) {
             return $('.js-build-refresh').remove();
           }
         }
@@ -96,7 +107,7 @@
       }
     };
 
-    Build.prototype.initScrollButtonAffix = function() {
+    Build.prototype.initScrollButtons = function() {
       var $body, $buildScroll, $buildTrace;
       $buildScroll = $('#js-build-scroll');
       $body = $('body');
@@ -155,6 +166,14 @@
       this.populateJobs(stage);
     };
 
+    Build.prototype.stepTrace = function(e) {
+      e.preventDefault();
+      $currentTarget = $(e.currentTarget);
+      $.scrollTo($currentTarget.attr('href'), {
+        offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
+      });
+    };
+
     return Build;
 
   })();
diff --git a/app/assets/javascripts/build_variables.js.es6 b/app/assets/javascripts/build_variables.js.es6
new file mode 100644
index 0000000..8d3e297
--- /dev/null
+++ b/app/assets/javascripts/build_variables.js.es6
@@ -0,0 +1,6 @@
+$(function(){
+  $('.reveal-variables').off('click').on('click',function(){
+    $('.js-build').toggle().niceScroll();
+    $(this).hide();
+  });
+});
diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image-file.js
index c0d0b2d..e893491 100644
--- a/app/assets/javascripts/commit/image-file.js
+++ b/app/assets/javascripts/commit/image-file.js
@@ -2,6 +2,7 @@
   this.ImageFile = (function() {
     var prepareFrames;
 
+    // Width where images must fits in, for 2-up this gets divided by 2
     ImageFile.availWidth = 900;
 
     ImageFile.viewModes = ['two-up', 'swipe'];
@@ -9,6 +10,7 @@
     function ImageFile(file) {
       this.file = file;
       this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
+        // Determine if old and new file has same dimensions, if not show 'two-up' view
         return function(deletedWidth, deletedHeight) {
           return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
             if (width === deletedWidth && height === deletedHeight) {
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 37f168c..9132089 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -45,6 +45,7 @@
           CommitsList.content.html(data.html);
           return history.replaceState({
             page: commitsUrl
+          // Change url so if user reload a page - search results are saved
           }, document.title, commitsUrl);
         },
         dataType: "json"
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
index c43af17..3e20db7 100644
--- a/app/assets/javascripts/copy_to_clipboard.js
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -6,14 +6,19 @@
 
   genericSuccess = function(e) {
     showTooltip(e.trigger, 'Copied!');
+    // Clear the selection and blur the trigger so it loses its border
     e.clearSelection();
     return $(e.trigger).blur();
   };
 
+  // Safari doesn't support `execCommand`, so instead we inform the user to
+  // copy manually.
+  //
+  // See http://clipboardjs.com/#browser-support
   genericError = function(e) {
     var key;
     if (/Mac/i.test(navigator.userAgent)) {
-      key = '⌘';
+      key = '⌘'; // Command
     } else {
       key = 'Ctrl';
     }
diff --git a/app/assets/javascripts/cycle-analytics.js.es6 b/app/assets/javascripts/cycle-analytics.js.es6
new file mode 100644
index 0000000..cd9886b
--- /dev/null
+++ b/app/assets/javascripts/cycle-analytics.js.es6
@@ -0,0 +1,93 @@
+((global) => {
+
+  const COOKIE_NAME = 'cycle_analytics_help_dismissed';
+  const store = gl.cycleAnalyticsStore = {
+    isLoading: true,
+    hasError: false,
+    isHelpDismissed: $.cookie(COOKIE_NAME),
+    analytics: {}
+  };
+
+  gl.CycleAnalytics = class CycleAnalytics {
+    constructor() {
+      const that = this;
+
+      this.vue = new Vue({
+        el: '#cycle-analytics',
+        name: 'CycleAnalytics',
+        created: this.fetchData(),
+        data: store,
+        methods: {
+          dismissLanding() {
+            that.dismissLanding();
+          }
+        }
+      });
+    }
+
+    fetchData(options) {
+      store.isLoading = true;
+      options = options || { startDate: 30 };
+
+      $.ajax({
+        url: $('#cycle-analytics').data('request-path'),
+        method: 'GET',
+        dataType: 'json',
+        contentType: 'application/json',
+        data: { start_date: options.startDate }
+      }).done((data) => {
+        this.decorateData(data);
+        this.initDropdown();
+      })
+      .error((data) => {
+        this.handleError(data);
+      })
+      .always(() => {
+        store.isLoading = false;
+      })
+    }
+
+    decorateData(data) {
+      data.summary = data.summary || [];
+      data.stats = data.stats || [];
+
+      data.summary.forEach((item) => {
+        item.value = item.value || '-';
+      });
+
+      data.stats.forEach((item) => {
+        item.value = item.value || '- - -';
+      });
+
+      store.analytics = data;
+    }
+
+    handleError(data) {
+      store.hasError = true;
+      new Flash('There was an error while fetching cycle analytics data.', 'alert');
+    }
+
+    dismissLanding() {
+      store.isHelpDismissed = true;
+      $.cookie(COOKIE_NAME, true, {
+        path: gon.relative_url_root || '/'
+      });
+    }
+
+    initDropdown() {
+      const $dropdown = $('.js-ca-dropdown');
+      const $label = $dropdown.find('.dropdown-label');
+
+      $dropdown.find('li a').off('click').on('click', (e) => {
+        e.preventDefault();
+        const $target = $(e.currentTarget);
+        const value = $target.data('value');
+
+        $label.text($target.text().trim());
+        this.fetchData({ startDate: value });
+      })
+    }
+
+  }
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 3dd7ceb..c8634b7 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -39,6 +39,9 @@
             bottom: unfoldBottom,
             offset: offset,
             unfold: unfold,
+            // indent is used to compensate for single space indent to fit
+            // '+' and '-' prepended to diff lines,
+            // see https://gitlab.com/gitlab-org/gitlab-ce/issues/707
             indent: 1,
             view: file.data('view')
           };
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 7d507c7..ddf11ec 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -23,6 +23,7 @@
         case 'projects:boards:show':
           shortcut_handler = new ShortcutsNavigation();
           break;
+        case 'projects:merge_requests:index':
         case 'projects:issues:index':
           Issuable.init();
           new IssuableBulkActions();
@@ -93,6 +94,7 @@
           break;
         case "projects:merge_requests:conflicts":
           window.mcui = new MergeConflictResolver()
+          break;
         case 'projects:merge_requests:index':
           shortcut_handler = new ShortcutsNavigation();
           Issuable.init();
@@ -167,6 +169,8 @@
           }
           break;
         case 'projects:network:show':
+          // Ensure we don't create a particular shortcut handler here. This is
+          // already created, where the network graph is created.
           shortcut_handler = true;
           break;
         case 'projects:forks:new':
@@ -186,6 +190,9 @@
           new gl.ProtectedBranchCreate();
           new gl.ProtectedBranchEditList();
           break;
+        case 'projects:cycle_analytics:show':
+          new gl.CycleAnalytics();
+          break;
       }
       switch (path.first()) {
         case 'admin':
@@ -199,9 +206,13 @@
               break;
             case 'labels':
               switch (path[2]) {
+                case 'new':
                 case 'edit':
                   new Labels();
               }
+            case 'abuse_reports':
+              new gl.AbuseReports();
+              break;
           }
           break;
         case 'dashboard':
@@ -259,12 +270,14 @@
               shortcut_handler = new ShortcutsNavigation();
           }
       }
+      // If we haven't installed a custom shortcut handler, install the default one
       if (!shortcut_handler) {
         return new Shortcuts();
       }
     };
 
     Dispatcher.prototype.initSearch = function() {
+      // Only when search form is present
       if ($('.search').length) {
         return new SearchAutocomplete();
       }
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index 5a725a4..bf68b7e 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -2,6 +2,7 @@
   this.DueDateSelect = (function() {
     function DueDateSelect() {
       var $datePicker, $dueDate, $loading;
+      // Milestone edit/new form
       $datePicker = $('.datepicker');
       if ($datePicker.length) {
         $dueDate = $('#milestone_due_date');
@@ -16,6 +17,7 @@
         e.preventDefault();
         return $.datepicker._clearDate($datePicker);
       });
+      // Issuable sidebar
       $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
       $('.js-due-date-select').each(function(i, dropdown) {
         var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL;
@@ -38,6 +40,7 @@
         });
         addDueDate = function(isDropdown) {
           var data, date, mediumDate, value;
+          // Create the post date
           value = $("input[name='" + fieldName + "']").val();
           if (value !== '') {
             date = new Date(value.replace(new RegExp('-', 'g'), ','));
diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js
index ae3dde6..4978e24 100644
--- a/app/assets/javascripts/extensions/jquery.js
+++ b/app/assets/javascripts/extensions/jquery.js
@@ -1,3 +1,4 @@
+// Disable an element and add the 'disabled' Bootstrap class
 (function() {
   $.fn.extend({
     disable: function() {
@@ -5,6 +6,7 @@
     }
   });
 
+  // Enable an element and remove the 'disabled' Bootstrap class
   $.fn.extend({
     enable: function() {
       return $(this).removeAttr('disabled').removeClass('disabled');
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index b2e49b7..3fb3b1a 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -39,12 +39,13 @@
     FilesCommentButton.prototype.render = function(e) {
       var $currentTarget, buttonParentElement, lineContentElement, textFileElement;
       $currentTarget = $(e.currentTarget);
+
       buttonParentElement = this.getButtonParent($currentTarget);
-      if (!this.shouldRender(e, buttonParentElement)) {
-        return;
-      }
-      textFileElement = this.getTextFileElement($currentTarget);
+      if (!this.validateButtonParent(buttonParentElement)) return;
       lineContentElement = this.getLineContent($currentTarget);
+      if (!this.validateLineContent(lineContentElement)) return;
+
+      textFileElement = this.getTextFileElement($currentTarget);
       buttonParentElement.append(this.buildButton({
         noteableType: textFileElement.attr('data-noteable-type'),
         noteableID: textFileElement.attr('data-noteable-id'),
@@ -119,10 +120,14 @@
       return newButtonParent.is(this.getButtonParent($(e.currentTarget)));
     };
 
-    FilesCommentButton.prototype.shouldRender = function(e, buttonParentElement) {
+    FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
       return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) && $(COMMENT_BUTTON_CLASS, buttonParentElement).length === 0;
     };
 
+    FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
+      return lineContentElement.attr('data-discussion-id') && lineContentElement.attr('data-discussion-id') !== '';
+    };
+
     return FilesCommentButton;
 
   })();
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 3dca06d..d0786bf 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -1,3 +1,4 @@
+// Creates the variables for setting up GFM auto-completion
 (function() {
   if (window.GitLab == null) {
     window.GitLab = {};
@@ -8,18 +9,22 @@
     dataLoaded: false,
     cachedData: {},
     dataSource: '',
+    // Emoji
     Emoji: {
       template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
     },
+    // Team Members
     Members: {
       template: '<li>${username} <small>${title}</small></li>'
     },
     Labels: {
       template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
     },
+    // Issues and MergeRequests
     Issues: {
       template: '<li><small>${id}</small> ${title}</li>'
     },
+    // Milestones
     Milestones: {
       template: '<li>${title}</li>'
     },
@@ -48,8 +53,11 @@
       }
     },
     setup: function(input) {
+      // Add GFM auto-completion to all input fields, that accept GFM input.
       this.input = input || $('.js-gfm-input');
+      // destroy previous instances
       this.destroyAtWho();
+      // set up instances
       this.setupAtWho();
       if (this.dataSource) {
         if (!this.dataLoading && !this.cachedData) {
@@ -63,6 +71,11 @@
                 return _this.loadData(data);
               });
             };
+          // We should wait until initializations are done
+          // and only trigger the last .setup since
+          // The previous .dataSource belongs to the previous issuable
+          // and the last one will have the **proper** .dataSource property
+          // TODO: Make this a singleton and turn off events when moving to another page
           })(this), 1000);
         }
         if (this.cachedData != null) {
@@ -71,6 +84,7 @@
       }
     },
     setupAtWho: function() {
+      // Emoji
       this.input.atwho({
         at: ':',
         displayTpl: (function(_this) {
@@ -90,6 +104,7 @@
           beforeInsert: this.DefaultOptions.beforeInsert
         }
       });
+      // Team Members
       this.input.atwho({
         at: '@',
         displayTpl: (function(_this) {
@@ -321,13 +336,22 @@
     loadData: function(data) {
       this.cachedData = data;
       this.dataLoaded = true;
+      // load members
       this.input.atwho('load', '@', data.members);
+      // load issues
       this.input.atwho('load', 'issues', data.issues);
+      // load milestones
       this.input.atwho('load', 'milestones', data.milestones);
+      // load merge requests
       this.input.atwho('load', 'mergerequests', data.mergerequests);
+      // load emojis
       this.input.atwho('load', ':', data.emojis);
+      // load labels
       this.input.atwho('load', '~', data.labels);
+      // load commands
       this.input.atwho('load', '/', data.commands);
+      // This trigger at.js again
+      // otherwise we would be stuck with loading until the user types
       return $(':focus').trigger('keyup');
     }
   };
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 0179b32..1b6db64 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -21,12 +21,14 @@
       $clearButton = $inputContainer.find('.js-dropdown-input-clear');
       this.indeterminateIds = [];
       $clearButton.on('click', (function(_this) {
+        // Clear click
         return function(e) {
           e.preventDefault();
           e.stopPropagation();
           return _this.input.val('').trigger('keyup').focus();
         };
       })(this));
+      // Key events
       timeout = "";
       this.input
         .on('keydown', function (e) {
@@ -49,6 +51,7 @@
           if (keyCode === 13 && !options.elIsInput) {
             return false;
           }
+          // Only filter asynchronously only if option remote is set
           if (this.options.remote) {
             clearTimeout(timeout);
             return timeout = setTimeout(function() {
@@ -79,11 +82,27 @@
       if ((data != null) && !this.options.filterByText) {
         results = data;
         if (search_text !== '') {
+          // When data is an array of objects therefore [object Array] e.g.
+          // [
+          //   { prop: 'foo' },
+          //   { prop: 'baz' }
+          // ]
           if (_.isArray(data)) {
             results = fuzzaldrinPlus.filter(data, search_text, {
               key: this.options.keys
             });
           } else {
+            // If data is grouped therefore an [object Object]. e.g.
+            // {
+            //   groupName1: [
+            //     { prop: 'foo' },
+            //     { prop: 'baz' }
+            //   ],
+            //   groupName2: [
+            //     { prop: 'abc' },
+            //     { prop: 'def' }
+            //   ]
+            // }
             if (gl.utils.isObject(data)) {
               results = {};
               for (key in data) {
@@ -117,7 +136,7 @@
             }
           });
         } else {
-          return elements.show();
+          return elements.show().removeClass('option-hidden');
         }
       }
     };
@@ -140,6 +159,7 @@
           this.options.beforeSend();
         }
         return this.dataEndpoint("", (function(_this) {
+          // Fetch the data by calling the data funcfion
           return function(data) {
             if (_this.options.success) {
               _this.options.success(data);
@@ -171,6 +191,7 @@
           };
         })(this)
       });
+    // Fetch the data through ajax if the data is a string
     };
 
     return GitLabDropdownRemote;
@@ -190,9 +211,9 @@
 
     currentIndex = -1;
 
-    NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden';
+    NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
 
-    SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")";
+    SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
 
     CURSOR_SELECT_SCROLL_PADDING = 5
 
@@ -209,13 +230,18 @@
       self = this;
       selector = $(this.el).data("target");
       this.dropdown = selector != null ? $(selector) : $(this.el).parent();
+      // Set Defaults
       ref = this.options, this.filterInput = (ref1 = ref.filterInput) != null ? ref1 : this.getElement(FILTER_INPUT), this.highlight = (ref2 = ref.highlight) != null ? ref2 : false, this.filterInputBlur = (ref3 = ref.filterInputBlur) != null ? ref3 : true;
+      // If no input is passed create a default one
       self = this;
+      // If selector was passed
       if (_.isString(this.filterInput)) {
         this.filterInput = this.getElement(this.filterInput);
       }
       searchFields = this.options.search ? this.options.search.fields : [];
       if (this.options.data) {
+        // If we provided data
+        // data could be an array of objects or a group of arrays
         if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
           this.fullData = this.options.data;
           currentIndex = -1;
@@ -232,10 +258,12 @@
                   return _this.filter.input.trigger('keyup');
                 }
               };
+            // Remote data
             })(this)
           });
         }
       }
+      // Init filterable
       if (this.options.filterable) {
         this.filter = new GitLabDropdownFilter(this.filterInput, {
           elIsInput: $(this.el).is('input'),
@@ -278,12 +306,14 @@
           })(this)
         });
       }
+      // Event listeners
       this.dropdown.on("shown.bs.dropdown", this.opened);
       this.dropdown.on("hidden.bs.dropdown", this.hidden);
       $(this.el).on("update.label", this.updateLabel);
       this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate);
       this.dropdown.on('keyup', (function(_this) {
         return function(e) {
+          // Escape key
           if (e.which === 27) {
             return $('.dropdown-menu-close', _this.dropdown).trigger('click');
           }
@@ -322,11 +352,18 @@
           if (self.options.clicked) {
             self.options.clicked(selected, $el, e);
           }
-          return $el.trigger('blur');
+
+          // Update label right after all modifications in dropdown has been done
+          if (self.options.toggleLabel) {
+            self.updateLabel(selected, $el, self);
+          }
+
+          $el.trigger('blur');
         });
       }
     }
 
+    // Finds an element inside wrapper element
     GitLabDropdown.prototype.getElement = function(selector) {
       return this.dropdown.find(selector);
     };
@@ -344,6 +381,7 @@
         }
       }
       menu.toggleClass(PAGE_TWO_CLASS);
+      // Focus first visible input on active page
       return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus();
     };
 
@@ -351,23 +389,28 @@
       var full_html, groupData, html, name;
       this.renderedData = data;
       if (this.options.filterable && data.length === 0) {
+        // render no matching results
         html = [this.noResults()];
       } else {
+        // Handle array groups
         if (gl.utils.isObject(data)) {
           html = [];
           for (name in data) {
             groupData = data[name];
             html.push(this.renderItem({
               header: name
+            // Add header for each group
             }, name));
             this.renderData(groupData, name).map(function(item) {
               return html.push(item);
             });
           }
         } else {
+          // Render each row
           html = this.renderData(data);
         }
       }
+      // Render the full menu
       full_html = this.renderMenu(html);
       return this.appendMenu(full_html);
     };
@@ -406,6 +449,7 @@
       if (this.options.setActiveIds) {
         this.options.setActiveIds.call(this);
       }
+      // Makes indeterminate items effective
       if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
         this.parseData(this.fullData);
       }
@@ -427,6 +471,8 @@
       if (this.options.filterable) {
         $input.blur().val("");
       }
+      // Triggering 'keyup' will re-render the dropdown which is not always required
+      // specially if we want to keep the state of the dropdown needed for bulk-assignment
       if (!this.options.persistWhenHide) {
         $input.trigger("keyup");
       }
@@ -439,6 +485,7 @@
       return this.dropdown.trigger('hidden.gl.dropdown');
     };
 
+    // Render the full menu
     GitLabDropdown.prototype.renderMenu = function(html) {
       var menu_html;
       menu_html = "";
@@ -450,6 +497,7 @@
       return menu_html;
     };
 
+    // Append the menu into the dropdown
     GitLabDropdown.prototype.appendMenu = function(html) {
       var selector;
       selector = '.dropdown-content';
@@ -465,35 +513,42 @@
         group = false;
       }
       if (index == null) {
+        // Render the row
         index = false;
       }
       html = "";
+      // Divider
       if (data === "divider") {
         return "<li class='divider'></li>";
       }
+      // Separator is a full-width divider
       if (data === "separator") {
         return "<li class='separator'></li>";
       }
+      // Header
       if (data.header != null) {
         return _.template('<li class="dropdown-header"><%- header %></li>')({ header: data.header });
       }
       if (this.options.renderRow) {
+        // Call the render function
         html = this.options.renderRow.call(this.options, data, this);
       } else {
         if (!selected) {
           value = this.options.id ? this.options.id(data) : data.id;
-          fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName;
+          fieldName = this.options.fieldName;
 
           field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
           if (field.length) {
             selected = true;
           }
         }
+        // Set URL
         if (this.options.url != null) {
           url = this.options.url(data);
         } else {
           url = data.url != null ? data.url : '#';
         }
+        // Set Text
         if (this.options.text != null) {
           text = this.options.text(data);
         } else {
@@ -540,6 +595,7 @@
 
     GitLabDropdown.prototype.rowClicked = function(el) {
       var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value;
+      fieldName = this.options.fieldName;
       isInput = $(this.el).is('input');
       if (this.renderedData) {
         groupName = el.data('group');
@@ -551,34 +607,31 @@
           selectedObject = this.renderedData[selectedIndex];
         }
       }
-      fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName;
+      field = [];
       value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
       if (isInput) {
         field = $(this.el);
-      } else {
-        field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
+      } else if(value) {
+        field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
       }
       if (el.hasClass(ACTIVE_CLASS)) {
         el.removeClass(ACTIVE_CLASS);
-        if (isInput) {
-          field.val('');
-        } else {
-          field.remove();
-        }
-        if (this.options.toggleLabel) {
-          this.updateLabel(selectedObject, el, this);
+        if (field && field.length) {
+          if (isInput) {
+            field.val('');
+          } else {
+            field.remove();
+          }
         }
-        return selectedObject;
       } else if (el.hasClass(INDETERMINATE_CLASS)) {
         el.addClass(ACTIVE_CLASS);
         el.removeClass(INDETERMINATE_CLASS);
-        if (value == null) {
+        if (field && field.length && value == null) {
           field.remove();
         }
-        if (!field.length && fieldName) {
+        if ((!field || !field.length) && fieldName) {
           this.addInput(fieldName, value, selectedObject);
         }
-        return selectedObject;
       } else {
         if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
           this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
@@ -586,33 +639,30 @@
             this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
           }
         }
-        if (value == null) {
+        if (field && field.length && value == null) {
           field.remove();
         }
+        // Toggle active class for the tick mark
         el.addClass(ACTIVE_CLASS);
-        if (this.options.toggleLabel) {
-          this.updateLabel(selectedObject, el, this);
-        }
         if (value != null) {
-          if (!field.length && fieldName) {
+          if ((!field || !field.length) && fieldName) {
             this.addInput(fieldName, value, selectedObject);
-          } else {
+          } else if (field && field.length) {
             field.val(value).trigger('change');
           }
         }
-        return selectedObject;
       }
+
+      return selectedObject;
     };
 
     GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
       var $input;
+      // Create hidden input for form
       $input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
       if (this.options.inputId != null) {
         $input.attr('id', this.options.inputId);
       }
-      if (selectedObject && selectedObject.type) {
-        $input.attr('data-type', selectedObject.type);
-      }
       return this.dropdown.before($input);
     };
 
@@ -627,6 +677,7 @@
       if (this.dropdown.find(".dropdown-toggle-page").length) {
         selector = ".dropdown-page-one " + selector;
       }
+      // simulate a click on the first link
       $el = $(selector, this.dropdown);
       if ($el.length) {
         var href = $el.attr('href');
@@ -655,11 +706,15 @@
             e.stopImmediatePropagation();
             PREV_INDEX = currentIndex;
             $listItems = $(selector, _this.dropdown);
+            // if @options.filterable
+            //   $input.blur()
             if (currentKeyCode === 40) {
+              // Move down
               if (currentIndex < ($listItems.length - 1)) {
                 currentIndex += 1;
               }
             } else if (currentKeyCode === 38) {
+              // Move up
               if (currentIndex > 0) {
                 currentIndex -= 1;
               }
@@ -687,24 +742,32 @@
 
     GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
       var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
+      // Remove the class for the previously focused row
       $('.is-focused', this.dropdown).removeClass('is-focused');
+      // Update the class for the row at the specific index
       $listItem = $listItems.eq(index);
       $listItem.find('a:first-child').addClass("is-focused");
+      // Dropdown content scroll area
       $dropdownContent = $listItem.closest('.dropdown-content');
       dropdownScrollTop = $dropdownContent.scrollTop();
       dropdownContentHeight = $dropdownContent.outerHeight();
       dropdownContentTop = $dropdownContent.prop('offsetTop');
       dropdownContentBottom = dropdownContentTop + dropdownContentHeight;
+      // Get the offset bottom of the list item
       listItemHeight = $listItem.outerHeight();
       listItemTop = $listItem.prop('offsetTop');
       listItemBottom = listItemTop + listItemHeight;
       if (!index) {
+        // Scroll the dropdown content to the top
         $dropdownContent.scrollTop(0)
       } else if (index === ($listItems.length - 1)) {
+        // Scroll the dropdown content to the bottom
         $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight'));
       } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) {
+        // Scroll the dropdown content down
         $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING);
       } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) {
+        // Scroll the dropdown content up
         return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING);
       }
     };
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 528a673..2703adc 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -3,12 +3,15 @@
     function GLForm(form) {
       this.form = form;
       this.textarea = this.form.find('textarea.js-gfm-input');
+      // Before we start, we should clean up any previous data for this form
       this.destroy();
+      // Setup the form
       this.setupForm();
       this.form.data('gl-form', this);
     }
 
     GLForm.prototype.destroy = function() {
+      // Clean form listeners
       this.clearEventListeners();
       return this.form.data('gl-form', null);
     };
@@ -21,12 +24,15 @@
         this.form.find('.div-dropzone').remove();
         this.form.addClass('gfm-form');
         disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
+        // remove notify commit author checkbox for non-commit notes
         GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
         new DropzoneInput(this.form);
         autosize(this.textarea);
+        // form and textarea event listeners
         this.addEventListeners();
         gl.text.init(this.form);
       }
+      // hide discard button
       this.form.find('.js-note-discard').hide();
       return this.form.show();
     };
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js
index b95faad..4886da9 100644
--- a/app/assets/javascripts/graphs/graphs_bundle.js
+++ b/app/assets/javascripts/graphs/graphs_bundle.js
@@ -1,7 +1,11 @@
-
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
 /*= require_tree . */
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
index a646ca1..7d9d4d7 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -204,6 +204,7 @@
 
     function ContributorsAuthorGraph(data1) {
       this.data = data1;
+      // Don't split graph size in half for mobile devices.
       if ($(window).width() < 768) {
         this.width = $('.content').width() - 80;
       } else {
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index fd5b6dc..7c2eebc 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -38,6 +38,7 @@
               return _this.formatSelection.apply(_this, args);
             },
             dropdownCssClass: "ajax-groups-dropdown",
+            // we do not want to escape markup since we are displaying html in results
             escapeMarkup: function(m) {
               return m;
             }
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 0f84082..4aced1e 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -10,21 +10,24 @@
     ImporterStatus.prototype.initStatusPage = function() {
       $('.js-add-to-import').off('click').on('click', (function(_this) {
         return function(e) {
-          var $btn, $namespace_input, $target_field, $tr, id, new_namespace;
+          var $btn, $namespace_input, $target_field, $tr, id, target_namespace, newName;
           $btn = $(e.currentTarget);
           $tr = $btn.closest('tr');
           $target_field = $tr.find('.import-target');
-          $namespace_input = $target_field.find('input');
+          $namespace_input = $target_field.find('.js-select-namespace option:selected');
           id = $tr.attr('id').replace('repo_', '');
-          new_namespace = null;
+          target_namespace = null;
+          newName = null;
           if ($namespace_input.length > 0) {
-            new_namespace = $namespace_input.prop('value');
-            $target_field.empty().append(new_namespace + "/" + ($target_field.data('project_name')));
+            target_namespace = $namespace_input[0].innerHTML;
+            newName = $target_field.find('#path').prop('value');
+            $target_field.empty().append(target_namespace + "/" + newName);
           }
           $btn.disable().addClass('is-loading');
           return $.post(_this.import_url, {
             repo_id: id,
-            new_namespace: new_namespace
+            target_namespace: target_namespace,
+            new_name: newName
           }, {
             dataType: 'script'
           });
@@ -70,7 +73,7 @@
     if ($('.js-importer-status').length) {
       var jobsImportPath = $('.js-importer-status').data('jobs-import-path');
       var importPath = $('.js-importer-status').data('import-path');
-      
+
       new ImporterStatus(jobsImportPath, importPath);
     }
   });
diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js
deleted file mode 100644
index d0305c6..0000000
--- a/app/assets/javascripts/issuable.js
+++ /dev/null
@@ -1,86 +0,0 @@
-(function() {
-  var issuable_created;
-
-  issuable_created = false;
-
-  this.Issuable = {
-    init: function() {
-      Issuable.initTemplates();
-      Issuable.initSearch();
-      Issuable.initChecks();
-      return Issuable.initLabelFilterRemove();
-    },
-    initTemplates: function() {
-      return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color:  [...]
-    },
-    initSearch: function() {
-      this.timer = null;
-      return $('#issue_search').off('keyup').on('keyup', function() {
-        clearTimeout(this.timer);
-        return this.timer = setTimeout(function() {
-          var $form, $input, $search;
-          $search = $('#issue_search');
-          $form = $('.js-filter-form');
-          $input = $("input[name='" + ($search.attr('name')) + "']", $form);
-          if ($input.length === 0) {
-            $form.append("<input type='hidden' name='" + ($search.attr('name')) + "' value='" + (_.escape($search.val())) + "'/>");
-          } else {
-            $input.val($search.val());
-          }
-          if ($search.val() !== '') {
-            return Issuable.filterResults($form);
-          }
-        }, 500);
-      });
-    },
-    initLabelFilterRemove: function() {
-      return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) {
-        var $button;
-        $button = $(this);
-        $('input[name="label_name[]"]').filter(function() {
-          return this.value === $button.data('label');
-        }).remove();
-        Issuable.filterResults($('.filter-form'));
-        return $('.js-label-select').trigger('update.label');
-      });
-    },
-    filterResults: (function(_this) {
-      return function(form) {
-        var formAction, formData, issuesUrl;
-        formData = form.serialize();
-        formAction = form.attr('action');
-        issuesUrl = formAction;
-        issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
-        issuesUrl += formData;
-        return Turbolinks.visit(issuesUrl);
-      };
-    })(this),
-    initChecks: function() {
-      this.issuableBulkActions = $('.bulk-update').data('bulkActions');
-      $('.check_all_issues').off('click').on('click', function() {
-        $('.selected_issue').prop('checked', this.checked);
-        return Issuable.checkChanged();
-      });
-      return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this));
-    },
-    checkChanged: function() {
-      var checked_issues, ids;
-      checked_issues = $('.selected_issue:checked');
-      if (checked_issues.length > 0) {
-        ids = $.map(checked_issues, function(value) {
-          return $(value).data('id');
-        });
-        $('#update_issues_ids').val(ids);
-        $('.issues-other-filters').hide();
-        $('.issues_bulk_update').show();
-      } else {
-        $('#update_issues_ids').val([]);
-        $('.issues_bulk_update').hide();
-        $('.issues-other-filters').show();
-        this.issuableBulkActions.willUpdateLabels = false;
-      }
-      return true;
-    }
-  };
-
-}).call(this);
diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6
new file mode 100644
index 0000000..73e2664
--- /dev/null
+++ b/app/assets/javascripts/issuable.js.es6
@@ -0,0 +1,110 @@
+(function() {
+  var issuable_created;
+
+  issuable_created = false;
+
+  this.Issuable = {
+    init: function() {
+      Issuable.initTemplates();
+      Issuable.initSearch();
+      Issuable.initChecks();
+      Issuable.initResetFilters();
+      return Issuable.initLabelFilterRemove();
+    },
+    initTemplates: function() {
+      return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color:  [...]
+    },
+    initSearch: function() {
+      // `immediate` param set to false debounces on the `trailing` edge, lets user finish typing
+      const debouncedExecSearch = _.debounce(Issuable.executeSearch, 500, false);
+
+      $('#issuable_search').off('keyup').on('keyup', debouncedExecSearch);
+
+      // ensures existing filters are preserved when manually submitted
+      $('#issue_search_form').on('submit', (e) => {
+        e.preventDefault();
+        debouncedExecSearch(e);
+      });
+    },
+    executeSearch: function(e) {
+      const $search = $('#issuable_search');
+      const $searchName = $search.attr('name');
+      const $searchValue = $search.val();
+      const $filtersForm = $('.js-filter-form');
+      const $input = $(`input[name='${$searchName}']`, $filtersForm);
+
+      if (!$input.length) {
+        $filtersForm.append(`<input type='hidden' name='${$searchName}' value='${_.escape($searchValue)}'/>`);
+      } else {
+        $input.val($searchValue);
+      }
+
+      Issuable.filterResults($filtersForm);
+    },
+    initLabelFilterRemove: function() {
+      return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) {
+        var $button;
+        $button = $(this);
+        // Remove the label input box
+        $('input[name="label_name[]"]').filter(function() {
+          return this.value === $button.data('label');
+        }).remove();
+        // Submit the form to get new data
+        Issuable.filterResults($('.filter-form'));
+        return $('.js-label-select').trigger('update.label');
+      });
+    },
+    filterResults: (function(_this) {
+      return function(form) {
+        var formAction, formData, issuesUrl;
+        formData = form.serialize();
+        formAction = form.attr('action');
+        issuesUrl = formAction;
+        issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
+        issuesUrl += formData;
+        return Turbolinks.visit(issuesUrl);
+      };
+    })(this),
+    initResetFilters: function() {
+      $('.reset-filters').on('click', function(e) {
+        e.preventDefault();
+        const target = e.target;
+        const $form = $(target).parents('.js-filter-form');
+        const baseIssuesUrl = target.href;
+
+        $form.attr('action', baseIssuesUrl);
+        Turbolinks.visit(baseIssuesUrl);
+      });
+    },
+    initChecks: function() {
+      this.issuableBulkActions = $('.bulk-update').data('bulkActions');
+      $('.check_all_issues').off('click').on('click', function() {
+        $('.selected_issue').prop('checked', this.checked);
+        return Issuable.checkChanged();
+      });
+      return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this));
+    },
+    checkChanged: function() {
+      const $checkedIssues = $('.selected_issue:checked');
+      const $updateIssuesIds = $('#update_issuable_ids');
+      const $issuesOtherFilters = $('.issues-other-filters');
+      const $issuesBulkUpdate = $('.issues_bulk_update');
+
+      if ($checkedIssues.length > 0) {
+        let ids = $.map($checkedIssues, function(value) {
+          return $(value).data('id');
+        });
+        $updateIssuesIds.val(ids);
+        $issuesOtherFilters.hide();
+        $issuesBulkUpdate.show();
+      } else {
+        $updateIssuesIds.val([]);
+        $issuesBulkUpdate.hide();
+        $issuesOtherFilters.show();
+        this.issuableBulkActions.willUpdateLabels = false;
+      }
+      return true;
+    }
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 297d4f0..b7f92ae 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -102,20 +102,34 @@
     };
 
     IssuableForm.prototype.initMoveDropdown = function() {
-      var $moveDropdown;
+      var $moveDropdown, pageSize;
       $moveDropdown = $('.js-move-dropdown');
       if ($moveDropdown.length) {
+        pageSize = $moveDropdown.data('page-size');
         return $('.js-move-dropdown').select2({
           ajax: {
             url: $moveDropdown.data('projects-url'),
-            results: function(data) {
+            quietMillis: 125,
+            data: function(term, page, context) {
               return {
-                results: data
+                search: term,
+                offset_id: context
               };
             },
-            data: function(query) {
+            results: function(data) {
+              var context,
+                more;
+
+              if (data.length >= pageSize)
+                more = true;
+
+              if (data[data.length - 1])
+                context = data[data.length - 1].id;
+
               return {
-                search: query
+                results: data,
+                more: more,
+                context: context
               };
             }
           },
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 6838d9d..261bf61 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,10 +1,6 @@
 
 /*= require flash */
-
-
 /*= require jquery.waitforimages */
-
-
 /*= require task_list */
 
 (function() {
@@ -13,6 +9,7 @@
   this.Issue = (function() {
     function Issue() {
       this.submitNoteForm = bind(this.submitNoteForm, this);
+      // Prevent duplicate event bindings
       this.disableTaskList();
       if ($('a.btn-close').length) {
         this.initTaskList();
@@ -99,6 +96,8 @@
         url: $('form.js-issuable-update').attr('action'),
         data: patchData
       });
+    // TODO (rspeicher): Make the issue description inline-editable like a note so
+    // that we can re-use its form here
     };
 
     Issue.prototype.initMergeRequests = function() {
@@ -127,7 +126,9 @@
 
     Issue.prototype.initCanCreateBranch = function() {
       var $container;
-      $container = $('div#new-branch');
+      $container = $('#new-branch');
+      // If the user doesn't have the required permissions the container isn't
+      // rendered at all.
       if ($container.length === 0) {
         return;
       }
@@ -139,7 +140,6 @@
         if (data.can_create_branch) {
           $container.find('.checking').hide();
           $container.find('.available').show();
-          return $container.find('a').attr('disabled', false);
         } else {
           $container.find('.checking').hide();
           return $container.find('.unavailable').show();
diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js
index 98d3358..62a7fc9 100644
--- a/app/assets/javascripts/issues-bulk-assignment.js
+++ b/app/assets/javascripts/issues-bulk-assignment.js
@@ -1,14 +1,17 @@
 (function() {
   this.IssuableBulkActions = (function() {
     function IssuableBulkActions(opts) {
+      // Set defaults
       var ref, ref1, ref2;
       if (opts == null) {
         opts = {};
       }
-      this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issues-list .issue');
+      this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li');
+      // Save instance
       this.form.data('bulkActions', this);
       this.willUpdateLabels = false;
       this.bindEvents();
+      // Fixes bulk-assign not working when navigating through pages
       Issuable.initChecks();
     }
 
@@ -86,6 +89,7 @@
       ref1 = this.getLabelsFromSelection();
       for (j = 0, len1 = ref1.length; j < len1; j++) {
         id = ref1[j];
+        // Only the ones that we are not going to keep
         if (labelsToKeep.indexOf(id) === -1) {
           result.push(id);
         }
@@ -106,7 +110,7 @@
           state_event: this.form.find('input[name="update[state_event]"]').val(),
           assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
           milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
-          issues_ids: this.form.find('input[name="update[issues_ids]"]').val(),
+          issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
           subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
           add_label_ids: [],
           remove_label_ids: []
@@ -147,6 +151,8 @@
       indeterminatedLabels = this.getUnmarkedIndeterminedLabels();
       labelsToApply = this.getLabelsToApply();
       indeterminatedLabels.map(function(id) {
+        // We need to exclude label IDs that will be applied
+        // By not doing this will cause issues from selection to not add labels at all
         if (labelsToApply.indexOf(id) === -1) {
           return result.push(id);
         }
diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js
index fe071fc..cb16e2b 100644
--- a/app/assets/javascripts/labels.js
+++ b/app/assets/javascripts/labels.js
@@ -26,13 +26,16 @@
       var previewColor;
       previewColor = $('input#label_color').val();
       return $('div.label-color-preview').css('background-color', previewColor);
+    // Updates the the preview color with the hex-color input
     };
 
+    // Updates the preview color with a click on a suggested color
     Labels.prototype.setSuggestedColor = function(e) {
       var color;
       color = $(e.currentTarget).data('color');
       $('input#label_color').val(color);
       this.updateColorPreview();
+      // Notify the form, that color has changed
       $('.label-form').trigger('keyup');
       return e.preventDefault();
     };
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 565dbea..3f15a11 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -156,15 +156,17 @@
                 selectedClass.push('is-indeterminate');
               }
               if (active.indexOf(label.id) !== -1) {
+                // Remove is-indeterminate class if the item will be marked as active
                 i = selectedClass.indexOf('is-indeterminate');
                 if (i !== -1) {
                   selectedClass.splice(i, 1);
                 }
                 selectedClass.push('is-active');
+                // Add input manually
                 instance.addInput(this.fieldName, label.id);
               }
             }
-            if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + (this.id(label)) + "']").length) {
+            if (this.id(label) && $form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + this.id(label).toString().replace(/'/g, '\\\'') + "']").length) {
               selectedClass.push('is-active');
             }
             if ($dropdown.hasClass('js-multiselect') && removesAll) {
@@ -172,6 +174,7 @@
             }
             if (label.duplicate) {
               spacing = 100 / label.color.length;
+              // Reduce the colors to 4
               label.color = label.color.filter(function(color, i) {
                 return i < 4;
               });
@@ -192,11 +195,13 @@
             } else {
               colorEl = '';
             }
+            // We need to identify which items are actually labels
             if (label.id) {
               selectedClass.push('label-item');
               $a.attr('data-label-id', label.id);
             }
             $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title);
+            // Return generated html
             return $li.html($a).prop('outerHTML');
           },
           persistWhenHide: $dropdown.data('persistWhenHide'),
@@ -238,6 +243,7 @@
             isIssueIndex = page === 'projects:issues:index';
             isMRIndex = page === 'projects:merge_requests:index';
             $selectbox.hide();
+            // display:block overrides the hide-collapse rule
             $value.removeAttr('style');
             if (page === 'projects:boards:show') {
               return;
@@ -255,6 +261,7 @@
               }
             }
             if ($dropdown.hasClass('js-filter-bulk-update')) {
+              // If we are persisting state we need the classes
               if (!this.options.persistWhenHide) {
                 return $dropdown.parent().find('.is-active, .is-indeterminate').removeClass();
               }
@@ -324,7 +331,9 @@
       if ($('.selected_issue:checked').length) {
         return;
       }
+      // Remove inputs
       $('.issues_bulk_update .labels-filter input[type="hidden"]').remove();
+      // Also restore button text
       return $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label');
     };
 
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index ce472f3..8e2fc0d 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -10,11 +10,13 @@
   };
 
   $(function() {
-    hideEndFade($('.scrolling-tabs'));
+    var $scrollingTabs = $('.scrolling-tabs');
+
+    hideEndFade($scrollingTabs);
     $(window).off('resize.nav').on('resize.nav', function() {
-      return hideEndFade($('.scrolling-tabs'));
+      return hideEndFade($scrollingTabs);
     });
-    return $('.scrolling-tabs').on('scroll', function(event) {
+    $scrollingTabs.off('scroll').on('scroll', function(event) {
       var $this, currentPosition, maxPosition;
       $this = $(this);
       currentPosition = $this.scrollLeft();
@@ -22,6 +24,23 @@
       $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
       return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
     });
+
+    $scrollingTabs.each(function () {
+      var $this = $(this),
+          scrollingTabWidth = $this.width(),
+          $active = $this.find('.active'),
+          activeWidth = $active.width();
+
+      if ($active.length) {
+        var offset = $active.offset().left + activeWidth;
+
+        if (offset > scrollingTabWidth - 30) {
+          var scrollLeft = scrollingTabWidth / 2;
+          scrollLeft = (offset - scrollLeft) - (activeWidth / 2);
+          $this.scrollLeft(scrollLeft);
+        }
+      }
+    });
   });
 
 }).call(this);
diff --git a/app/assets/javascripts/lib/chart.js b/app/assets/javascripts/lib/chart.js
index 8d5e522..d9b07c1 100644
--- a/app/assets/javascripts/lib/chart.js
+++ b/app/assets/javascripts/lib/chart.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/lib/cropper.js b/app/assets/javascripts/lib/cropper.js
index 8ee8180..a88e640 100644
--- a/app/assets/javascripts/lib/cropper.js
+++ b/app/assets/javascripts/lib/cropper.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/lib/d3.js b/app/assets/javascripts/lib/d3.js
index 31e6033..ee1baf5 100644
--- a/app/assets/javascripts/lib/d3.js
+++ b/app/assets/javascripts/lib/d3.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/lib/raphael.js b/app/assets/javascripts/lib/raphael.js
index 923c575..6df427b 100644
--- a/app/assets/javascripts/lib/raphael.js
+++ b/app/assets/javascripts/lib/raphael.js
@@ -1,13 +1,8 @@
 
 /*= require raphael */
-
-
 /*= require g.raphael */
-
-
 /*= require g.bar */
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 10afa7e..8fdf464 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -29,6 +29,7 @@
       if (setTimeago) {
         $timeagoEls.timeago();
         $timeagoEls.tooltip('destroy');
+        // Recreate with custom template
         return $timeagoEls.tooltip({
           template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
         });
@@ -67,6 +68,14 @@
       $.timeago.settings.strings = tmpLocale;
     };
 
+    w.gl.utils.getDayDifference = function(a, b) {
+      var millisecondsPerDay = 1000 * 60 * 60 * 24;
+      var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
+      var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
+
+      return Math.floor((date2 - date1) / millisecondsPerDay);
+    }
+
   })(window);
 
 }).call(this);
diff --git a/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb b/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb
deleted file mode 100644
index 80f9936..0000000
--- a/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-gl.emojiAliases = ->
-  JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>')
diff --git a/app/assets/javascripts/lib/utils/emoji_aliases.js.erb b/app/assets/javascripts/lib/utils/emoji_aliases.js.erb
new file mode 100644
index 0000000..aeb86c9
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/emoji_aliases.js.erb
@@ -0,0 +1,6 @@
+(function() {
+  gl.emojiAliases = function() {
+    return JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>');
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index 42b6ac0..5b338b0 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -6,6 +6,7 @@
       notification = new Notification(message, opts);
       setTimeout(function() {
         return notification.close();
+      // Hide the notification after X amount of seconds
       }, 8000);
       if (onclick) {
         return notification.onclick = onclick;
@@ -22,12 +23,16 @@
         body: body,
         icon: icon
       };
+      // Let's check if the browser supports notifications
       if (!('Notification' in window)) {
 
+      // do nothing
       } else if (Notification.permission === 'granted') {
+        // If it's okay let's create a notification
         return notificationGranted(message, opts, onclick);
       } else if (Notification.permission !== 'denied') {
         return Notification.requestPermission(function(permission) {
+          // If the user accepts, let's create a notification
           if (permission === 'granted') {
             return notificationGranted(message, opts, onclick);
           }
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 1304796..d761a84 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -29,6 +29,7 @@
       lineBefore = this.lineBefore(text, textArea);
       lineAfter = this.lineAfter(text, textArea);
       if (lineBefore === blockTag && lineAfter === blockTag) {
+        // To remove the block tag we have to select the line before & after
         if (blockTag != null) {
           textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
           textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
@@ -63,11 +64,11 @@
       if (!inserted) {
         try {
           document.execCommand("ms-beginUndoUnit");
-        } catch (undefined) {}
+        } catch (error) {}
         textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
         try {
           document.execCommand("ms-endUndoUnit");
-        } catch (undefined) {}
+        } catch (error) {}
       }
       return this.moveCursor(textArea, tag, wrap);
     };
@@ -104,9 +105,12 @@
         return self.updateText($this.closest('.md-area').find('textarea'), $this.data('md-tag'), $this.data('md-block'), !$this.data('md-prepend'));
       });
     };
-    return gl.text.removeListeners = function(form) {
+    gl.text.removeListeners = function(form) {
       return $('.js-md', form).off();
     };
+    return gl.text.truncate = function(string, maxLength) {
+      return string.substr(0, (maxLength - 3)) + '...';
+    };
   })(window);
 
 }).call(this);
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 533310c..f84a20c 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -7,6 +7,8 @@
     if ((base = w.gl).utils == null) {
       base.utils = {};
     }
+    // Returns an array containing the value(s) of the
+    // of the key passed as an argument
     w.gl.utils.getParameterValues = function(sParam) {
       var i, sPageURL, sParameterName, sURLVariables, values;
       sPageURL = decodeURIComponent(window.location.search.substring(1));
@@ -23,6 +25,8 @@
       }
       return values;
     };
+    // @param {Object} params - url keys and value to merge
+    // @param {String} url
     w.gl.utils.mergeUrlParams = function(params, url) {
       var lastChar, newUrl, paramName, paramValue, pattern;
       newUrl = decodeURIComponent(url);
@@ -37,12 +41,14 @@
           newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
         }
       }
+      // Remove a trailing ampersand
       lastChar = newUrl[newUrl.length - 1];
       if (lastChar === '&') {
         newUrl = newUrl.slice(0, -1);
       }
       return newUrl;
     };
+    // removes parameter query string from url. returns the modified url
     w.gl.utils.removeParamQueryString = function(url, param) {
       var urlVariables, variables;
       url = decodeURIComponent(url);
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index f145bd3..93daea1 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -1,17 +1,49 @@
-
+// LineHighlighter
+//
+// Handles single- and multi-line selection and highlight for blob views.
+//
 /*= require jquery.scrollTo */
 
+//
+// ### Example Markup
+//
+//   <div id="blob-content-holder">
+//     <div class="file-content">
+//       <div class="line-numbers">
+//         <a href="#L1" id="L1" data-line-number="1">1</a>
+//         <a href="#L2" id="L2" data-line-number="2">2</a>
+//         <a href="#L3" id="L3" data-line-number="3">3</a>
+//         <a href="#L4" id="L4" data-line-number="4">4</a>
+//         <a href="#L5" id="L5" data-line-number="5">5</a>
+//       </div>
+//       <pre class="code highlight">
+//         <code>
+//           <span id="LC1" class="line">...</span>
+//           <span id="LC2" class="line">...</span>
+//           <span id="LC3" class="line">...</span>
+//           <span id="LC4" class="line">...</span>
+//           <span id="LC5" class="line">...</span>
+//         </code>
+//       </pre>
+//     </div>
+//   </div>
+//
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
   this.LineHighlighter = (function() {
+    // CSS class applied to highlighted lines
     LineHighlighter.prototype.highlightClass = 'hll';
 
+    // Internal copy of location.hash so we're not dependent on `location` in tests
     LineHighlighter.prototype._hash = '';
 
     function LineHighlighter(hash) {
       var range;
       if (hash == null) {
+        // Initialize a LineHighlighter object
+        //
+        // hash - String URL hash for dependency injection in tests
         hash = location.hash;
       }
       this.setHash = bind(this.setHash, this);
@@ -24,6 +56,8 @@
         if (range[0]) {
           this.highlightRange(range);
           $.scrollTo("#L" + range[0], {
+            // Scroll to the first highlighted line on initial load
+            // Offset -50 for the sticky top bar, and another -100 for some context
             offset: -150
           });
         }
@@ -32,6 +66,12 @@
 
     LineHighlighter.prototype.bindEvents = function() {
       $('#blob-content-holder').on('mousedown', 'a[data-line-number]', this.clickHandler);
+      // While it may seem odd to bind to the mousedown event and then throw away
+      // the click event, there is a method to our madness.
+      //
+      // If not done this way, the line number anchor will sometimes keep its
+      // active state even when the event is cancelled, resulting in an ugly border
+      // around the link and/or a persisted underline text decoration.
       return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
         return event.preventDefault();
       });
@@ -44,6 +84,8 @@
       lineNumber = $(event.target).closest('a').data('line-number');
       current = this.hashToRange(this._hash);
       if (!(current[0] && event.shiftKey)) {
+        // If there's no current selection, or there is but Shift wasn't held,
+        // treat this like a single-line selection.
         this.setHash(lineNumber);
         return this.highlightLine(lineNumber);
       } else if (event.shiftKey) {
@@ -59,10 +101,23 @@
 
     LineHighlighter.prototype.clearHighlight = function() {
       return $("." + this.highlightClass).removeClass(this.highlightClass);
+    // Unhighlight previously highlighted lines
     };
 
+    // Convert a URL hash String into line numbers
+    //
+    // hash - Hash String
+    //
+    // Examples:
+    //
+    //   hashToRange('#L5')    # => [5, null]
+    //   hashToRange('#L5-15') # => [5, 15]
+    //   hashToRange('#foo')   # => [null, null]
+    //
+    // Returns an Array
     LineHighlighter.prototype.hashToRange = function(hash) {
       var first, last, matches;
+      //?L(\d+)(?:-(\d+))?$/)
       matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
       if (matches && matches.length) {
         first = parseInt(matches[1]);
@@ -73,10 +128,16 @@
       }
     };
 
+    // Highlight a single line
+    //
+    // lineNumber - Line number to highlight
     LineHighlighter.prototype.highlightLine = function(lineNumber) {
       return $("#LC" + lineNumber).addClass(this.highlightClass);
     };
 
+    // Highlight all lines within a range
+    //
+    // range - Array containing the starting and ending line numbers
     LineHighlighter.prototype.highlightRange = function(range) {
       var i, lineNumber, ref, ref1, results;
       if (range[1]) {
@@ -90,6 +151,7 @@
       }
     };
 
+    // Set the URL hash string
     LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
       var hash;
       if (lastLineNumber) {
@@ -101,10 +163,15 @@
       return this.__setLocationHash__(hash);
     };
 
+    // Make the actual hash change in the browser
+    //
+    // This method is stubbed in tests.
     LineHighlighter.prototype.__setLocationHash__ = function(value) {
       return history.pushState({
         turbolinks: false,
         url: value
+      // We're using pushState instead of assigning location.hash directly to
+      // prevent the page from scrolling on the hashchange event
       }, document.title, value);
     };
 
diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js
index 218f24f..7d8eef1 100644
--- a/app/assets/javascripts/logo.js
+++ b/app/assets/javascripts/logo.js
@@ -1,54 +1,12 @@
 (function() {
-  var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work;
-
   Turbolinks.enableProgressBar();
 
-  defaultClass = 'tanuki-shape';
-
-  pieces = ['path#tanuki-right-cheek', 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-nose', 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek'];
-
-  pieceIndex = 0;
-
-  firstPiece = pieces[0];
-
-  currentTimer = null;
-
-  delay = 150;
-
-  clearHighlights = function() {
-    return $("." + defaultClass + ".highlight").attr('class', defaultClass);
-  };
-
-  start = function() {
-    clearHighlights();
-    pieceIndex = 0;
-    if (pieces[0] !== firstPiece) {
-      pieces.reverse();
-    }
-    if (currentTimer) {
-      clearInterval(currentTimer);
-    }
-    return currentTimer = setInterval(work, delay);
-  };
-
-  stop = function() {
-    clearInterval(currentTimer);
-    return clearHighlights();
-  };
-
-  work = function() {
-    clearHighlights();
-    $(pieces[pieceIndex]).attr('class', defaultClass + " highlight");
-    if (pieceIndex === pieces.length - 1) {
-      pieceIndex = 0;
-      return pieces.reverse();
-    } else {
-      return pieceIndex++;
-    }
-  };
-
-  $(document).on('page:fetch', start);
+  $(document).on('page:fetch', function() {
+    $('.tanuki-logo').addClass('animate');
+  });
 
-  $(document).on('page:change', stop);
+  $(document).on('page:change', function() {
+    $('.tanuki-logo').removeClass('animate');
+  });
 
 }).call(this);
diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6
index 77bffbc..b56fd5a 100644
--- a/app/assets/javascripts/merge_conflict_resolver.js.es6
+++ b/app/assets/javascripts/merge_conflict_resolver.js.es6
@@ -75,10 +75,8 @@ class MergeConflictResolver {
         window.location.href = data.redirect_to;
       })
       .error(() => {
-        new Flash('Something went wrong!');
-      })
-      .always(() => {
         this.vue.isSubmitting = false;
+        new Flash('Something went wrong!');
       });
   }
 
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 56ebf84..05644b3 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,10 +1,6 @@
 
 /*= require jquery.waitforimages */
-
-
 /*= require task_list */
-
-
 /*= require merge_request_tabs */
 
 (function() {
@@ -12,6 +8,11 @@
 
   this.MergeRequest = (function() {
     function MergeRequest(opts) {
+      // Initialize MergeRequest behavior
+      //
+      // Options:
+      //   action - String, current controller action
+      //
       this.opts = opts != null ? opts : {};
       this.submitNoteForm = bind(this.submitNoteForm, this);
       this.$el = $('.merge-request');
@@ -21,6 +22,7 @@
         };
       })(this));
       this.initTabs();
+      // Prevent duplicate event bindings
       this.disableTaskList();
       this.initMRBtnListeners();
       if ($("a.btn-close").length) {
@@ -28,14 +30,17 @@
       }
     }
 
+    // Local jQuery finder
     MergeRequest.prototype.$ = function(selector) {
       return this.$el.find(selector);
     };
 
     MergeRequest.prototype.initTabs = function() {
       if (this.opts.action !== 'new') {
+        // `MergeRequests#new` has no tab-persisting or lazy-loading behavior
         window.mrTabs = new MergeRequestTabs(this.opts);
       } else {
+        // Show the first tab (Commits)
         return $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show');
       }
     };
@@ -96,6 +101,8 @@
         url: $('form.js-issuable-update').attr('action'),
         data: patchData
       });
+    // TODO (rspeicher): Make the merge request description inline-editable like a
+    // note so that we can re-use its form here
     };
 
     return MergeRequest;
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index ad08209..18bbfa7 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,6 +1,49 @@
-
+// MergeRequestTabs
+//
+// Handles persisting and restoring the current tab selection and lazily-loading
+// content on the MergeRequests#show page.
+//
 /*= require jquery.cookie */
 
+//
+// ### Example Markup
+//
+//   <ul class="nav-links merge-request-tabs">
+//     <li class="notes-tab active">
+//       <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1">
+//         Discussion
+//       </a>
+//     </li>
+//     <li class="commits-tab">
+//       <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits">
+//         Commits
+//       </a>
+//     </li>
+//     <li class="diffs-tab">
+//       <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs">
+//         Diffs
+//       </a>
+//     </li>
+//   </ul>
+//
+//   <div class="tab-content">
+//     <div class="notes tab-pane active" id="notes">
+//       Notes Content
+//     </div>
+//     <div class="commits tab-pane" id="commits">
+//       Commits Content
+//     </div>
+//     <div class="diffs tab-pane" id="diffs">
+//       Diffs Content
+//     </div>
+//   </div>
+//
+//   <div class="mr-loading-status">
+//     <div class="loading">
+//       Loading Animation
+//     </div>
+//   </div>
+//
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
@@ -19,6 +62,7 @@
       this.setCurrentAction = bind(this.setCurrentAction, this);
       this.tabShown = bind(this.tabShown, this);
       this.showTab = bind(this.showTab, this);
+      // Store the `location` object, allowing for easier stubbing in tests
       this._location = location;
       this.bindEvents();
       this.activateTab(this.opts.action);
@@ -77,6 +121,7 @@
       }
     };
 
+    // Activate a tab based on the current action
     MergeRequestTabs.prototype.activateTab = function(action) {
       if (action === 'show') {
         action = 'notes';
@@ -84,20 +129,48 @@
       return $(".merge-request-tabs a[data-action='" + action + "']").tab('show');
     };
 
+    // Replaces the current Merge Request-specific action in the URL with a new one
+    //
+    // If the action is "notes", the URL is reset to the standard
+    // `MergeRequests#show` route.
+    //
+    // Examples:
+    //
+    //   location.pathname # => "/namespace/project/merge_requests/1"
+    //   setCurrentAction('diffs')
+    //   location.pathname # => "/namespace/project/merge_requests/1/diffs"
+    //
+    //   location.pathname # => "/namespace/project/merge_requests/1/diffs"
+    //   setCurrentAction('notes')
+    //   location.pathname # => "/namespace/project/merge_requests/1"
+    //
+    //   location.pathname # => "/namespace/project/merge_requests/1/diffs"
+    //   setCurrentAction('commits')
+    //   location.pathname # => "/namespace/project/merge_requests/1/commits"
+    //
+    // Returns the new URL String
     MergeRequestTabs.prototype.setCurrentAction = function(action) {
       var new_state;
+      // Normalize action, just to be safe
       if (action === 'show') {
         action = 'notes';
       }
       this.currentAction = action;
+      // Remove a trailing '/commits' or '/diffs'
       new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines)(\.html)?\/?$/, '');
+      // Append the new action if we're on a tab other than 'notes'
       if (action !== 'notes') {
         new_state += "/" + action;
       }
+      // Ensure parameters and hash come along for the ride
       new_state += this._location.search + this._location.hash;
       history.replaceState({
         turbolinks: true,
         url: new_state
+      // Replace the current history state with the new one without breaking
+      // Turbolinks' history.
+      //
+      // See https://github.com/rails/turbolinks/issues/363
       }, document.title, new_state);
       return new_state;
     };
@@ -159,10 +232,10 @@
       $('.hll').removeClass('hll');
       locationHash = window.location.hash;
       if (locationHash !== '') {
-        hashClassString = "." + (locationHash.replace('#', ''));
+        dataLineString = '[data-line-code="' + locationHash.replace('#', '') + '"]';
         $diffLine = $(locationHash + ":not(.match)", $('#diffs'));
         if (!$diffLine.is('tr')) {
-          $diffLine = $('#diffs').find("td" + locationHash + ", td" + hashClassString);
+          $diffLine = $('#diffs').find("td" + locationHash + ", td" + dataLineString);
         } else {
           $diffLine = $diffLine.find('td');
         }
@@ -206,6 +279,9 @@
       });
     };
 
+    // Show or hide the loading spinner
+    //
+    // status - Boolean, true to show, false to hide
     MergeRequestTabs.prototype.toggleLoading = function(status) {
       return $('.mr-loading-status .loading').toggle(status);
     };
@@ -232,6 +308,7 @@
 
     MergeRequestTabs.prototype.diffViewType = function() {
       return $('.inline-parallel-buttons a.active').data('view-type');
+    // Returns diff view type
     };
 
     MergeRequestTabs.prototype.expandViewContainer = function() {
@@ -245,6 +322,8 @@
         if ($gutterIcon.is('.fa-angle-double-right')) {
           return $gutterIcon.closest('a').trigger('click', [true]);
         }
+      // Wait until listeners are set
+      // Only when sidebar is expanded
       }, 0);
     };
 
@@ -259,6 +338,9 @@
           return $gutterIcon.closest('a').trigger('click', [true]);
         }
       }, 0);
+    // Expand the issuable sidebar unless the user explicitly collapsed it
+    // Wait until listeners are set
+    // Only when sidebar is collapsed
     };
 
     return MergeRequestTabs;
diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js
index bd35b6f..7bbcdf5 100644
--- a/app/assets/javascripts/merge_request_widget.js
+++ b/app/assets/javascripts/merge_request_widget.js
@@ -3,6 +3,12 @@
 
   this.MergeRequestWidget = (function() {
     function MergeRequestWidget(opts) {
+      // Initialize MergeRequestWidget behavior
+      //
+      //   check_enable           - Boolean, whether to check automerge status
+      //   merge_check_url - String, URL to use to check automerge status
+      //   ci_status_url        - String, URL to use to check CI status
+      //
       this.opts = opts;
       $('#modal_merge_info').modal({
         show: false
@@ -118,6 +124,8 @@
             if (data.coverage) {
               _this.showCICoverage(data.coverage);
             }
+            // The first check should only update the UI, a notification
+            // should only be displayed on status changes
             if (showNotification && !_this.firstCICheck) {
               status = _this.ciLabelForStatus(data.status);
               if (status === "preparing") {
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index e8d51da..bc1a990 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -110,6 +110,7 @@
         },
         update: function(event, ui) {
           var data;
+          // Prevents sorting from container which element has been removed.
           if ($(this).find(ui.item).length > 0) {
             data = $(this).sortable("serialize");
             return Milestone.sortIssues(data);
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index e897ebd..c803117 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -92,6 +92,7 @@
           },
           hidden: function() {
             $selectbox.hide();
+            // display:block overrides the hide-collapse rule
             return $value.css('display', '');
           },
           clicked: function(selected, $el, e) {
diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch-graph.js
index c0fec1f..91132af 100644
--- a/app/assets/javascripts/network/branch-graph.js
+++ b/app/assets/javascripts/network/branch-graph.js
@@ -90,6 +90,7 @@
       results = [];
       while (k < this.mspace) {
         this.colors.push(Raphael.getColor(.8));
+        // Skipping a few colors in the spectrum to get more contrast between colors
         Raphael.getColor();
         Raphael.getColor();
         results.push(k++);
@@ -112,6 +113,7 @@
       for (mm = j = 0, len = ref.length; j < len; mm = ++j) {
         day = ref[mm];
         if (cuday !== day[0] || cumonth !== day[1]) {
+          // Dates
           r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
             font: "12px Monaco, monospace",
             fill: "#BBB"
@@ -119,6 +121,7 @@
           cuday = day[0];
         }
         if (cumonth !== day[1]) {
+          // Months
           r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
             font: "12px Monaco, monospace",
             fill: "#EEE"
@@ -207,6 +210,7 @@
       }
       r = this.r;
       shortrefs = commit.refs;
+      // Truncate if longer than 15 chars
       if (shortrefs.length > 17) {
         shortrefs = shortrefs.substr(0, 15) + "…";
       }
@@ -217,6 +221,7 @@
         title: commit.refs
       });
       textbox = text.getBBox();
+      // Create rectangle based on the size of the textbox
       rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
         fill: "#000",
         "fill-opacity": .5,
@@ -229,6 +234,7 @@
       });
       label = r.set(rect, text);
       label.transform(["t", -rect.getBBox().width - 15, 0]);
+      // Set text to front
       return text.toFront();
     };
 
@@ -283,11 +289,13 @@
         parentY = this.offsetY + this.unitTime * parentCommit.time;
         parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
         parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
+        // Set line color
         if (parentCommit.space <= commit.space) {
           color = this.colors[commit.space];
         } else {
           color = this.colors[parentCommit.space];
         }
+        // Build line shape
         if (parent[1] === commit.space) {
           offset = [0, 5];
           arrow = "l-2,5,4,0,-2,-5,0,5";
@@ -298,13 +306,17 @@
           offset = [-3, 3];
           arrow = "l-5,0,2,4,3,-4,-4,2";
         }
+        // Start point
         route = ["M", x + offset[0], y + offset[1]];
+        // Add arrow if not first parent
         if (i > 0) {
           route.push(arrow);
         }
+        // Circumvent if overlap
         if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
           route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5);
         }
+        // End point
         route.push("L", parentX1, parentY);
         results.push(r.path(route).attr({
           stroke: color,
@@ -325,6 +337,7 @@
           "fill-opacity": .5,
           stroke: "none"
         });
+        // Displayed in the center
         return this.element.scrollTop(y - this.graphHeight / 2);
       }
     };
diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js
index 6a7422a..67c3e64 100644
--- a/app/assets/javascripts/network/network_bundle.js
+++ b/app/assets/javascripts/network/network_bundle.js
@@ -1,4 +1,9 @@
-
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
 /*= require_tree . */
 
 (function() {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index d0d5cad..c6854f7 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,22 +1,10 @@
 
 /*= require autosave */
-
-
 /*= require autosize */
-
-
 /*= require dropzone */
-
-
 /*= require dropzone_input */
-
-
 /*= require gfm_auto_complete */
-
-
 /*= require jquery.atwho */
-
-
 /*= require task_list */
 
 (function() {
@@ -60,26 +48,43 @@
     }
 
     Notes.prototype.addBinding = function() {
+      // add note to UI after creation
       $(document).on("ajax:success", ".js-main-target-form", this.addNote);
       $(document).on("ajax:success", ".js-discussion-note-form", this.addDiscussionNote);
+      // catch note ajax errors
       $(document).on("ajax:error", ".js-main-target-form", this.addNoteError);
+      // change note in UI after update
       $(document).on("ajax:success", "form.edit-note", this.updateNote);
+      // Edit note link
       $(document).on("click", ".js-note-edit", this.showEditForm);
       $(document).on("click", ".note-edit-cancel", this.cancelEdit);
+      // Reopen and close actions for Issue/MR combined with note form submit
       $(document).on("click", ".js-comment-button", this.updateCloseButton);
       $(document).on("keyup input", ".js-note-text", this.updateTargetButtons);
+      // resolve a discussion
       $(document).on('click', '.js-comment-resolve-button', this.resolveDiscussion);
+      // remove a note (in general)
       $(document).on("click", ".js-note-delete", this.removeNote);
+      // delete note attachment
       $(document).on("click", ".js-note-attachment-delete", this.removeAttachment);
+      // reset main target form after submit
       $(document).on("ajax:complete", ".js-main-target-form", this.reenableTargetFormSubmitButton);
       $(document).on("ajax:success", ".js-main-target-form", this.resetMainTargetForm);
+      // reset main target form when clicking discard
       $(document).on("click", ".js-note-discard", this.resetMainTargetForm);
+      // update the file name when an attachment is selected
       $(document).on("change", ".js-note-attachment-input", this.updateFormAttachment);
+      // reply to diff/discussion notes
       $(document).on("click", ".js-discussion-reply-button", this.replyToDiscussionNote);
+      // add diff note
       $(document).on("click", ".js-add-diff-note-button", this.addDiffNote);
+      // hide diff note form
       $(document).on("click", ".js-close-discussion-note-form", this.cancelDiscussionForm);
+      // fetch notes when tab becomes visible
       $(document).on("visibilitychange", this.visibilityChange);
+      // when issue status changes, we need to refresh data
       $(document).on("issuable:change", this.refresh);
+      // when a key is clicked on the notes
       return $(document).on("keydown", ".js-note-text", this.keydownNoteText);
     };
 
@@ -112,6 +117,7 @@
         return;
       }
       $textarea = $(e.target);
+      // Edit previous note when UP arrow is hit
       switch (e.which) {
         case 38:
           if ($textarea.val() !== '') {
@@ -123,6 +129,7 @@
             return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
           }
           break;
+        // Cancel creating diff note or editing any note when ESCAPE is hit
         case 27:
           discussionNoteForm = $textarea.closest('.js-discussion-note-form');
           if (discussionNoteForm.length) {
@@ -247,10 +254,13 @@
         votesBlock = $('.js-awards-block').eq(0);
         gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.name);
         return gl.awardsHandler.scrollToAwards();
+      // render note if it not present in loaded list
+      // or skip if rendered
       } else if (this.isNewNote(note)) {
         this.note_ids.push(note.id);
         $notesList = $('ul.main-notes-list');
         $notesList.append(note.html).syntaxHighlight();
+        // Update datetime format on the recent note
         gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
         this.initTaskList();
         this.refresh();
@@ -291,19 +301,26 @@
       row = form.closest("tr");
       note_html = $(note.html);
       note_html.syntaxHighlight();
+      // is this the first note of discussion?
       discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
       if ((note.original_discussion_id != null) && discussionContainer.length === 0) {
         discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']");
       }
       if (discussionContainer.length === 0) {
+        // insert the note and the reply button after the temp row
         row.after(note.diff_discussion_html);
+        // remove the note (will be added again below)
         row.next().find(".note").remove();
+        // Before that, the container didn't exist
         discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
+        // Add note to 'Changes' page discussions
         discussionContainer.append(note_html);
+        // Init discussion on 'Discussion' page if it is merge request page
         if ($('body').attr('data-page').indexOf('projects:merge_request') === 0) {
           $('ul.main-notes-list').append(note.discussion_html).syntaxHighlight();
         }
       } else {
+        // append new note to all matching discussions
         discussionContainer.append(note_html);
       }
 
@@ -327,11 +344,18 @@
     Notes.prototype.resetMainTargetForm = function(e) {
       var form;
       form = $(".js-main-target-form");
+      // remove validation errors
       form.find(".js-errors").remove();
+      // reset text and preview
       form.find(".js-md-write-button").click();
       form.find(".js-note-text").val("").trigger("input");
       form.find(".js-note-text").data("autosave").reset();
-      return this.updateTargetButtons(e);
+
+      var event = document.createEvent('Event');
+      event.initEvent('autosize:update', true, false);
+      form.find('.js-autosize')[0].dispatchEvent(event);
+
+      this.updateTargetButtons(e);
     };
 
     Notes.prototype.reenableTargetFormSubmitButton = function() {
@@ -349,9 +373,13 @@
 
     Notes.prototype.setupMainTargetNoteForm = function() {
       var form;
+      // find the form
       form = $(".js-new-note-form");
+      // Set a global clone of the form for later cloning
       this.formClone = form.clone();
+      // show the form
       this.setupNoteForm(form);
+      // fix classes
       form.removeClass("js-new-note-form");
       form.addClass("js-main-target-form");
       form.find("#note_line_code").remove();
@@ -416,6 +444,7 @@
       }
 
       this.renderDiscussionNote(note);
+      // cleanup after successfully creating a diff/discussion note
       this.removeDiscussionNoteForm($form);
     };
 
@@ -428,10 +457,12 @@
 
     Notes.prototype.updateNote = function(_xhr, note, _status) {
       var $html, $note_li;
+      // Convert returned HTML to a jQuery object so we can modify it further
       $html = $(note.html);
       gl.utils.localTimeAgo($('.js-timeago', $html));
       $html.syntaxHighlight();
       $html.find('.js-task-list-container').taskList('enable');
+      // Find the note's `li` element by ID and replace it with the updated HTML
       $note_li = $('.note-row-' + note.id);
 
       $note_li.replaceWith($html);
@@ -456,15 +487,20 @@
       note.addClass("is-editting");
       form = note.find(".note-edit-form");
       form.addClass('current-note-edit-form');
+      // Show the attachment delete link
       note.find(".js-note-attachment-delete").show();
       done = function($noteText) {
         var noteTextVal;
+        // Neat little trick to put the cursor at the end
         noteTextVal = $noteText.val();
+        // Store the original note text in a data attribute to retrieve if a user cancels edit.
         form.find('form.edit-note').data('original-note', noteTextVal);
         return $noteText.val('').val(noteTextVal);
       };
       new GLForm(form);
       if ((scrollTo != null) && (myLastNote != null)) {
+        // scroll to the bottom
+        // so the open of the last element doesn't make a jump
         $('html, body').scrollTop($(document).height());
         return $('html, body').animate({
           scrollTop: myLastNote.offset().top - 150
@@ -500,6 +536,7 @@
       form = note.find(".current-note-edit-form");
       note.removeClass("is-editting");
       form.removeClass("current-note-edit-form");
+      // Replace markdown textarea text with original note text.
       return form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'));
     };
 
@@ -515,6 +552,9 @@
       var noteId;
       noteId = $(e.currentTarget).closest(".note").attr("id");
       $(".note[id='" + noteId + "']").each((function(_this) {
+        // A same note appears in the "Discussion" and in the "Changes" tab, we have
+        // to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
+        // where $("#noteId") would return only one.
         return function(i, el) {
           var note, notes;
           note = $(el);
@@ -528,13 +568,17 @@
             }
           }
 
+          // check if this is the last note for this line
           if (notes.find(".note").length === 1) {
+            // "Discussions" tab
             notes.closest(".timeline-entry").remove();
+            // "Changes" tab / commit view
             notes.closest("tr").remove();
           }
           return note.remove();
         };
       })(this));
+      // Decrement the "Discussions" counter only once
       return this.updateNotesCount(-1);
     };
 
@@ -566,10 +610,12 @@
       var form, replyLink;
       form = this.formClone.clone();
       replyLink = $(e.target).closest(".js-discussion-reply-button");
+      // insert the form after the button
       replyLink
         .closest('.discussion-reply-holder')
         .hide()
         .after(form);
+      // show the form
       return this.setupDiscussionNoteForm(replyLink, form);
     };
 
@@ -584,6 +630,7 @@
      */
 
     Notes.prototype.setupDiscussionNoteForm = function(dataHolder, form) {
+      // setup note target
       form.attr('id', "new-discussion-note-form-" + (dataHolder.data("discussionId")));
       form.attr("data-line-code", dataHolder.data("lineCode"));
       form.find("#note_type").val(dataHolder.data("noteType"));
@@ -631,6 +678,7 @@
       addForm = false;
       notesContentSelector = ".notes_content";
       rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"><div class=\"content\"></div></td></tr>";
+      // In parallel view, look inside the correct left/right pane
       if (this.isParallelView()) {
         lineType = $link.data("lineType");
         notesContentSelector += "." + lineType;
@@ -647,6 +695,7 @@
             e.target = replyButton[0];
             $.proxy(this.replyToDiscussionNote, replyButton[0], e).call();
           } else {
+            // In parallel view, the form may not be present in one of the panes
             noteForm = notesContent.find(".js-discussion-note-form");
             if (noteForm.length === 0) {
               addForm = true;
@@ -654,6 +703,7 @@
           }
         }
       } else {
+        // add a notes row and insert the form
         row.after(rowCssToAdd);
         nextRow = row.next();
         notesContent = nextRow.find(notesContentSelector);
@@ -662,6 +712,7 @@
       if (addForm) {
         newForm = this.formClone.clone();
         newForm.appendTo(notesContent);
+        // show the form
         return this.setupDiscussionNoteForm($link, newForm);
       }
     };
@@ -680,12 +731,15 @@
       glForm = form.data('gl-form');
       glForm.destroy();
       form.find(".js-note-text").data("autosave").reset();
+      // show the reply button (will only work for replies)
       form
         .prev('.discussion-reply-holder')
         .show();
       if (row.is(".js-temp-notes-holder")) {
+        // remove temporary row for diff lines
         return row.remove();
       } else {
+        // only remove the form
         return form.remove();
       }
     };
@@ -707,6 +761,7 @@
     Notes.prototype.updateFormAttachment = function() {
       var filename, form;
       form = $(this).closest("form");
+      // get only the basename
       filename = $(this).val().replace(/^.*[\\\/]/, "");
       return form.find(".js-attachment-filename").text(filename);
     };
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 5fd7579..5200487 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -1,9 +1,15 @@
+// MarkdownPreview
+//
+// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
+// and showing a warning when more than `x` users are referenced.
+//
 (function() {
   var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector;
 
   this.MarkdownPreview = (function() {
     function MarkdownPreview() {}
 
+    // Minimum number of users referenced before triggering a warning
     MarkdownPreview.prototype.referenceThreshold = 10;
 
     MarkdownPreview.prototype.ajaxCache = {};
@@ -101,8 +107,10 @@
       return;
     }
     lastTextareaPreviewed = $form.find('textarea.markdown-area');
+    // toggle tabs
     $form.find(writeButtonSelector).parent().removeClass('active');
     $form.find(previewButtonSelector).parent().addClass('active');
+    // toggle content
     $form.find('.md-write-holder').hide();
     $form.find('.md-preview-holder').show();
     return markdownPreview.showPreview($form);
@@ -113,8 +121,10 @@
       return;
     }
     lastTextareaPreviewed = null;
+    // toggle tabs
     $form.find(writeButtonSelector).parent().addClass('active');
     $form.find(previewButtonSelector).parent().removeClass('active');
+    // toggle content
     $form.find('.md-write-holder').show();
     $form.find('textarea.markdown-area').focus();
     return $form.find('.md-preview-holder').hide();
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index a3eea31..30cd6f6 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -5,6 +5,7 @@
   GitLabCrop = (function() {
     var FILENAMEREGEX;
 
+    // Matches everything but the file name
     FILENAMEREGEX = /^.*[\\\/]/;
 
     function GitLabCrop(input, opts) {
@@ -17,11 +18,18 @@
       this.onModalShow = bind(this.onModalShow, this);
       this.onPickImageClick = bind(this.onPickImageClick, this);
       this.fileInput = $(input);
+      // We should rename to avoid spec to fail
+      // Form will submit the proper input filed with a file using FormData
       this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger");
+      // Set defaults
       this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickI [...]
+      // Required params
+      // Ensure needed elements are jquery objects
+      // If selector is provided we will convert them to a jQuery Object
       this.filename = this.getElement(this.filename);
       this.previewImage = this.getElement(this.previewImage);
       this.pickImageEl = this.getElement(this.pickImageEl);
+      // Modal elements usually are outside the @form element
       this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop;
       this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn;
       this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg;
@@ -93,8 +101,8 @@
       return this.modalCropImg.attr('src', '').cropper('destroy');
     };
 
-    GitLabCrop.prototype.onUploadImageBtnClick = function(e) {
-      e.preventDefault();
+    GitLabCrop.prototype.onUploadImageBtnClick = function(e) { // Remove attached image
+      e.preventDefault(); // Destroy cropper instance
       this.setBlob();
       this.setPreview();
       this.modalCrop.modal('hide');
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index ed1d87a..60f9fba 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -11,9 +11,11 @@
       this.form = (ref = opts.form) != null ? ref : $('.edit-user');
       $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() {
         return $(this).parents('form').submit();
+      // Automatically submit the Preferences form when any of its radio buttons change
       });
       $('#user_notification_email').on('change', function() {
         return $(this).parents('form').submit();
+      // Automatically submit email form when it changes
       });
       $('.update-username').on('ajax:before', function() {
         $('.loading-username').show();
@@ -76,6 +78,7 @@
         },
         complete: function() {
           window.scrollTo(0, 0);
+          // Enable submit button after requests ends
           return self.form.find(':input[disabled]').enable();
         }
       });
@@ -93,6 +96,7 @@
       if (comment && comment.length > 1 && $title.val() === '') {
         return $title.val(comment[1]).change();
       }
+    // Extract the SSH Key title from its comment
     });
     if (gl.utils.getPagePath() === 'profiles') {
       return new Profile();
diff --git a/app/assets/javascripts/profile/profile_bundle.js b/app/assets/javascripts/profile/profile_bundle.js
index b95faad..d6e4d9f 100644
--- a/app/assets/javascripts/profile/profile_bundle.js
+++ b/app/assets/javascripts/profile/profile_bundle.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index b97f6d2..a6c0152 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -11,25 +11,27 @@
         url = $("#project_clone").val();
         $('#project_clone').val(url);
         return $('.clone').text(url);
+      // Git protocol switcher
+      // Remove the active class for all buttons (ssh, http, kerberos if shown)
+      // Add the active class for the clicked button
+      // Update the input field
+      // Update the command line instructions
       });
+      // Ref switcher
       this.initRefSwitcher();
       $('.project-refs-select').on('change', function() {
         return $(this).parents('form').submit();
       });
       $('.hide-no-ssh-message').on('click', function(e) {
-        var path;
-        path = '/';
         $.cookie('hide_no_ssh_message', 'false', {
-          path: path
+          path: gon.relative_url_root || '/'
         });
         $(this).parents('.no-ssh-key-message').remove();
         return e.preventDefault();
       });
       $('.hide-no-password-message').on('click', function(e) {
-        var path;
-        path = '/';
         $.cookie('hide_no_password_message', 'false', {
-          path: path
+          path: gon.relative_url_root || '/'
         });
         $(this).parents('.no-password-message').remove();
         return e.preventDefault();
@@ -65,7 +67,8 @@
               url: $dropdown.data('refs-url'),
               data: {
                 ref: $dropdown.data('ref')
-              }
+              },
+              dataType: "json"
             }).done(function(refs) {
               return callback(refs);
             });
@@ -73,7 +76,7 @@
           selectable: true,
           filterable: true,
           filterByText: true,
-          fieldName: 'ref',
+          fieldName: $dropdown.data('field-name'),
           renderRow: function(ref) {
             var link;
             if (ref.header != null) {
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 4925f05..5bf900f 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -13,8 +13,11 @@
       this.selectRowUp = bind(this.selectRowUp, this);
       this.filePaths = {};
       this.inputElement = this.element.find(".file-finder-input");
+      // init event
       this.initEvent();
+      // focus text input box
       this.inputElement.focus();
+      // load file list
       this.load(this.options.url);
     }
 
@@ -42,6 +45,7 @@
           }
         }
       });
+    // init event
     };
 
     ProjectFindFile.prototype.findFile = function() {
@@ -49,8 +53,10 @@
       searchText = this.inputElement.val();
       result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
       return this.renderList(result, searchText);
+    // find file
     };
 
+    // files pathes load
     ProjectFindFile.prototype.load = function(url) {
       return $.ajax({
         url: url,
@@ -67,6 +73,7 @@
       });
     };
 
+    // render result
     ProjectFindFile.prototype.renderList = function(filePaths, searchText) {
       var blobItemUrl, filePath, html, i, j, len, matches, results;
       this.element.find(".tree-table > tbody").empty();
@@ -86,6 +93,7 @@
       return results;
     };
 
+    // highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
     highlighter = function(element, text, matches) {
       var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
       lastIndex = 0;
@@ -110,6 +118,7 @@
       return element.append(document.createTextNode(text.substring(lastIndex)));
     };
 
+    // make tbody row html
     ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
       var $tr;
       $tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>");
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index 798f15e..a787b11 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -4,6 +4,8 @@
   this.ProjectNew = (function() {
     function ProjectNew() {
       this.toggleSettings = bind(this.toggleSettings, this);
+      this.$selects = $('.features select');
+
       $('.project-edit-container').on('ajax:before', (function(_this) {
         return function() {
           $('.project-edit-container').hide();
@@ -15,18 +17,24 @@
     }
 
     ProjectNew.prototype.toggleSettings = function() {
-      this._showOrHide('#project_builds_enabled', '.builds-feature');
-      return this._showOrHide('#project_merge_requests_enabled', '.merge-requests-feature');
+      var self = this;
+
+      this.$selects.each(function () {
+        var $select = $(this),
+            className = $select.data('field').replace(/_/g, '-')
+              .replace('access-level', 'feature');
+        self._showOrHide($select, '.' + className);
+      });
     };
 
     ProjectNew.prototype.toggleSettingsOnclick = function() {
-      return $('#project_builds_enabled, #project_merge_requests_enabled').on('click', this.toggleSettings);
+      this.$selects.on('change', this.toggleSettings);
     };
 
     ProjectNew.prototype._showOrHide = function(checkElement, container) {
-      var $container;
-      $container = $(container);
-      if ($(checkElement).prop('checked')) {
+      var $container = $(container);
+
+      if ($(checkElement).val() !== '0') {
         return $container.show();
       } else {
         return $container.hide();
diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js
index 8ca4c42..c8cfc9a 100644
--- a/app/assets/javascripts/project_show.js
+++ b/app/assets/javascripts/project_show.js
@@ -7,3 +7,5 @@
   })();
 
 }).call(this);
+
+// I kept class for future
diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js
index 4f415b0..04fb495 100644
--- a/app/assets/javascripts/projects_list.js
+++ b/app/assets/javascripts/projects_list.js
@@ -33,6 +33,7 @@
           $('.projects-list-holder').replaceWith(data.html);
           return history.replaceState({
             page: project_filter_url
+          // Change url so if user reload a page - search results are saved
           }, document.title, project_filter_url);
         },
         dataType: "json"
diff --git a/app/assets/javascripts/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branch_dropdown.js.es6
index 6738dc8..983322c 100644
--- a/app/assets/javascripts/protected_branch_dropdown.js.es6
+++ b/app/assets/javascripts/protected_branch_dropdown.js.es6
@@ -45,6 +45,7 @@ class ProtectedBranchDropdown {
   }
 
   onClickCreateWildcard() {
+    // Refresh the dropdown's data, which ends up calling `getProtectedBranches`
     this.$dropdown.data('glDropdown').remote.execute();
     this.$dropdown.data('glDropdown').selectRowAtIndex(0);
   }
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index dc4d511..e3d5f41 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -30,7 +30,7 @@
         }
         if (!triggered) {
           return $.cookie("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'), {
-            path: '/'
+            path: gon.relative_url_root || '/'
           });
         }
       });
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 227e8c6..8abb09c 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -24,6 +24,7 @@
       this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this);
       this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this);
       this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || '';
+      // Dropdown Element
       this.dropdown = this.wrap.find('.dropdown');
       this.dropdownContent = this.dropdown.find('.dropdown-content');
       this.locationBadgeEl = this.getElement('.location-badge');
@@ -35,6 +36,7 @@
       this.repositoryInputEl = this.getElement('#repository_ref');
       this.clearInput = this.getElement('.js-clear-input');
       this.saveOriginalState();
+      // Only when user is logged in
       if (gon.current_user_id) {
         this.createAutocomplete();
       }
@@ -43,6 +45,7 @@
       this.bindEvents();
     }
 
+    // Finds an element inside wrapper element
     SearchAutocomplete.prototype.getElement = function(selector) {
       return this.wrap.find(selector);
     };
@@ -82,6 +85,7 @@
         }
         return;
       }
+      // Prevent multiple ajax calls
       if (this.loadingSuggestions) {
         return;
       }
@@ -92,14 +96,17 @@
         term: term
       }, function(response) {
         var data, firstCategory, i, lastCategory, len, suggestion;
+        // Hide dropdown menu if no suggestions returns
         if (!response.length) {
           _this.disableAutocomplete();
           return;
         }
         data = [];
+        // List results
         firstCategory = true;
         for (i = 0, len = response.length; i < len; i++) {
           suggestion = response[i];
+          // Add group header before list each group
           if (lastCategory !== suggestion.category) {
             if (!firstCategory) {
               data.push('separator');
@@ -119,6 +126,7 @@
             url: suggestion.url
           });
         }
+        // Add option to proceed with the search
         if (data.length) {
           data.push('separator');
           data.push({
@@ -169,11 +177,13 @@
 
     SearchAutocomplete.prototype.serializeState = function() {
       return {
+        // Search Criteria
         search_project_id: this.projectInputEl.val(),
         group_id: this.groupInputEl.val(),
         search_code: this.searchCodeInputEl.val(),
         repository_ref: this.repositoryInputEl.val(),
         scope: this.scopeInputEl.val(),
+        // Location badge
         _location: this.locationBadgeEl.text()
       };
     };
@@ -194,6 +204,7 @@
 
     SearchAutocomplete.prototype.enableAutocomplete = function() {
       var _this;
+      // No need to enable anything if user is not logged in
       if (!gon.current_user_id) {
         return;
       }
@@ -206,18 +217,22 @@
     };
 
     SearchAutocomplete.prototype.onSearchInputKeyDown = function() {
+      // Saves last length of the entered text
       return this.saveTextLength();
     };
 
     SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) {
       switch (e.keyCode) {
         case KEYCODE.BACKSPACE:
+          // when trying to remove the location badge
           if (this.lastTextLength === 0 && this.badgePresent()) {
             this.removeLocationBadge();
           }
+          // When removing the last character and no badge is present
           if (this.lastTextLength === 1) {
             this.disableAutocomplete();
           }
+          // When removing any character from existin value
           if (this.lastTextLength > 1) {
             this.enableAutocomplete();
           }
@@ -232,9 +247,12 @@
         case KEYCODE.DOWN:
           return;
         default:
+          // Handle the case when deleting the input value other than backspace
+          // e.g. Pressing ctrl + backspace or ctrl + x
           if (this.searchInput.val() === '') {
             this.disableAutocomplete();
           } else {
+            // We should display the menu only when input is not empty
             if (e.keyCode !== KEYCODE.ENTER) {
               this.enableAutocomplete();
             }
@@ -243,7 +261,9 @@
       this.wrap.toggleClass('has-value', !!e.target.value);
     };
 
+    // Avoid falsy value to be returned
     SearchAutocomplete.prototype.onSearchInputClick = function(e) {
+      // Prevents closing the dropdown menu
       return e.stopImmediatePropagation();
     };
 
@@ -267,6 +287,7 @@
     SearchAutocomplete.prototype.onSearchInputBlur = function(e) {
       this.isFocused = false;
       this.wrap.removeClass('search-active');
+      // If input is blank then restore state
       if (this.searchInput.val() === '') {
         return this.restoreOriginalState();
       }
@@ -311,6 +332,7 @@
       results = [];
       for (i = 0, len = inputs.length; i < len; i++) {
         input = inputs[i];
+        // _location isnt a input
         if (input === '_location') {
           break;
         }
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index 3b28332..3aa8536 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -86,6 +86,7 @@
     var defaultStopCallback;
     defaultStopCallback = Mousetrap.stopCallback;
     return function(e, element, combo) {
+      // allowed shortcuts if textarea, input, contenteditable are focused
       if (['ctrl+shift+p', 'command+shift+p'].indexOf(combo) !== -1) {
         return false;
       } else {
diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js
index 6c78914..92ce319 100644
--- a/app/assets/javascripts/shortcuts_find_file.js
+++ b/app/assets/javascripts/shortcuts_find_file.js
@@ -14,8 +14,10 @@
       ShortcutsFindFile.__super__.constructor.call(this);
       _oldStopCallback = Mousetrap.stopCallback;
       Mousetrap.stopCallback = (function(_this) {
+        // override to fire shortcuts action when focus in textbox
         return function(event, element, combo) {
           if (element === _this.projectFindFile.inputElement[0] && (combo === 'up' || combo === 'down' || combo === 'esc' || combo === 'enter')) {
+            // when press up/down key in textbox, cusor prevent to move to home/end
             event.preventDefault();
             return false;
           }
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 3f3a8a9..235bf4f 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -1,7 +1,5 @@
 
 /*= require mousetrap */
-
-
 /*= require shortcuts_navigation */
 
 (function() {
@@ -43,16 +41,20 @@
         if (selected.trim() === "") {
           return;
         }
+        // Put a '>' character before each non-empty line in the selection
         quote = _.map(selected.split("\n"), function(val) {
           if (val.trim() !== '') {
             return "> " + val + "\n";
           }
         });
+        // If replyField already has some content, add a newline before our quote
         separator = replyField.val().trim() !== "" && "\n" || '';
         replyField.val(function(_, current) {
           return current + separator + quote.join('') + "\n";
         });
+        // Trigger autosave for the added text
         replyField.trigger('input');
+        // Focus the input field
         return replyField.focus();
       }
     };
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js
index 469e254..b041594 100644
--- a/app/assets/javascripts/shortcuts_navigation.js
+++ b/app/assets/javascripts/shortcuts_navigation.js
@@ -34,6 +34,9 @@
       Mousetrap.bind('g i', function() {
         return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
       });
+      Mousetrap.bind('g l', function() {
+        ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards');
+      });
       Mousetrap.bind('g m', function() {
         return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
       });
diff --git a/app/assets/javascripts/sidebar.js b/app/assets/javascripts/sidebar.js
deleted file mode 100644
index bd0c119..0000000
--- a/app/assets/javascripts/sidebar.js
+++ /dev/null
@@ -1,41 +0,0 @@
-(function() {
-  var collapsed, expanded, toggleSidebar;
-
-  collapsed = 'page-sidebar-collapsed';
-
-  expanded = 'page-sidebar-expanded';
-
-  toggleSidebar = function() {
-    $('.page-with-sidebar').toggleClass(collapsed + " " + expanded);
-    $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded");
-    if ($.cookie('pin_nav') === 'true') {
-      $('.navbar-fixed-top').toggleClass('header-pinned-nav');
-      $('.page-with-sidebar').toggleClass('page-sidebar-pinned');
-    }
-    return setTimeout((function() {
-      var niceScrollBars;
-      niceScrollBars = $('.nav-sidebar').niceScroll();
-      return niceScrollBars.updateScrollBar();
-    }), 300);
-  };
-
-  $(document).off('click', 'body').on('click', 'body', function(e) {
-    var $nav, $target, $toggle, pageExpanded;
-    if ($.cookie('pin_nav') !== 'true') {
-      $target = $(e.target);
-      $nav = $target.closest('.sidebar-wrapper');
-      pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded');
-      $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle');
-      if ($nav.length === 0 && pageExpanded && $toggle.length === 0) {
-        $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
-        return $('.navbar-fixed-top').toggleClass('header-collapsed header-expanded');
-      }
-    }
-  });
-
-  $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', function(e) {
-    e.preventDefault();
-    return toggleSidebar();
-  });
-
-}).call(this);
diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6
new file mode 100644
index 0000000..755fac8
--- /dev/null
+++ b/app/assets/javascripts/sidebar.js.es6
@@ -0,0 +1,93 @@
+((global) => {
+  let singleton;
+
+  const pinnedStateCookie = 'pin_nav';
+  const sidebarBreakpoint = 1024;
+
+  const pageSelector = '.page-with-sidebar';
+  const navbarSelector = '.navbar-fixed-top';
+  const sidebarWrapperSelector = '.sidebar-wrapper';
+  const sidebarContentSelector = '.nav-sidebar';
+
+  const pinnedToggleSelector = '.js-nav-pin';
+  const sidebarToggleSelector = '.toggle-nav-collapse, .side-nav-toggle';
+
+  const pinnedPageClass = 'page-sidebar-pinned';
+  const expandedPageClass = 'page-sidebar-expanded';
+
+  const pinnedNavbarClass = 'header-sidebar-pinned';
+  const expandedNavbarClass = 'header-sidebar-expanded';
+
+  class Sidebar {
+    constructor() {
+      if (!singleton) {
+        singleton = this;
+        singleton.init();
+      }
+      return singleton;
+    }
+
+    init() {
+      this.isPinned = $.cookie(pinnedStateCookie) === 'true';
+      this.isExpanded = (
+        window.innerWidth >= sidebarBreakpoint &&
+        $(pageSelector).hasClass(expandedPageClass)
+      );
+      $(document)
+        .on('click', sidebarToggleSelector, () => this.toggleSidebar())
+        .on('click', pinnedToggleSelector, () => this.togglePinnedState())
+        .on('click', 'html, body', (e) => this.handleClickEvent(e))
+        .on('page:change', () => this.renderState());
+      this.renderState();
+    }
+
+    handleClickEvent(e) {
+      if (this.isExpanded && (!this.isPinned || window.innerWidth < sidebarBreakpoint)) {
+        const $target = $(e.target);
+        const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0;
+        const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0;
+        if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) {
+          this.toggleSidebar();
+        }
+      }
+    }
+
+    toggleSidebar() {
+      this.isExpanded = !this.isExpanded;
+      this.renderState();
+    }
+
+    togglePinnedState() {
+      this.isPinned = !this.isPinned;
+      if (!this.isPinned) {
+        this.isExpanded = false;
+      }
+      $.cookie(pinnedStateCookie, this.isPinned ? 'true' : 'false', {
+        path: gon.relative_url_root || '/',
+        expires: 3650
+      });
+      this.renderState();
+    }
+
+    renderState() {
+      $(pageSelector)
+        .toggleClass(pinnedPageClass, this.isPinned && this.isExpanded)
+        .toggleClass(expandedPageClass, this.isExpanded);
+      $(navbarSelector)
+        .toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded)
+        .toggleClass(expandedNavbarClass, this.isExpanded);
+
+      const $pinnedToggle = $(pinnedToggleSelector);
+      const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation';
+      const tooltipState = $pinnedToggle.attr('aria-describedby') && this.isExpanded ? 'show' : 'hide';
+      $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState);
+
+      if (this.isExpanded) {
+        setTimeout(() => $(sidebarContentSelector).niceScroll().updateScrollBar(), 200);
+      }
+    }
+  }
+
+  global.Sidebar = Sidebar;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/snippets_list.js.es6 b/app/assets/javascripts/snippets_list.js.es6
new file mode 100644
index 0000000..6f0996c
--- /dev/null
+++ b/app/assets/javascripts/snippets_list.js.es6
@@ -0,0 +1,11 @@
+(global => {
+  global.gl = global.gl || {};
+
+  gl.SnippetsList = function() {
+    var $holder = $('.snippets-list-holder');
+
+    $holder.find('.pagination').on('ajax:success', (e, data) => {
+      $holder.replaceWith(data.html);
+    });
+  }
+})(window);
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index dba6263..2ae7bf5 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -1,9 +1,20 @@
+// Syntax Highlighter
+//
+// Applies a syntax highlighting color scheme CSS class to any element with the
+// `js-syntax-highlight` class
+//
+// ### Example Markup
+//
+//   <div class="js-syntax-highlight"></div>
+//
 (function() {
   $.fn.syntaxHighlight = function() {
     var $children;
     if ($(this).hasClass('js-syntax-highlight')) {
+      // Given the element itself, apply highlighting
       return $(this).addClass(gon.user_color_scheme);
     } else {
+      // Given a parent element, recurse to any of its applicable children
       $children = $(this).find('.js-syntax-highlight');
       if ($children.length) {
         return $children.syntaxHighlight();
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index 6e677fa..9342164 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -13,6 +13,7 @@
       this.perPage = this.el.data('perPage');
       this.clearListeners();
       this.initBtnListeners();
+      this.initFilters();
     }
 
     Todos.prototype.clearListeners = function() {
@@ -27,6 +28,31 @@
       return $('.todo').on('click', this.goToTodoUrl);
     };
 
+    Todos.prototype.initFilters = function() {
+      new UsersSelect();
+      this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']);
+      this.initFilterDropdown($('.js-type-search'), 'type');
+      this.initFilterDropdown($('.js-action-search'), 'action_id');
+
+      $('form.filter-form').on('submit', function (event) {
+        event.preventDefault();
+        Turbolinks.visit(this.action + '&' + $(this).serialize());
+      });
+    };
+
+    Todos.prototype.initFilterDropdown = function($dropdown, fieldName, searchFields) {
+      $dropdown.glDropdown({
+        selectable: true,
+        filterable: searchFields ? true : false,
+        fieldName: fieldName,
+        search: { fields: searchFields },
+        data: $dropdown.data('data'),
+        clicked: function() {
+          return $dropdown.closest('form.filter-form').submit();
+        }
+      })
+    };
+
     Todos.prototype.doneClicked = function(e) {
       var $this;
       e.preventDefault();
@@ -66,7 +92,7 @@
         success: (function(_this) {
           return function(data) {
             $this.remove();
-            $('.js-todos-list').remove();
+            $('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>');
             return _this.updateBadges(data);
           };
         })(this)
@@ -103,16 +129,21 @@
       var currPage, currPages, newPages, pageParams, url;
       currPages = this.getTotalPages();
       currPage = this.getCurrentPage();
+      // Refresh if no remaining Todos
       if (!total) {
         location.reload();
         return;
       }
+      // Do nothing if no pagination
       if (!currPages) {
         return;
       }
       newPages = Math.ceil(total / this.getTodosPerPage());
+      // Includes query strings
       url = location.href;
+      // If new total of pages is different than we have now
       if (newPages !== currPages) {
+        // Redirect to previous page if there's one available
         if (currPages > 1 && currPage === currPages) {
           pageParams = {
             page: currPages - 1
@@ -129,6 +160,7 @@
       if (!todoLink) {
         return;
       }
+      // Allow Meta-Click or Mouse3-click to open in a new tab
       if (e.metaKey || e.which === 2) {
         e.preventDefault();
         return window.open(todoLink, '_blank');
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 78e159a..9b7be17 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -2,6 +2,8 @@
   this.TreeView = (function() {
     function TreeView() {
       this.initKeyNav();
+      // Code browser tree slider
+      // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
       $(".tree-content-holder .tree-item").on('click', function(e) {
         var $clickedEl, path;
         $clickedEl = $(e.target);
@@ -15,6 +17,7 @@
           }
         }
       });
+      // Show the "Loading commit data" for only the first element
       $('span.log_loading:first').removeClass('hide');
     }
 
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
index 9ba847f..ce2930c 100644
--- a/app/assets/javascripts/u2f/authenticate.js
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -1,3 +1,7 @@
+// Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
+//
+// State Flow #1: setup -> in_progress -> authenticated -> POST to server
+// State Flow #2: setup -> in_progress -> error -> setup
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
@@ -15,6 +19,17 @@
       this.appId = u2fParams.app_id;
       this.challenge = u2fParams.challenge;
       this.signRequests = u2fParams.sign_requests.map(function(request) {
+        // The U2F Javascript API v1.1 requires a single challenge, with
+        // _no challenges per-request_. The U2F Javascript API v1.0 requires a
+        // challenge per-request, which is done by copying the single challenge
+        // into every request.
+        //
+        // In either case, we don't need the per-request challenges that the server
+        // has generated, so we can remove them.
+        //
+        // Note: The server library fixes this behaviour in (unreleased) version 1.0.0.
+        // This can be removed once we upgrade.
+        // https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4
         return _(request).omit('challenge');
       });
     }
@@ -41,6 +56,7 @@
       })(this), 10);
     };
 
+    // Rendering #
     U2FAuthenticate.prototype.templates = {
       "notSupported": "#js-authenticate-u2f-not-supported",
       "setup": '#js-authenticate-u2f-setup',
@@ -75,6 +91,8 @@
 
     U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) {
       this.renderTemplate('authenticated');
+      // Prefer to do this instead of interpolating using Underscore templates
+      // because of JSON escaping issues.
       return this.container.find("#js-device-response").val(deviceResponse);
     };
 
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
index c87e084..926912f 100644
--- a/app/assets/javascripts/u2f/register.js
+++ b/app/assets/javascripts/u2f/register.js
@@ -1,3 +1,7 @@
+// Register U2F (universal 2nd factor) devices for users to authenticate with.
+//
+// State Flow #1: setup -> in_progress -> registered -> POST to server
+// State Flow #2: setup -> in_progress -> error -> setup
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
@@ -39,6 +43,7 @@
       })(this), 10);
     };
 
+    // Rendering #
     U2FRegister.prototype.templates = {
       "notSupported": "#js-register-u2f-not-supported",
       "setup": '#js-register-u2f-setup',
@@ -73,6 +78,8 @@
 
     U2FRegister.prototype.renderRegistered = function(deviceResponse) {
       this.renderTemplate('registered');
+      // Prefer to do this instead of interpolating using Underscore templates
+      // because of JSON escaping issues.
       return this.container.find("#js-device-response").val(deviceResponse);
     };
 
diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js
deleted file mode 100644
index b46390a..0000000
--- a/app/assets/javascripts/user.js
+++ /dev/null
@@ -1,31 +0,0 @@
-(function() {
-  this.User = (function() {
-    function User(opts) {
-      this.opts = opts;
-      $('.profile-groups-avatars').tooltip({
-        "placement": "top"
-      });
-      this.initTabs();
-      $('.hide-project-limit-message').on('click', function(e) {
-        var path;
-        path = '/';
-        $.cookie('hide_project_limit_message', 'false', {
-          path: path
-        });
-        $(this).parents('.project-limit-message').remove();
-        return e.preventDefault();
-      });
-    }
-
-    User.prototype.initTabs = function() {
-      return new UserTabs({
-        parentEl: '.user-profile',
-        action: this.opts.action
-      });
-    };
-
-    return User;
-
-  })();
-
-}).call(this);
diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6
new file mode 100644
index 0000000..6889d3a
--- /dev/null
+++ b/app/assets/javascripts/user.js.es6
@@ -0,0 +1,34 @@
+(global => {
+  global.User = class {
+    constructor(opts) {
+      this.opts = opts;
+      this.placeProfileAvatarsToTop();
+      this.initTabs();
+      this.hideProjectLimitMessage();
+    }
+
+    placeProfileAvatarsToTop() {
+      $('.profile-groups-avatars').tooltip({
+        placement: 'top'
+      });
+    }
+
+    initTabs() {
+      return new UserTabs({
+        parentEl: '.user-profile',
+        action: this.opts.action
+      });
+    }
+
+    hideProjectLimitMessage() {
+      $('.hide-project-limit-message').on('click', e => {
+        e.preventDefault();
+        const path = gon.relative_url_root || '/';
+        $.cookie('hide_project_limit_message', 'false', {
+          path: path
+        });
+        $(this).parents('.project-limit-message').remove();
+      });
+    }
+  }
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js
index e5e7570..8a65778 100644
--- a/app/assets/javascripts/user_tabs.js
+++ b/app/assets/javascripts/user_tabs.js
@@ -1,3 +1,61 @@
+// UserTabs
+//
+// Handles persisting and restoring the current tab selection and lazily-loading
+// content on the Users#show page.
+//
+// ### Example Markup
+//
+//   <ul class="nav-links">
+//     <li class="activity-tab active">
+//       <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
+//         Activity
+//       </a>
+//     </li>
+//     <li class="groups-tab">
+//       <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
+//         Groups
+//       </a>
+//     </li>
+//     <li class="contributed-tab">
+//       <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
+//         Contributed projects
+//       </a>
+//     </li>
+//     <li class="projects-tab">
+//       <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
+//         Personal projects
+//       </a>
+//     </li>
+//    <li class="snippets-tab">
+//       <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
+//       </a>
+//     </li>
+//   </ul>
+//
+//   <div class="tab-content">
+//     <div class="tab-pane" id="activity">
+//       Activity Content
+//     </div>
+//     <div class="tab-pane" id="groups">
+//       Groups Content
+//     </div>
+//     <div class="tab-pane" id="contributed">
+//       Contributed projects content
+//     </div>
+//     <div class="tab-pane" id="projects">
+//       Projects content
+//     </div>
+//     <div class="tab-pane" id="snippets">
+//       Snippets content
+//     </div>
+//   </div>
+//
+//   <div class="loading-status">
+//     <div class="loading">
+//       Loading Animation
+//     </div>
+//   </div>
+//
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
@@ -6,18 +64,23 @@
       this.tabShown = bind(this.tabShown, this);
       var i, item, len, ref, ref1, ref2, ref3;
       this.action = (ref = opts.action) != null ? ref : 'activity', this.defaultAction = (ref1 = opts.defaultAction) != null ? ref1 : 'activity', this.parentEl = (ref2 = opts.parentEl) != null ? ref2 : $(document);
+      // Make jQuery object if selector is provided
       if (typeof this.parentEl === 'string') {
         this.parentEl = $(this.parentEl);
       }
+      // Store the `location` object, allowing for easier stubbing in tests
       this._location = location;
+      // Set tab states
       this.loaded = {};
       ref3 = this.parentEl.find('.nav-links a');
       for (i = 0, len = ref3.length; i < len; i++) {
         item = ref3[i];
         this.loaded[$(item).attr('data-action')] = false;
       }
+      // Actions
       this.actions = Object.keys(this.loaded);
       this.bindEvents();
+      // Set active tab
       if (this.action === 'show') {
         this.action = this.defaultAction;
       }
@@ -25,6 +88,7 @@
     }
 
     UserTabs.prototype.bindEvents = function() {
+      // Toggle event listeners
       return this.parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]').on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', this.tabShown);
     };
 
@@ -74,6 +138,7 @@
             tabSelector = 'div#' + action;
             _this.parentEl.find(tabSelector).html(data.html);
             _this.loaded[action] = true;
+            // Fix tooltips
             return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
           };
         })(this)
@@ -97,13 +162,17 @@
 
     UserTabs.prototype.setCurrentAction = function(action) {
       var new_state, regExp;
+      // Remove possible actions from URL
       regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$');
       new_state = this._location.pathname;
+      // remove trailing slashes
       new_state = new_state.replace(/\/+$/, "");
       new_state = new_state.replace(regExp, '');
+      // Append the new action if we're on a tab other than 'activity'
       if (action !== this.defaultAction) {
         new_state += "/" + action;
       }
+      // Ensure parameters and hash come along for the ride
       new_state += this._location.search + this._location.hash;
       history.replaceState({
         turbolinks: true,
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
index 8b3dbf5..3bd4c3c 100644
--- a/app/assets/javascripts/users/calendar.js
+++ b/app/assets/javascripts/users/calendar.js
@@ -3,7 +3,6 @@
 
   this.Calendar = (function() {
     function Calendar(timestamps, calendar_activities_path) {
-      var group, i;
       this.calendar_activities_path = calendar_activities_path;
       this.clickDay = bind(this.clickDay, this);
       this.currentSelectedDate = '';
@@ -12,29 +11,46 @@
       this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
       this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
       this.months = [];
+      // Loop through the timestamps to create a group of objects
+      // The group of objects will be grouped based on the day of the week they are
       this.timestampsTmp = [];
-      i = 0;
-      group = 0;
-      _.each(timestamps, (function(_this) {
-        return function(count, date) {
-          var day, innerArray, newDate;
-          newDate = new Date(parseInt(date) * 1000);
-          day = newDate.getDay();
-          if ((day === 0 && i !== 0) || i === 0) {
-            _this.timestampsTmp.push([]);
-            group++;
-          }
-          innerArray = _this.timestampsTmp[group - 1];
-          innerArray.push({
-            count: count,
-            date: newDate,
-            day: day
-          });
-          return i++;
-        };
-      })(this));
+      var group = 0;
+
+      var today = new Date()
+      today.setHours(0, 0, 0, 0, 0);
+
+      var oneYearAgo = new Date(today);
+      oneYearAgo.setFullYear(today.getFullYear() - 1);
+
+      var days = gl.utils.getDayDifference(oneYearAgo, today);
+
+      for(var i = 0; i <= days; i++) {
+        var date = new Date(oneYearAgo);
+        date.setDate(date.getDate() + i);
+
+        var day = date.getDay();
+        var count = timestamps[dateFormat(date, 'yyyy-mm-dd')];
+
+        // Create a new group array if this is the first day of the week
+        // or if is first object
+        if ((day === 0 && i !== 0) || i === 0) {
+          this.timestampsTmp.push([]);
+          group++;
+        }
+
+        var innerArray = this.timestampsTmp[group - 1];
+        // Push to the inner array the values that will be used to render map
+        innerArray.push({
+          count: count || 0,
+          date: date,
+          day: day
+        });
+      }
+
+      // Init color functions
       this.colorKey = this.initColorKey();
       this.color = this.initColor();
+      // Init the svg element
       this.renderSvg(group);
       this.renderDays();
       this.renderMonths();
@@ -43,8 +59,22 @@
       this.initTooltips();
     }
 
+    // Add extra padding for the last month label if it is also the last column
+    Calendar.prototype.getExtraWidthPadding = function(group) {
+      var extraWidthPadding = 0;
+      var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
+      var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
+
+      if (lastColMonth != secondLastColMonth) {
+        extraWidthPadding = 3;
+      }
+
+      return extraWidthPadding;
+    }
+
     Calendar.prototype.renderSvg = function(group) {
-      return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', (group + 1) * this.daySizeWithSpace).attr('height', 167).attr('class', 'contrib-calendar');
+      var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
+      return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
     };
 
     Calendar.prototype.renderDays = function() {
diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js
index b95faad..d6e4d9f 100644
--- a/app/assets/javascripts/users/users_bundle.js
+++ b/app/assets/javascripts/users/users_bundle.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index bad8286..9c27799 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -81,6 +81,7 @@
                 if (term.length === 0) {
                   showDivider = 0;
                   if (firstUser) {
+                    // Move current user to the front of the list
                     for (index = j = 0, len = users.length; j < len; index = ++j) {
                       obj = users[index];
                       if (obj.username === firstUser) {
@@ -115,6 +116,7 @@
                 if (showDivider) {
                   users.splice(showDivider, 0, "divider");
                 }
+                // Send the data back
                 return callback(users);
               });
             },
@@ -139,6 +141,7 @@
             inputId: 'issue_assignee_id',
             hidden: function(e) {
               $selectbox.hide();
+              // display:block overrides the hide-collapse rule
               return $value.css('display', '');
             },
             clicked: function(user, $el, e) {
@@ -177,6 +180,7 @@
                   img = "<img src='" + avatar + "' class='avatar avatar-inline' width='30' />";
                 }
               }
+              // split into three parts so we can remove the username section if nessesary
               listWithName = "<li> <a href='#' class='dropdown-menu-user-link " + selected + "'> " + img + " <strong class='dropdown-menu-user-full-name'> " + user.name + " </strong>";
               listWithUserName = "<span class='dropdown-menu-user-username'> " + username + " </span>";
               listClosingTags = "</a> </li>";
@@ -215,6 +219,7 @@
                 };
                 if (query.term.length === 0) {
                   if (firstUser) {
+                    // Move current user to the front of the list
                     ref = data.results;
                     for (index = j = 0, len = ref.length; j < len; index = ++j) {
                       obj = ref[index];
@@ -271,6 +276,7 @@
               return _this.formatSelection.apply(_this, args);
             },
             dropdownCssClass: "ajax-users-dropdown",
+            // we do not want to escape markup since we are displaying html in results
             escapeMarkup: function(m) {
               return m;
             }
@@ -318,6 +324,8 @@
       });
     };
 
+    // Return users list. Filtered by query
+    // Only active users retrieved
     UsersSelect.prototype.users = function(query, options, callback) {
       var url;
       url = this.buildUrl(this.usersPath);
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index 71236c6..777b32b 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -1,21 +1,34 @@
-
+// Zen Mode (full screen) textarea
+//
 /*= provides zen_mode:enter */
-
-
 /*= provides zen_mode:leave */
-
-
+//
 /*= require jquery.scrollTo */
-
-
 /*= require dropzone */
-
-
 /*= require mousetrap */
-
-
 /*= require mousetrap/pause */
 
+//
+// ### Events
+//
+// `zen_mode:enter`
+//
+// Fired when the "Edit in fullscreen" link is clicked.
+//
+// **Synchronicity** Sync
+// **Bubbles** Yes
+// **Cancelable** No
+// **Target** a.js-zen-enter
+//
+// `zen_mode:leave`
+//
+// Fired when the "Leave Fullscreen" link is clicked.
+//
+// **Synchronicity** Sync
+// **Bubbles** Yes
+// **Cancelable** No
+// **Target** a.js-zen-leave
+//
 (function() {
   this.ZenMode = (function() {
     function ZenMode() {
@@ -40,6 +53,7 @@
         };
       })(this));
       $(document).on('keydown', function(e) {
+        // Esc
         if (e.keyCode === 27) {
           e.preventDefault();
           return $(document).trigger('zen_mode:leave');
@@ -52,6 +66,7 @@
       this.active_backdrop = $(backdrop);
       this.active_backdrop.addClass('fullscreen');
       this.active_textarea = this.active_backdrop.find('textarea');
+      // Prevent a user-resized textarea from persisting to fullscreen
       this.active_textarea.removeAttr('style');
       return this.active_textarea.focus();
     };
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index a306b8f..d5cca1b 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -24,6 +24,7 @@
 @import "framework/issue_box.scss";
 @import "framework/jquery.scss";
 @import "framework/lists.scss";
+ at import "framework/logo.scss";
 @import "framework/markdown_area.scss";
 @import "framework/mobile.scss";
 @import "framework/modal.scss";
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 1fec61b..1e9a45c 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -8,65 +8,44 @@
 // Copyright (c) 2016 Daniel Eden
 
 .animated {
-  -webkit-animation-duration: 1s;
-  animation-duration: 1s;
-  -webkit-animation-fill-mode: both;
-  animation-fill-mode: both;
-}
-
-.animated.infinite {
-  -webkit-animation-iteration-count: infinite;
-  animation-iteration-count: infinite;
-}
+  @include webkit-prefix(animation-duration, 1s);
+  @include webkit-prefix(animation-fill-mode, both);
 
-.animated.hinge {
-  -webkit-animation-duration: 2s;
-  animation-duration: 2s;
-}
+  &.infinite {
+    @include webkit-prefix(animation-iteration-count, infinite);
+  }
 
-.animated.flipOutX,
-.animated.flipOutY,
-.animated.bounceIn,
-.animated.bounceOut {
-  -webkit-animation-duration: .75s;
-  animation-duration: .75s;
-}
+  &.once {
+    @include webkit-prefix(animation-iteration-count, 1);
+  }
 
- at -webkit-keyframes pulse {
-  from {
-    -webkit-transform: scale3d(1, 1, 1);
-    transform: scale3d(1, 1, 1);
+  &.hinge {
+    @include webkit-prefix(animation-duration, 2s);
   }
 
-  50% {
-    -webkit-transform: scale3d(1.05, 1.05, 1.05);
-    transform: scale3d(1.05, 1.05, 1.05);
+  &.flipOutX,
+  &.flipOutY,
+  &.bounceIn,
+  &.bounceOut {
+    @include webkit-prefix(animation-duration, .75s);
   }
 
-  to {
-    -webkit-transform: scale3d(1, 1, 1);
-    transform: scale3d(1, 1, 1);
+  &.short {
+    @include webkit-prefix(animation-duration, 321ms);
+    @include webkit-prefix(animation-fill-mode, none);
   }
 }
 
- at keyframes pulse {
-  from {
-    -webkit-transform: scale3d(1, 1, 1);
-    transform: scale3d(1, 1, 1);
+ at include keyframes(pulse) {
+  from, to {
+    @include webkit-prefix(transform, scale3d(1, 1, 1));
   }
 
   50% {
-    -webkit-transform: scale3d(1.05, 1.05, 1.05);
-    transform: scale3d(1.05, 1.05, 1.05);
-  }
-
-  to {
-    -webkit-transform: scale3d(1, 1, 1);
-    transform: scale3d(1, 1, 1);
+    @include webkit-prefix(transform, scale3d(1.05, 1.05, 1.05));
   }
 }
 
 .pulse {
-  -webkit-animation-name: pulse;
-  animation-name: pulse;
+  @include webkit-prefix(animation-name, pulse);
 }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 7ce203d..f522320 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -249,6 +249,10 @@
   > .controls {
     float: right;
   }
+
+  .new-branch {
+    margin-top: 3px;
+  }
 }
 
 .content-block-small {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 5a08a92..4618687 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -204,6 +204,12 @@
     position: relative;
     top: 2px;
   }
+
+  svg, .fa {
+    &:not(:last-child) {
+      margin-right: 3px;
+    }
+  }
 }
 
 .btn-lg {
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index c1e5305..5957dce 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -53,7 +53,7 @@ pre {
 
   &.well-pre {
     border: 1px solid #eee;
-    background: #f9f9f9;
+    background: $gray-light;
     border-radius: 0;
     color: #555;
   }
@@ -225,7 +225,7 @@ li.note {
 
 .milestone {
   &.milestone-closed {
-    background: #f9f9f9;
+    background: $gray-light;
   }
   .progress {
     margin-bottom: 0;
@@ -248,7 +248,7 @@ li.note {
 
 img.emoji {
   height: 20px;
-  vertical-align: middle;
+  vertical-align: top;
   width: 20px;
 }
 
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index f1635a5..b0ba112 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -17,6 +17,12 @@
 
 .dropdown {
   position: relative;
+
+  .btn-link {
+    &:hover {
+      cursor: pointer;
+    }
+  }
 }
 
 .open {
@@ -84,6 +90,15 @@
       width: 100%;
     }
   }
+
+  // Allows dynamic-width text in the dropdown toggle.
+  // Resizes to allow long text without overflowing the container.
+  &.dynamic {
+    width: auto;
+    min-width: 160px;
+    max-width: 100%;
+    padding-right: 25px;
+  }
 }
 
 .dropdown-menu,
@@ -168,6 +183,13 @@
     &.dropdown-menu-user-link {
       line-height: 16px;
     }
+
+    .icon-play {
+      fill: $table-text-gray;
+      margin-right: 6px;
+      height: 12px;
+      width: 11px;
+    }
   }
 
   .dropdown-header {
@@ -180,6 +202,12 @@
   .separator + .dropdown-header {
     padding-top: 2px;
   }
+
+  .unclickable {
+    cursor: not-allowed;
+    padding: 5px 8px;
+    color: $dropdown-header-color;
+  }
 }
 
 .dropdown-menu-large {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 407f187..76a3c08 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -63,9 +63,10 @@
     &.image_file {
       background: #eee;
       text-align: center;
+
       img {
-        padding: 100px;
-        max-width: 50%;
+        padding: 20px;
+        max-width: 80%;
       }
     }
 
@@ -93,7 +94,6 @@
     &.blame {
       table {
         border: none;
-        box-shadow: none;
         margin: 0;
       }
       tr {
@@ -107,19 +107,10 @@
           border-right: none;
         }
       }
-      img.avatar {
-        border: 0 none;
-        float: none;
-        margin: 0;
-        padding: 0;
-      }
       td.blame-commit {
-        background: #f9f9f9;
-        min-width: 350px;
-
-        .commit-author-link {
-          color: #888;
-        }
+        padding: 0 10px;
+        min-width: 400px;
+        background: $gray-light;
       }
       td.line-numbers {
         float: none;
@@ -132,12 +123,6 @@
       }
       td.lines {
         padding: 0;
-        code {
-          font-family: $monospace_font;
-        }
-        pre {
-          margin: 0;
-        }
       }
     }
 
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 9209347..1982794 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -1,6 +1,10 @@
 .filter-item {
   margin-right: 6px;
   vertical-align: top;
+
+  &.reset-filters {
+    padding: 7px;
+  }
 }
 
 @media (min-width: $screen-sm-min) {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 43d5566..37ff7e2 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -19,7 +19,6 @@ input[type='text'].danger {
 }
 
 .form-actions {
-  margin: -$gl-padding;
   margin-top: 0;
   margin-bottom: -$gl-padding;
   padding: $gl-padding;
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index f4d35c4..c0de09f 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -2,7 +2,7 @@
  * Styles that apply to all GFM related forms.
  */
 
-.gfm-commit, .gfm-commit_range {
+.gfm-commit_range {
   font-family: $monospace_font;
   font-size: 90%;
 }
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 0c60707..d4a030f 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -2,16 +2,6 @@
  *  Application Header
  *
  */
- at mixin tanuki-logo-colors($path-color) {
-  fill: $path-color;
-  transition: all 0.8s;
-
-  &:hover,
-  &.highlight {
-    fill: lighten($path-color, 25%);
-    transition: all 0.1s;
-  }
-}
 
 header {
   transition: padding $sidebar-transition-duration;
@@ -25,7 +15,7 @@ header {
       margin: 8px 0;
       text-align: center;
 
-      #tanuki-logo, img {
+      .tanuki-logo, img {
         height: 36px;
       }
     }
@@ -87,14 +77,10 @@ header {
       }
     }
 
-    &.header-collapsed {
-      padding: 0 16px;
-    }
-
     .side-nav-toggle {
       position: absolute;
       left: -10px;
-      margin: 6px 0;
+      margin: 7px 0;
       font-size: 18px;
       padding: 6px 10px;
       border: none;
@@ -146,6 +132,8 @@ header {
     }
 
     .title {
+      position: relative;
+      padding-right: 20px;
       margin: 0;
       font-size: 19px;
       max-width: 400px;
@@ -158,7 +146,11 @@ header {
       vertical-align: top;
       white-space: nowrap;
 
-      @media (max-width: $screen-sm-max) {
+      @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
+        max-width: 300px;
+      }
+
+      @media (max-width: $screen-xs-max) {
         max-width: 190px;
       }
 
@@ -170,11 +162,15 @@ header {
       }
 
       .dropdown-toggle-caret {
-        position: relative;
-        top: -2px;
+        color: $gl-text-color;
+        border: transparent;
+        background: transparent;
+        position: absolute;
+        right: 3px;
         width: 12px;
-        line-height: 12px;
-        margin-left: 5px;
+        line-height: 19px;
+        margin-top: (($header-height - 19) / 2);
+        padding: 0;
         font-size: 10px;
         text-align: center;
         cursor: pointer;
@@ -205,26 +201,6 @@ header {
   }
 }
 
-#tanuki-logo {
-
-  #tanuki-left-ear,
-  #tanuki-right-ear,
-  #tanuki-nose {
-    @include tanuki-logo-colors($tanuki-red);
-  }
-
-  #tanuki-left-eye,
-  #tanuki-right-eye {
-    @include tanuki-logo-colors($tanuki-orange);
-  }
-
-  #tanuki-left-cheek,
-  #tanuki-right-cheek {
-    @include tanuki-logo-colors($tanuki-yellow);
-  }
-
-}
-
 @media (max-width: $screen-xs-max) {
   header .container-fluid {
     font-size: 18px;
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 7cf4d4f..07c8874 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -6,11 +6,11 @@
   table-layout: fixed;
 
   pre {
-    padding: 10px;
+    padding: 10px 0;
     border: none;
     border-radius: 0;
     font-family: $monospace_font;
-    font-size: $code_font_size !important;
+    font-size: $code_font_size;
     line-height: $code_line_height !important;
     margin: 0;
     overflow: auto;
@@ -20,13 +20,20 @@
     border-left: 1px solid;
 
     code {
+      display: inline-block;
+      min-width: 100%;
       font-family: $monospace_font;
-      white-space: pre;
+      white-space: normal;
       word-wrap: normal;
       padding: 0;
 
       .line {
-        display: inline-block;
+        display: block;
+        width: 100%;
+        min-height: 19px;
+        padding-left: 10px;
+        padding-right: 10px;
+        white-space: pre;
       }
     }
   }
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 965fcc0..46af185 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -162,6 +162,10 @@ ul.content-list {
           margin-right: 0;
         }
       }
+
+      .no-comments {
+        opacity: 0.5;
+      }
     }
 
     // When dragging a list item
diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss
new file mode 100644
index 0000000..3ee3fb4
--- /dev/null
+++ b/app/assets/stylesheets/framework/logo.scss
@@ -0,0 +1,118 @@
+ at mixin unique-keyframes {
+  $animation-name: unique-id();
+  @include webkit-prefix(animation-name, $animation-name);
+
+  @-webkit-keyframes #{$animation-name} {
+    @content;
+  }
+  @keyframes #{$animation-name} {
+    @content;
+  }
+}
+
+ at mixin tanuki-logo-colors($path-color) {
+  fill: $path-color;
+  transition: all 0.8s;
+
+  &:hover {
+    fill: lighten($path-color, 25%);
+    transition: all 0.1s;
+  }
+}
+
+ at mixin tanuki-second-highlight-animations($tanuki-color) {
+  @include unique-keyframes {
+    10%, 80% {
+      fill: #{$tanuki-color}
+    }
+    20%, 90% {
+      fill: lighten($tanuki-color, 25%);
+    }
+  }
+}
+
+ at mixin tanuki-forth-highlight-animations($tanuki-color) {
+  @include unique-keyframes {
+    30%, 60% {
+      fill: #{$tanuki-color};
+    }
+    40%, 70% {
+      fill: lighten($tanuki-color, 25%);
+    }
+  }
+}
+
+.tanuki-logo {
+
+  .tanuki-left-ear,
+  .tanuki-right-ear,
+  .tanuki-nose {
+    @include tanuki-logo-colors($tanuki-red);
+  }
+
+  .tanuki-left-eye,
+  .tanuki-right-eye {
+    @include tanuki-logo-colors($tanuki-orange);
+  }
+
+  .tanuki-left-cheek,
+  .tanuki-right-cheek {
+    @include tanuki-logo-colors($tanuki-yellow);
+  }
+
+  &.animate {
+    .tanuki-shape {
+      @include webkit-prefix(animation-duration, 1.5s);
+      @include webkit-prefix(animation-iteration-count, infinite);
+    }
+
+    .tanuki-left-cheek {
+      @include unique-keyframes {
+        0%, 10%, 100% {
+          fill: lighten($tanuki-yellow, 25%);
+        }
+        90% {
+          fill: $tanuki-yellow;
+        }
+      }
+    }
+
+    .tanuki-left-eye {
+      @include tanuki-second-highlight-animations($tanuki-orange);
+    }
+
+    .tanuki-left-ear {
+      @include tanuki-second-highlight-animations($tanuki-red);
+    }
+
+    .tanuki-nose {
+      @include unique-keyframes {
+        20%, 70% {
+          fill: $tanuki-red;
+        }
+        30%, 80% {
+          fill: lighten($tanuki-red, 25%);
+        }
+      }
+    }
+
+    .tanuki-right-eye {
+      @include tanuki-forth-highlight-animations($tanuki-orange);
+    }
+
+    .tanuki-right-ear {
+      @include tanuki-forth-highlight-animations($tanuki-red);
+    }
+
+    .tanuki-right-cheek {
+      @include unique-keyframes {
+        40% {
+          fill: $tanuki-yellow;
+        }
+        60% {
+          fill: lighten($tanuki-yellow, 25%);
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index d2d60ed..1ec08cd 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -9,43 +9,11 @@
   border-radius: $radius;
 }
 
- at mixin border-radius-left($radius) {
-  @include border-radius($radius 0 0 $radius)
-}
-
- at mixin border-radius-right($radius) {
-  @include border-radius(0 0 $radius $radius)
-}
-
- at mixin linear-gradient($from, $to) {
-  background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to));
-  background-image: -webkit-linear-gradient($from, $to);
-  background-image: -moz-linear-gradient($from, $to);
-  background-image: -ms-linear-gradient($from, $to);
-  background-image: -o-linear-gradient($from, $to);
-}
-
- at mixin transition($transition) {
-  -webkit-transition: $transition;
-  -moz-transition: $transition;
-  -ms-transition: $transition;
-  -o-transition: $transition;
-  transition: $transition;
-}
-
 /**
  * Prefilled mixins
  * Mixins with fixed values
  */
 
- at mixin shade {
-  @include box-shadow(0 0 3px #ddd);
-}
-
- at mixin solid-shade {
-  @include box-shadow(0 0 0 3px #f1f1f1);
-}
-
 @mixin str-truncated($max_width: 82%) {
   display: inline-block;
   overflow: hidden;
@@ -76,7 +44,7 @@
     }
 
     &.active {
-      background: #f9f9f9;
+      background: $gray-light;
       a {
         font-weight: 600;
       }
@@ -94,23 +62,6 @@
   }
 }
 
- at mixin input-big {
-  height: 36px;
-  padding: 5px 10px;
-  font-size: 16px;
-  line-height: 24px;
-  color: #7f8fa4;
-  background-color: #fff;
-  border-color: #e7e9ed;
-}
-
- at mixin btn-big {
-  height: 36px;
-  padding: 5px 10px;
-  font-size: 16px;
-  line-height: 24px;
-}
-
 @mixin bulleted-list {
   > ul {
     list-style-type: disc;
@@ -129,3 +80,18 @@
   color: rgba(255, 255, 255, 0.3);
   background: rgba(255, 255, 255, 0.1);
 }
+
+ at mixin webkit-prefix($property, $value) {
+  #{'-webkit-' + $property}: $value;
+  #{$property}: $value;
+}
+
+ at mixin keyframes($animation-name) {
+  @-webkit-keyframes #{$animation-name} {
+    @content;
+  }
+
+  @keyframes #{$animation-name} {
+    @content;
+  }
+}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 367c7d0..76b93b2 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -79,10 +79,6 @@
     padding-left: 15px !important;
   }
 
-  .issue-info, .merge-request-info {
-    display: none;
-  }
-
   .nav-links, .nav-links {
     li a {
       font-size: 14px;
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 26ad287..8374f30 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -1,6 +1,5 @@
 .modal-body {
   position: relative;
-  overflow-y: auto;
   padding: 15px;
 
   .form-actions {
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 7852fc9..ea43f4a 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -1,4 +1,4 @@
- at mixin fade($gradient-direction, $rgba, $gradient-color) {
+ at mixin fade($gradient-direction, $gradient-color) {
   visibility: hidden;
   opacity: 0;
   z-index: 2;
@@ -8,10 +8,7 @@
   height: 30px;
   transition-duration: .3s;
   -webkit-transform: translateZ(0);
-  background: -webkit-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
-  background: -o-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
-  background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
-  background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
+  background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4));
 
   &.scrolling {
     visibility: visible;
@@ -71,7 +68,8 @@
     .badge {
       font-weight: normal;
       background-color: #eee;
-      color: #78a;
+      color: $btn-transparent-color;
+      vertical-align: baseline;
     }
   }
 
@@ -101,8 +99,7 @@
 
 .top-area {
   @include clearfix;
-
-  border-bottom: 1px solid #eee;
+  border-bottom: 1px solid $btn-gray-hover;
 
   .nav-text {
     padding-top: 16px;
@@ -140,7 +137,7 @@
     }
 
     li a {
-      padding: 16px 10px 11px;
+      padding: 16px 15px 11px;
     }
 
     /* Small devices (phones, tablets, 768px and lower) */
@@ -160,6 +157,7 @@
     > .dropdown {
       margin-right: $gl-padding-top;
       display: inline-block;
+      vertical-align: top;
 
       &:last-child {
         margin-right: 0;
@@ -209,12 +207,6 @@
       }
     }
 
-    .project-filter-form {
-      input {
-        background-color: $background-color;
-      }
-    }
-
     @media (max-width: $screen-xs-max) {
       padding-bottom: 0;
       width: 100%;
@@ -334,10 +326,6 @@
         }
       }
 
-      .badge {
-        color: $gl-icon-color;
-      }
-
       &:hover {
         a, i {
           color: $black;
@@ -355,7 +343,7 @@
   }
 
   .fade-right {
-    @include fade(left, rgba(255, 255, 255, 0.4), $background-color);
+    @include fade(left, $background-color);
     right: -5px;
 
     .fa {
@@ -364,7 +352,7 @@
   }
 
   .fade-left {
-    @include fade(right, rgba(255, 255, 255, 0.4), $background-color);
+    @include fade(right, $background-color);
     left: -5px;
 
     .fa {
@@ -375,6 +363,7 @@
   &.sub-nav-scroll {
 
     .fade-right {
+      @include fade(left, $dark-background-color);
       right: 0;
 
       .fa {
@@ -383,6 +372,7 @@
     }
 
     .fade-left {
+      @include fade(right, $dark-background-color);
       left: 0;
 
       .fa {
@@ -399,7 +389,7 @@
     @include scrolling-links();
 
     .fade-right {
-      @include fade(left, rgba(255, 255, 255, 0.4), $white-light);
+      @include fade(left, $white-light);
       right: -5px;
 
       .fa {
@@ -408,7 +398,7 @@
     }
 
     .fade-left {
-      @include fade(right, rgba(255, 255, 255, 0.4), $white-light);
+      @include fade(right, $white-light);
       left: -5px;
 
       .fa {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 21d87cc..c75dacf 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -45,7 +45,8 @@
   min-width: 175px;
 }
 
-.select2-results .select2-result-label {
+.select2-results .select2-result-label,
+.select2-more-results {
   padding: 10px 15px;
 }
 
@@ -150,7 +151,7 @@
   background-position: right 0 bottom 6px;
   border: 1px solid $input-border;
   @include border-radius($border-radius-default);
-  @include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s);
+  transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
 
   &:focus {
     border-color: $input-border-focus;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 015fe3d..3b7de4b 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,6 +1,5 @@
 .page-with-sidebar {
-  padding-top: $header-height;
-  padding-bottom: 25px;
+  padding: $header-height 0 25px;
   transition: padding $sidebar-transition-duration;
 
   &.page-sidebar-pinned {
@@ -15,6 +14,7 @@
     bottom: 0;
     left: 0;
     height: 100%;
+    width: 0;
     overflow: hidden;
     transition: width $sidebar-transition-duration;
     @include box-shadow(2px 0 16px 0 $black-transparent);
@@ -128,10 +128,8 @@
 
     .fa {
       transition: transform .15s;
-    }
 
-    &.is-active {
-      .fa {
+      .page-sidebar-pinned & {
         transform: rotate(90deg);
       }
     }
@@ -152,14 +150,6 @@
   }
 }
 
-.page-sidebar-collapsed {
-  padding-left: 0;
-
-  .sidebar-wrapper {
-    width: 0;
-  }
-}
-
 .page-sidebar-expanded {
   .sidebar-wrapper {
     width: $sidebar_width;
@@ -175,7 +165,7 @@
   }
 }
 
-header.header-pinned-nav {
+header.header-sidebar-pinned {
   @media (min-width: $sidebar-breakpoint) {
     padding-left: ($sidebar_width + $gl-padding);
 
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 371c1bf..915aa63 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -125,7 +125,7 @@ $panel-inner-border:       $border-color;
 //
 //##
 
-$well-bg:                     #f9f9f9;
+$well-bg:                     $gray-light;
 $well-border:                 #eee;
 
 //== Code
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 06874a9..2582cde 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -159,25 +159,18 @@
     position: relative;
 
     a.anchor {
-      // Setting `display: none` would prevent the anchor being scrolled to, so
-      // instead we set the height to 0 and it gets updated on hover.
-      height: 0;
+      left: -16px;
+      position: absolute;
+      text-decoration: none;
+
+      &:after {
+        content: image-url('icon_anchor.svg');
+        visibility: hidden;
+      }
     }
 
-    &:hover > a.anchor {
-      $size: 14px;
-      position: absolute;
-      right: 100%;
-      top: 50%;
-      margin-top: -11px;
-      margin-right: 0;
-      padding-right: 15px;
-      display: inline-block;
-      width: $size;
-      height: $size;
-      background-image: image-url("icon-link.png");
-      background-size: contain;
-      background-repeat: no-repeat;
+    &:hover > a.anchor:after {
+      visibility: visible;
     }
   }
 }
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 5da3901..ad0f34b 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -10,12 +10,78 @@ $sidebar-transition-duration: .15s;
 $sidebar-breakpoint: 1024px;
 
 /*
+ * Color schema
+ */
+$white-light: #fff;
+$white-normal: #ededed;
+$white-dark: #ececec;
+
+$gray-light: #fafafa;
+$gray-normal: #f5f5f5;
+$gray-dark: #ededed;
+$gray-darkest: #c9c9c9;
+
+$green-light: #38ae67;
+$green-normal: #2faa60;
+$green-dark: #2ca05b;
+
+$blue-light: #2ea8e5;
+$blue-normal: #2d9fd8;
+$blue-dark: #2897ce;
+
+$blue-medium-light: #3498cb;
+$blue-medium: #2f8ebf;
+$blue-medium-dark: #2d86b4;
+
+$orange-light: #fc8a51;
+$orange-normal: #e75e40;
+$orange-dark: #ce5237;
+
+$red-light: #e52c5a;
+$red-normal: #d22852;
+$red-dark: darken($red-normal, 5%);
+
+$black: #000;
+$black-transparent: rgba(0, 0, 0, 0.3);
+
+$border-white-light: #f1f2f4;
+$border-white-normal: #d6dae2;
+$border-white-dark: #c6cacf;
+
+$border-gray-light: #dcdcdc;
+$border-gray-normal: #d7d7d7;
+$border-gray-dark: #c6cacf;
+
+$border-green-light: #2faa60;
+$border-green-normal: #2ca05b;
+$border-green-dark: #279654;
+
+$border-blue-light: #2d9fd8;
+$border-blue-normal: #2897ce;
+$border-blue-dark: #258dc1;
+
+$border-orange-light: #fc6d26;
+$border-orange-normal: #ce5237;
+$border-orange-dark: #c14e35;
+
+$border-red-light: #d22852;
+$border-red-normal: #ca264f;
+$border-red-dark: darken($border-red-normal, 5%);
+
+$help-well-bg: $gray-light;
+$help-well-border: #e5e5e5;
+
+$warning-message-bg: #fbf2d9;
+$warning-message-color: #9e8e60;
+$warning-message-border: #f0e2bb;
+
+/*
  * UI elements
  */
 $border-color:          #e5e5e5;
 $focus-border-color:    #3aabf0;
 $table-border-color:    #f0f0f0;
-$background-color:      #fafafa;
+$background-color:      $gray-light;
 $dark-background-color: #f5f5f5;
 $table-text-gray:       #8f8f8f;
 
@@ -35,6 +101,7 @@ $gl-icon-color:        $gl-placeholder-color;
 $gl-grayish-blue:      #7f8fa4;
 $gl-gray:              $gl-text-color;
 $gl-gray-dark:         #313236;
+$gl-gray-light:        $gl-placeholder-color;
 $gl-header-color:      $gl-title-color;
 
 /*
@@ -90,73 +157,6 @@ $btn-side-margin: 10px;
 $btn-sm-side-margin: 7px;
 $btn-xs-side-margin: 5px;
 
-/*
- * Color schema
- */
-
-$white-light: #fff;
-$white-normal: #ededed;
-$white-dark: #ececec;
-
-$gray-light: #faf9f9;
-$gray-normal: #f5f5f5;
-$gray-dark: #ededed;
-$gray-darkest: #c9c9c9;
-
-$green-light: #38ae67;
-$green-normal: #2faa60;
-$green-dark: #2ca05b;
-
-$blue-light: #2ea8e5;
-$blue-normal: #2d9fd8;
-$blue-dark: #2897ce;
-
-$blue-medium-light: #3498cb;
-$blue-medium: #2f8ebf;
-$blue-medium-dark: #2d86b4;
-
-$orange-light: #fc8a51;
-$orange-normal: #e75e40;
-$orange-dark: #ce5237;
-
-$red-light: #e52c5a;
-$red-normal: #d22852;
-$red-dark: darken($red-normal, 5%);
-
-$black: #000;
-$black-transparent: rgba(0, 0, 0, 0.3);
-
-$border-white-light: #f1f2f4;
-$border-white-normal: #d6dae2;
-$border-white-dark: #c6cacf;
-
-$border-gray-light: #dcdcdc;
-$border-gray-normal: #d7d7d7;
-$border-gray-dark: #c6cacf;
-
-$border-green-light: #2faa60;
-$border-green-normal: #2ca05b;
-$border-green-dark: #279654;
-
-$border-blue-light: #2d9fd8;
-$border-blue-normal: #2897ce;
-$border-blue-dark: #258dc1;
-
-$border-orange-light: #fc6d26;
-$border-orange-normal: #ce5237;
-$border-orange-dark: #c14e35;
-
-$border-red-light: #d22852;
-$border-red-normal: #ca264f;
-$border-red-dark: darken($border-red-normal, 5%);
-
-$help-well-bg: #fafafa;
-$help-well-border: #e5e5e5;
-
-$warning-message-bg: #fbf2d9;
-$warning-message-color: #9e8e60;
-$warning-message-border: #f0e2bb;
-
 /* tanuki logo colors */
 $tanuki-red: #e24329;
 $tanuki-orange: #fc6d26;
@@ -186,7 +186,7 @@ $line-removed-dark: #fac5cd;
 $line-number-old: #f9d7dc;
 $line-number-new: #ddfbe6;
 $line-number-select: #fbf2da;
-$match-line: #fafafa;
+$match-line: $gray-light;
 $table-border-gray: #f0f0f0;
 $line-target-blue: #eaf3fc;
 $line-select-yellow: #fcf8e7;
@@ -267,7 +267,13 @@ $zen-control-hover-color: #111;
 $calendar-header-color: #b8b8b8;
 $calendar-hover-bg: #ecf3fe;
 $calendar-border-color: rgba(#000, .1);
-$calendar-unselectable-bg: #faf9f9;
+$calendar-unselectable-bg: $gray-light;
+
+/*
+ *  Cycle Analytics
+ */
+$cycle-analytics-box-padding: 30px;
+$cycle-analytics-box-text-color: #8c8c8c;
 
 /*
  * Personal Access Tokens
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index 5607239..8f71381 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -72,7 +72,6 @@
   margin-bottom: 20px;
 }
 
-
 // Users List
 
 .users-list {
@@ -97,4 +96,49 @@
       line-height: inherit;
     }
   }
+
+  .label-default {
+    color: $btn-transparent-color;
+  }
+}
+
+.abuse-reports {
+  .table {
+    table-layout: fixed;
+  }
+  .subheading {
+    padding-bottom: $gl-padding;
+  }
+  .message {
+    word-wrap: break-word;
+  }
+  .btn {
+    white-space: normal;
+    padding: $gl-btn-padding;
+  }
+  th {
+    width: 15%;
+    &.wide {
+      width: 55%;
+    }
+  }
+  @media (max-width: $screen-sm-max) {
+    th {
+      width: 100%;
+    }
+    td {
+      width: 100%;
+      float: left;
+    }
+  }
+
+  .no-reports {
+    .emoji-icon {
+      margin-left: $btn-side-margin;
+      margin-top: 3px;
+    }
+    span {
+      font-size: 19px;
+    }
+  }
 }
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 5faedfe..9282e0a 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -93,11 +93,8 @@
 }
 
 .award-control {
-  margin-right: 5px;
-  margin-bottom: 5px;
-  padding-left: 5px;
-  padding-right: 5px;
-  line-height: 20px;
+  margin: 3px 5px 3px 0;
+  padding: 6px 5px;
   outline: 0;
 
   &:hover,
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 9ac4d80..9c84dce 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -1,3 +1,4 @@
+lex
 [v-cloak] {
   display: none;
 }
@@ -10,7 +11,7 @@
 .is-dragging {
   // Important because plugin sets inline CSS
   opacity: 1!important;
-  
+
   * {
     // !important to make sure no style can override this when dragging
     cursor: -webkit-grabbing!important;
@@ -18,6 +19,10 @@
   }
 }
 
+.is-ghost {
+  opacity: 0.3;
+}
+
 .dropdown-menu-issues-board-new {
   width: 320px;
 
@@ -34,47 +39,13 @@
   > p {
     margin: 0;
     font-size: 14px;
-    color: #9c9c9c;
   }
 }
 
 .issue-boards-page {
-  .content-wrapper {
-    display: -webkit-flex;
-    display: flex;
-    -webkit-flex-direction: column;
-    flex-direction: column;
-  }
-
-  .sub-nav,
-  .issues-filters {
-    -webkit-flex: none;
-    flex: none;
-  }
-
   .page-with-sidebar {
-    display: -webkit-flex;
-    display: flex;
-    min-height: 100vh;
-    max-height: 100vh;
     padding-bottom: 0;
   }
-
-  .issue-boards-content {
-    display: -webkit-flex;
-    display: flex;
-    -webkit-flex: 1;
-    flex: 1;
-    width: 100%;
-
-    .content {
-      display: -webkit-flex;
-      display: flex;
-      -webkit-flex-direction: column;
-      flex-direction: column;
-      width: 100%;
-    }
-  }
 }
 
 .boards-app-loading {
@@ -83,46 +54,38 @@
 }
 
 .boards-list {
-  display: -webkit-flex;
-  display: flex;
-  -webkit-flex: 1;
-  flex: 1;
-  -webkit-flex-basis: 0;
-  flex-basis: 0;
-  min-height: calc(100vh - 152px);
-  max-height: calc(100vh - 152px);
+  height: calc(100vh - 152px);
+  width: 100%;
   padding-top: 25px;
+  padding-bottom: 25px;
   padding-right: ($gl-padding / 2);
   padding-left: ($gl-padding / 2);
   overflow-x: scroll;
+  white-space: nowrap;
 
   @media (min-width: $screen-sm-min) {
+    height: 475px; // Needed for PhantomJS
+    height: calc(100vh - 220px);
     min-height: 475px;
-    max-height: none;
   }
 }
 
 .board {
-  display: -webkit-flex;
-  display: flex;
-  min-width: calc(85vw - 15px);
-  max-width: calc(85vw - 15px);
-  margin-bottom: 25px;
+  display: inline-block;
+  width: calc(85vw - 15px);
+  height: 100%;
   padding-right: ($gl-padding / 2);
   padding-left: ($gl-padding / 2);
+  white-space: normal;
+  vertical-align: top;
 
   @media (min-width: $screen-sm-min) {
-    min-width: 400px;
-    max-width: 400px;
+    width: 400px;
   }
 }
 
 .board-inner {
-  display: -webkit-flex;
-  display: flex;
-  -webkit-flex-direction: column;
-  flex-direction: column;
-  width: 100%;
+  height: 100%;
   font-size: $issue-boards-font-size;
   background: $background-color;
   border: 1px solid $border-color;
@@ -142,11 +105,6 @@
   }
 }
 
-.board-header-loading-spinner {
-  margin-right: 10px;
-  color: $gray-darkest;
-}
-
 .board-inner-container {
   border-bottom: 1px solid $border-color;
   padding: $gl-padding;
@@ -160,40 +118,6 @@
   border-bottom: 1px solid $border-color;
 }
 
-.board-search-container {
-  position: relative;
-  background-color: #fff;
-
-  .form-control {
-    padding-right: 30px;
-  }
-}
-
-.board-search-icon,
-.board-search-clear-btn {
-  position: absolute;
-  right: $gl-padding + 10px;
-  top: 50%;
-  margin-top: -7px;
-  font-size: 14px;
-}
-
-.board-search-icon {
-  color: $gl-placeholder-color;
-}
-
-.board-search-clear-btn {
-  padding: 0;
-  line-height: 1;
-  background: transparent;
-  border: 0;
-  outline: 0;
-
-  &:hover {
-    color: $gl-link-color;
-  }
-}
-
 .board-delete {
   margin-right: 10px;
   padding: 0;
@@ -232,45 +156,31 @@
 }
 
 .board-list {
-  -webkit-flex: 1;
-  flex: 1;
-  height: 400px;
+  height: calc(100% - 49px);
   margin-bottom: 0;
   padding: 5px;
+  list-style: none;
   overflow-y: scroll;
   overflow-x: hidden;
 }
 
 .board-list-loading {
   margin-top: 10px;
-  font-size: 26px;
-}
-
-.is-ghost {
-  opacity: 0.3;
+  font-size: (26px / $issue-boards-font-size) * 1em;
 }
 
 .card {
   position: relative;
-  width: 100%;
   padding: 10px $gl-padding;
   background: #fff;
   border-radius: $border-radius-default;
   box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5);
   list-style: none;
 
-  &.user-can-drag {
-    padding-left: $gl-padding;
-  }
-
   &:not(:last-child) {
     margin-bottom: 5px;
   }
 
-  a {
-    cursor: pointer;
-  }
-
   .label {
     border: 0;
     outline: 0;
@@ -295,12 +205,30 @@
   line-height: 25px;
 
   .label {
-    margin-right: 4px;
+    margin-right: 5px;
     font-size: (14px / $issue-boards-font-size) * 1em;
   }
 }
 
 .card-number {
-  margin-right: 8px;
-  font-weight: 500;
+  margin-right: 5px;
+}
+
+.issue-boards-search {
+  width: 335px;
+
+  .form-control {
+    display: inline-block;
+    width: 210px;
+  }
+}
+
+.board-list-count {
+  padding: 10px 0;
+  color: $gl-placeholder-color;
+  font-size: 13px;
+
+  > .fa {
+    margin-right: 5px;
+  }
 }
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index c1bb250..a5a260d 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -36,6 +36,7 @@
     &.affix {
       right: 30px;
       bottom: 15px;
+      z-index: 1;
 
       @media (min-width: $screen-md-min) {
         right: 26%;
@@ -47,12 +48,6 @@
       margin-bottom: 10px;
     }
   }
-
-  .page-sidebar-collapsed {
-    .scroll-controls {
-      left: 70px;
-    }
-  }
 }
 
 .build-header {
@@ -107,13 +102,27 @@
   }
 
   .blocks-container {
-    padding: $gl-padding;
+    padding: 0 $gl-padding;
   }
 
   .block {
     width: 100%;
   }
 
+  .block-first {
+    padding: 5px 16px 11px;
+  }
+
+  .js-build-variable {
+    color: $code-color;
+  }
+
+  .js-build-value {
+    padding: 2px 4px;
+    color: $black;
+    background-color: $white-light;
+  }
+
   .build-sidebar-header {
     padding: 0 $gl-padding $gl-padding;
 
@@ -122,6 +131,13 @@
     }
   }
 
+  .retry-link {
+    color: $gl-link-color;
+    &:hover {
+      text-decoration: underline;
+    }
+  }
+
   .stage-item {
     cursor: pointer;
 
@@ -131,7 +147,7 @@
   }
 
   .build-dropdown {
-    padding: 0 $gl-padding;
+    padding: $gl-padding 0;
 
     .dropdown-menu-toggle {
       margin-top: 8px;
@@ -145,12 +161,11 @@
   }
 
   .builds-container {
-    margin-top: $gl-padding;
     background-color: $white-light;
     border-top: 1px solid $border-color;
     border-bottom: 1px solid $border-color;
     max-height: 300px;
-    overflow: scroll;
+    overflow: auto;
 
     svg {
       position: relative;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 6a58b44..dc57a83 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -18,8 +18,7 @@
 }
 
 .commit-row-title {
-  line-height: 1;
-  margin-bottom: 7px;
+  line-height: 1.35;
 
   .notes_count {
     float: right;
@@ -43,6 +42,7 @@
     border: 1px solid $border-gray-dark;
     border-radius: $border-radius-default;
     margin-left: 5px;
+    line-height: 1;
 
     &:hover {
       background-color: darken($gray-light, 10%);
@@ -113,11 +113,13 @@
 
   .commit-row-description {
     font-size: 14px;
-    border-left: 1px solid #eee;
+    border-left: 1px solid $btn-gray-hover;
     padding: 10px 15px;
     margin: 10px 0;
-    background: #f9f9f9;
+    background: $gray-light;
     display: none;
+    white-space: pre-line;
+    word-break: normal;
 
     pre {
       border: none;
@@ -134,7 +136,7 @@
 
   .commit-row-info {
     color: $gl-gray;
-    line-height: 1;
+    line-height: 1.35;
 
     a {
       color: $gl-gray;
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
new file mode 100644
index 0000000..778471a
--- /dev/null
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -0,0 +1,144 @@
+#cycle-analytics {
+  margin: 24px auto 0;
+  max-width: 800px;
+  position: relative;
+
+  .panel {
+
+    .content-block {
+      padding: 24px 0;
+      border-bottom: none;
+      position: relative;
+      
+      @media (max-width: $screen-sm-min) {
+        padding: 6px 0 24px;
+      } 
+    }
+
+    .column {
+      text-align: center;
+      
+      @media (max-width: $screen-sm-min) {
+        padding: 15px 0;
+      }
+
+      .header {
+        font-size: 30px;
+        line-height: 38px;
+        font-weight: normal;
+        margin: 0;
+      }
+
+      .text {
+        color: $layout-link-gray;
+        margin: 0;
+      }
+
+      &:last-child {
+        text-align: right;
+        
+        @media (max-width: $screen-sm-min) {
+          text-align: center;
+        }
+      }
+    }
+
+    .dropdown {
+      top: 13px;
+    }
+  }
+
+  .bordered-box {
+    border: 1px solid $border-color;
+    @include border-radius($border-radius-default);
+  
+  }
+
+  .content-list {
+    li {
+      padding: 18px $gl-padding $gl-padding;
+
+      .container-fluid {
+        padding: 0;
+      }
+    }
+
+    .title-col {
+      p {
+        margin: 0;
+
+        &.title {
+          line-height: 19px;
+          font-size: 15px;
+          font-weight: 600;
+          color: $gl-title-color;
+        }
+        
+        &.text {
+          color: $layout-link-gray;
+          
+          &.value-col {
+            color: $gl-title-color;
+          }
+        }
+      }
+    }
+
+    .value-col {
+      text-align: right;
+
+      span {
+        position: relative;
+        vertical-align: middle;
+        top: 3px;
+      }
+    }
+  }
+
+  .landing {
+    margin-bottom: $gl-padding;
+    overflow: hidden;
+
+    .dismiss-icon {
+      position: absolute;
+      right: $cycle-analytics-box-padding;
+      cursor: pointer;
+      color: #b2b2b2;
+    }
+
+    .svg-container {
+      text-align: center;
+      
+      svg {
+        width: 136px;
+        height: 136px;
+      }
+    }
+    
+    .inner-content {
+      @media (max-width: $screen-sm-min) {
+        padding: 0 28px;
+        text-align: center;
+      }
+
+      h4 {
+        color: $gl-text-color;
+        font-size: 17px;
+      }
+
+      p {
+        color: $cycle-analytics-box-text-color;
+        margin-bottom: $gl-padding;
+      }
+    }
+  }
+
+  .fa-spinner {
+    font-size: 28px;
+    position: relative;
+    margin-left: -20px;
+    left: 50%;
+    margin-top: 36px;
+  }
+
+}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 21cee2e..b8ef76c 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -68,6 +68,11 @@
       border-collapse: separate;
       margin: 0;
       padding: 0;
+      table-layout: fixed;
+
+      .diff-line-num {
+        width: 50px;
+      }
 
       .line_holder td {
         line-height: $code_line_height;
@@ -98,10 +103,6 @@
     }
 
     tr.line_holder.parallel {
-      .old_line, .new_line {
-        min-width: 50px;
-      }
-
       td.line_content.parallel {
         width: 46%;
       }
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 55f9d4a..d01c60e 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -4,8 +4,9 @@
     margin: 0;
   }
 
-  .fa-play {
-    font-size: 14px;
+  .icon-play {
+    height: 13px;
+    width: 12px;
   }
 
   .dropdown-new {
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 5c336bb..1d00da1 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -60,7 +60,7 @@
 
       pre {
         border: none;
-        background: #f9f9f9;
+        background: $gray-light;
         border-radius: 0;
         color: #777;
         margin: 0 20px;
@@ -92,7 +92,7 @@
     border: 1px solid #eee;
     padding: 5px;
     @include border-radius(5px);
-    background: #f9f9f9;
+    background: $gray-light;
     margin-left: 10px;
     top: -6px;
     img {
@@ -115,11 +115,8 @@
       }
 
       &.commits-stat {
-        margin-top: 3px;
         display: block;
-        padding: 3px;
-        padding-left: 0;
-
+        padding: 0 3px 0 0;
         &:hover {
           background: none;
         }
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index b657ca4..732dc64 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -55,3 +55,16 @@
     }
   }
 }
+
+.groups-header {
+
+  @media (min-width: $screen-sm-min) {
+    .nav-links {
+      width: 35%;
+    }
+
+    .nav-controls {
+      width: 65%;
+    }
+  }
+}
diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss
index 84cc352..a4f76a9 100644
--- a/app/assets/stylesheets/pages/import.scss
+++ b/app/assets/stylesheets/pages/import.scss
@@ -1,22 +1,3 @@
-i.icon-gitorious {
-  display: inline-block;
-  background-position: 0 0;
-  background-size: contain;
-  background-repeat: no-repeat;
-}
-
-i.icon-gitorious-small {
-  background-image: image-url('gitorious-logo-blue.png');
-  width: 13px;
-  height: 13px;
-}
-
-i.icon-gitorious-big {
-  background-image: image-url('gitorious-logo-black.png');
-  width: 18px;
-  height: 18px;
-}
-
 .import-jobs-from-col,
 .import-jobs-to-col {
   width: 40%;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 46c4a11..41079b6 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -206,7 +206,7 @@
     padding-top: 0;
 
     .block {
-      width: $sidebar_collapsed_width - 1px;
+      width: $sidebar_collapsed_width - 2px;
       margin-left: -19px;
       padding: 15px 0 0;
       border-bottom: none;
@@ -404,3 +404,18 @@
     margin-bottom: $gl-padding;
   }
 }
+
+.issuable-list {
+  li {
+    .issue-check {
+      float: left;
+      padding-right: $gl-padding;
+      margin-bottom: 10px;
+      min-width: 15px;
+
+      .selected_issue {
+        vertical-align: text-top;
+      }
+    }
+  }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index dfe1e30..3ac34cb 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -7,20 +7,9 @@
       margin-bottom: 2px;
     }
 
-    .issue-check {
-      float: left;
-      padding-right: 8px;
-      margin-bottom: 10px;
-      min-width: 15px;
-    }
-
     .issue-labels {
       display: inline-block;
     }
-
-    .issue-no-comments {
-      opacity: 0.5;
-    }
   }
 }
 
@@ -44,6 +33,15 @@ form.edit-issue {
   margin: 0;
 }
 
+ul.related-merge-requests > li {
+  display: -ms-flexbox;
+  display: -webkit-flex;
+  display: flex;
+  .merge-request-id {
+    flex-shrink: 0;
+  }
+}
+
 .merge-requests-title, .related-branches-title {
   font-size: 16px;
   font-weight: 600;
@@ -68,12 +66,12 @@ form.edit-issue {
   }
 
   &.closed {
-    background: #f9f9f9;
+    background: $gray-light;
     border-color: #e5e5e5;
   }
 
   &.merged {
-    background: #f9f9f9;
+    background: $gray-light;
     border-color: #e5e5e5;
   }
 }
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 606459f..38c7cd9 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -7,6 +7,7 @@
     display: inline-block;
     margin-right: 10px;
     margin-bottom: 10px;
+    text-decoration: none;
   }
 
   &.suggest-colors-dropdown {
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index 1f49989..5ec6607 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -16,7 +16,7 @@ $colors: (
   white_button_origin_chosen  : #268ced,
 
   white_header_not_chosen     : #f0f0f0,
-  white_line_not_chosen       : #f9f9f9,
+  white_line_not_chosen       : $gray-light,
 
 
   dark_header_head_neutral   : rgba(#3f3, .2),
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index fcdaf67..926247e 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -231,10 +231,6 @@
   .merge-request-labels {
     display: inline-block;
   }
-
-  .merge-request-no-comments {
-    opacity: 0.5;
-  }
 }
 
 .merge-request-angle {
@@ -269,7 +265,7 @@
 
 .builds {
   .table-holder {
-    overflow-x: scroll;
+    overflow-x: auto;
   }
 }
 
@@ -375,6 +371,45 @@
   }
 }
 
+.mr-version-controls {
+  background: $background-color;
+  border-bottom: 1px solid $border-color;
+  color: $gl-text-color;
+
+  .mr-version-menus-container {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    padding: 16px;
+  }
+
+  .comments-disabled-notif {
+    padding: 10px 16px;
+    .btn {
+      margin-left: 5px;
+    }
+  }
+
+  .mr-version-dropdown,
+  .mr-version-compare-dropdown {
+    margin: 0 7px;
+  }
+
+  .comments-disabled-notif {
+    border-top: 1px solid $border-color;
+  }
+
+  .dropdown-title {
+    color: $gl-text-color;
+  }
+
+  .fa-info-circle {
+    color: $orange-normal;
+    padding-right: 5px;
+  }
+}
+
 .merge-request-details {
 
   .title {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 08d1692..54124a3 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -281,19 +281,13 @@ ul.notes {
     font-size: 17px;
   }
 
-  &.js-note-delete {
-    i {
-      &:hover {
-        color: $gl-text-red;
-      }
+  &:hover {
+    .danger-highlight {
+      color: $gl-text-red;
     }
-  }
 
-  &.js-note-edit {
-    i {
-      &:hover {
-        color: $gl-link-color;
-      }
+    .link-highlight {
+      color: $gl-link-color;
     }
   }
 }
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 6fa097e..1b4d12d 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -2,6 +2,7 @@
   .stage {
     max-width: 90px;
     width: 90px;
+    text-align: center;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
@@ -146,16 +147,36 @@
   }
 
   .stage-cell {
+    font-size: 0;
 
     svg {
       height: 18px;
       width: 18px;
+      position: relative;
+      z-index: 2;
       vertical-align: middle;
       overflow: visible;
     }
 
-    .light {
-      width: 3px;
+    .stage-container {
+      display: inline-block;
+      position: relative;
+      margin-right: 6px;
+
+      .tooltip {
+        white-space: nowrap;
+      }
+
+      &:not(:last-child) {
+        &::after {
+          content: '';
+          width: 8px;
+          position: absolute;;
+          right: -7px;
+          bottom: 8px;
+          border-bottom: 2px solid $border-color;
+        }
+      }
     }
   }
 
@@ -215,6 +236,13 @@
           border-color: $border-white-normal;
         }
       }
+
+      .btn {
+        .icon-play {
+          height: 13px;
+          width: 12px;
+        }
+      }
     }
   }
 
@@ -254,7 +282,6 @@
   width: 100%;
   overflow: auto;
   white-space: nowrap;
-  max-height: 500px;
   transition: max-height 0.3s, padding 0.3s;
 
   &.graph-collapsed {
@@ -265,7 +292,6 @@
 
 .pipeline-visualization {
   position: relative;
-  min-width: 1220px;
 
   ul {
     padding: 0;
@@ -275,7 +301,7 @@
 .stage-column {
   display: inline-block;
   vertical-align: top;
-  margin-right: 50px;
+  margin-right: 65px;
 
   li {
     list-style: none;
@@ -315,18 +341,95 @@
 
     .build-content {
       width: 130px;
-      white-space: nowrap;
-      overflow: hidden;
-      text-overflow: ellipsis;
+
+      .ci-status-text {
+        width: 110px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        vertical-align: middle;
+        display: inline-block;
+        position: relative;
+        top: -1px;
+      }
 
       a {
         color: $layout-link-gray;
+        text-decoration: none;
+
+        &:hover {
+          .ci-status-text {
+            text-decoration: underline;
+          }
+        }
+      }
+
+      .dropdown-menu-toggle {
+        border: none;
+        width: auto;
+        padding: 0;
+        color: $layout-link-gray;
+
+        .ci-status-text {
+          width: 80px;
+        }
+      }
+
+      .grouped-pipeline-dropdown {
+        padding: 8px 0;
+        width: 200px;
+        left: auto;
+        right: -214px;
+        top: -9px;
+
+        a:hover {
+          .ci-status-text {
+            text-decoration: none;
+          }
+        }
+
+        .ci-status-text {
+          width: 145px;
+        }
+
+        .arrow {
+          &:before,
+          &:after {
+            content: '';
+            display: inline-block;
+            position: absolute;
+            width: 0;
+            height: 0;
+            border-color: transparent;
+            border-style: solid;
+            top: 18px;
+          }
+
+          &:before {
+            left: -5px;
+            margin-top: -6px;
+            border-width: 7px 5px 7px 0;
+            border-right-color: $border-color;
+          }
+
+          &:after {
+            left: -4px;
+            margin-top: -9px;
+            border-width: 10px 7px 10px 0;
+            border-right-color: $white-light;
+          }
+        }
+      }
+
+      .badge {
+        background-color: $gray-dark;
+        color: $layout-link-gray;
+        font-weight: normal;
       }
     }
 
     svg {
-      position: relative;
-      top: 2px;
+      vertical-align: middle;
       margin-right: 5px;
     }
 
@@ -336,9 +439,9 @@
         content: '';
         position: absolute;
         top: 50%;
-        right: -54px;
+        right: -69px;
         border-top: 2px solid $border-color;
-        width: 54px;
+        width: 69px;
         height: 1px;
       }
     }
@@ -358,22 +461,25 @@
       &::after {
         right: -20px;
         border-right: 2px solid $border-color;
-        border-radius: 0 0 50px;
+        border-radius: 0 0 15px;
       }
 
       // Left connecting curves
       &::before {
         left: -20px;
         border-left: 2px solid $border-color;
-        border-radius: 0 0 0 50px;
+        border-radius: 0 0 0 15px;
       }
     }
 
     // Connect second build to first build with smaller curved line
     &:nth-child(2) {
       &::after, &::before {
-        height: 45px;
-        top: -26px;
+        height: 29px;
+        top: -10px;
+      }
+      .curve {
+        display: block;
       }
     }
   }
@@ -392,6 +498,12 @@
           border: none;
         }
       }
+      // Remove opposite curve
+      .curve {
+        &::before {
+          display: none;
+        }
+      }
     }
   }
 
@@ -403,6 +515,39 @@
           border: none;
         }
       }
+      // Remove opposite curve
+      .curve {
+        &::after {
+          display: none;
+        }
+      }
+    }
+  }
+
+  // Curve first child connecting lines in opposite direction
+  .curve {
+    display: none;
+
+    &::before,
+    &::after {
+      content: '';
+      width: 21px;
+      height: 25px;
+      position: absolute;
+      top: -29px;
+      border-top: 2px solid $border-color;
+    }
+
+    &::after {
+      left: -39px;
+      border-right: 2px solid $border-color;
+      border-radius: 0 15px;
+    }
+
+    &::before {
+      right: -39px;
+      border-left: 2px solid $border-color;
+      border-radius: 15px 0 0;
     }
   }
 }
@@ -421,11 +566,22 @@
 .pipelines.tab-pane {
 
   .content-list.pipelines {
-    overflow: scroll;
+    overflow: auto;
   }
 
   .stage {
-    max-width: 60px;
-    width: 60px;
+    max-width: 100px;
+    width: 100px;
+  }
+
+  .pipeline-actions {
+    min-width: initial;
+  }
+}
+
+.ci-status-icon-created {
+
+  svg {
+    fill: $gray-darkest;
   }
 }
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 91253ed..8c8c403 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -99,7 +99,7 @@
     margin-left: auto;
     margin-right: auto;
     margin-bottom: 15px;
-    max-width: 480px;
+    max-width: 700px;
 
     > p {
       margin-bottom: 0;
@@ -311,6 +311,14 @@ a.deploy-project-label {
   color: $gl-success;
 }
 
+.lfs-enabled {
+  color: $gl-success;
+}
+
+.lfs-disabled {
+  color: $gl-warning;
+}
+
 .breadcrumb.repo-breadcrumb {
   padding: 0;
   background: transparent;
@@ -326,6 +334,10 @@ a.deploy-project-label {
   a {
     color: $gl-dark-link-color;
   }
+
+  .dropdown-menu {
+    width: 240px;
+  }
 }
 
 .last-push-widget {
@@ -600,18 +612,25 @@ pre.light-well {
   }
 }
 
-.project-show-readme .readme-holder {
-  padding: $gl-padding 0;
-  border-top: 0;
-
-  .edit-project-readme {
-    z-index: 2;
-    position: relative;
+.project-show-readme {
+  .row-content-block {
+    background-color: inherit;
+    border: none;
   }
 
-  .wiki h1 {
-    border-bottom: none;
-    padding: 0;
+  .readme-holder {
+    padding: $gl-padding 0;
+    border-top: 0;
+
+    .edit-project-readme {
+      z-index: 2;
+      position: relative;
+    }
+
+    .wiki h1 {
+      border-bottom: none;
+      padding: 0;
+    }
   }
 }
 
@@ -708,9 +727,15 @@ pre.light-well {
   }
 }
 
-.project-refs-form {
-  .dropdown-menu {
-    width: 300px;
+.project-refs-form .dropdown-menu, .dropdown-menu-projects {
+  width: 300px;
+
+  @media (min-width: $screen-sm-min) {
+    width: 500px;
+  }
+
+  a {
+    white-space: normal;
   }
 }
 
@@ -745,3 +770,13 @@ pre.light-well {
     }
   }
 }
+
+.project-path {
+  .form-control {
+    min-width: 100px;
+  }
+  .select2-choice {
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+  }
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index c9d436d..436fb00 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -80,7 +80,7 @@
 
     .search-icon {
       @extend .fa-search;
-      @include transition(color .15s);
+      transition: color 0.15s;
       -webkit-user-select: none;
       -moz-user-select: none;
       -ms-user-select: none;
@@ -125,7 +125,7 @@
     }
 
     .location-badge {
-      @include transition(all .15s);
+      transition: all 0.15s;
       background-color: $location-badge-active-bg;
       color: $white-light;
     }
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 2aa939b..4d5df56 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -2,20 +2,6 @@
   padding: 2px;
 }
 
-.snippet-holder {
-  margin-bottom: -$gl-padding;
-
-  .file-holder {
-    border-top: 0;
-  }
-
-  .file-actions {
-    .btn-clipboard {
-      @extend .btn;
-    }
-  }
-}
-
 .markdown-snippet-copy {
   position: fixed;
   top: -10px;
@@ -24,29 +10,25 @@
   max-width: 0;
 }
 
-.file-holder.snippet-file-content {
-  padding-bottom: $gl-padding;
-  border-bottom: 1px solid $border-color;
-
-  .file-title {
-    padding-top: $gl-padding;
-    padding-bottom: $gl-padding;
-  }
+.snippet-file-content {
+  border-radius: 3px;
+  margin-bottom: $gl-padding;
 
-  .file-actions {
-    top: 12px;
+  .btn-clipboard {
+    @extend .btn;
   }
+}
 
-  .file-content {
-    border-left: 1px solid $border-color;
-    border-right: 1px solid $border-color;
-    border-bottom: 1px solid $border-color;
-  }
+.project-snippets .awards {
+  border-bottom: 1px solid $table-border-color;
+  padding-bottom: $gl-padding;
 }
 
 .snippet-title {
   font-size: 24px;
-  font-weight: normal;
+  font-weight: 600;
+  padding: $gl-padding;
+  padding-left: 0;
 }
 
 .snippet-actions {
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 587f2d9..0ee7cee 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -43,6 +43,15 @@
       border-color: $blue-normal;
     }
 
+    &.ci-created {
+      color: $table-text-gray;
+      border-color: $table-text-gray;
+
+      svg {
+        fill: $table-text-gray;
+      }
+    }
+
     svg {
       height: 13px;
       width: 13px;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index cf16d07..68a5d1a 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -20,10 +20,43 @@
   }
 }
 
-.todo {
+.todos-list > .todo {
+  // workaround because we cannot use border-colapse
+  border-top: 1px solid transparent;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-direction: row;
+  flex-direction: row;
+
   &:hover {
+    background-color: $row-hover;
+    border-color: $row-hover-border;
     cursor: pointer;
   }
+
+  // overwrite border style of .content-list
+  &:last-child {
+    border-bottom: 1px solid transparent;
+
+    &:hover {
+      border-color: $row-hover-border;
+    }
+  }
+
+  .todo-actions {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: center;
+    justify-content: center;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    margin-left: 10px;
+  }
+
+  .todo-item {
+    -webkit-flex: auto;
+    flex: auto;
+  }
 }
 
 .todo-item {
@@ -43,8 +76,6 @@
   }
 
   .todo-body {
-    margin-right: 174px;
-
     .todo-note {
       word-wrap: break-word;
 
@@ -68,7 +99,7 @@
 
       pre {
         border: none;
-        background: #f9f9f9;
+        background: $gray-light;
         border-radius: 0;
         color: #777;
         margin: 0 20px;
@@ -90,6 +121,12 @@
 }
 
 @media (max-width: $screen-xs-max) {
+  .todo {
+    .avatar {
+      display: none;
+    }
+  }
+
   .todo-item {
     .todo-title {
       white-space: normal;
@@ -98,10 +135,6 @@
       margin-bottom: 10px;
     }
 
-    .avatar {
-      display: none;
-    }
-
     .todo-body {
       margin: 0;
       border-left: 2px solid #ddd;
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 9da40fe..1778c06 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -11,6 +11,10 @@
     }
   }
 
+  .add-to-tree {
+    vertical-align: top;
+  }
+
   .tree-table {
     margin-bottom: 0;
 
@@ -22,6 +26,15 @@
         line-height: 21px;
       }
 
+      .last-commit {
+        @include str-truncated(60%);
+      }
+
+      .commit-history-link-spacer {
+        margin: 0 10px;
+        color: $table-border-color;
+      }
+
       &:hover {
         td {
           background-color: $row-hover;
@@ -77,11 +90,17 @@
     }
   }
 
-  .tree_commit {
-    color: $gl-gray;
+  .tree-time-ago {
+    min-width: 135px;
+    color: $gl-gray-light;
+  }
+
+  .tree-commit {
+    max-width: 320px;
+    color: $gl-gray-light;
 
     .tree-commit-link {
-      color: $gl-gray;
+      color: $gl-gray-light;
 
       &:hover {
         text-decoration: underline;
diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
index 8d855ce..c984610 100644
--- a/app/assets/stylesheets/pages/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
@@ -20,6 +20,9 @@
   $l-cyan: #8abeb7;
   $l-white: $ci-text-color;
 
+  .term-bold {
+    font-weight: bold;
+  }
   .term-italic {
     font-style: italic;
   }
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index cdfa8d9..aed77d0 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -60,6 +60,14 @@ class Admin::GroupsController < Admin::ApplicationController
   end
 
   def group_params
-    params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level, :request_access_enabled)
+    params.require(:group).permit(
+      :avatar,
+      :description,
+      :lfs_enabled,
+      :name,
+      :path,
+      :request_access_enabled,
+      :visibility_level
+    )
   end
 end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 634d36a..bd4ba38 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
   include Gitlab::GonHelper
   include GitlabRoutingHelper
   include PageLayoutHelper
+  include SentryHelper
   include WorkhorseHelper
 
   before_action :authenticate_user_from_private_token!
@@ -23,8 +24,8 @@ class ApplicationController < ActionController::Base
 
   protect_from_forgery with: :exception
 
-  helper_method :abilities, :can?, :current_application_settings
-  helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
+  helper_method :can?, :current_application_settings
+  helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
 
   rescue_from Encoding::CompatibilityError do |exception|
     log_exception(exception)
@@ -46,28 +47,6 @@ class ApplicationController < ActionController::Base
 
   protected
 
-  def sentry_context
-    if Rails.env.production? && current_application_settings.sentry_enabled
-      if current_user
-        Raven.user_context(
-          id: current_user.id,
-          email: current_user.email,
-          username: current_user.username,
-        )
-      end
-
-      Raven.tags_context(program: sentry_program_context)
-    end
-  end
-
-  def sentry_program_context
-    if Sidekiq.server?
-      'sidekiq'
-    else
-      'rails'
-    end
-  end
-
   # This filter handles both private tokens and personal access tokens
   def authenticate_user_from_private_token!
     token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
@@ -118,12 +97,8 @@ class ApplicationController < ActionController::Base
     current_application_settings.after_sign_out_path.presence || new_user_session_path
   end
 
-  def abilities
-    Ability.abilities
-  end
-
   def can?(object, action, subject)
-    abilities.allowed?(object, action, subject)
+    Ability.allowed?(object, action, subject)
   end
 
   def access_denied!
@@ -271,10 +246,6 @@ class ApplicationController < ActionController::Base
     Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
   end
 
-  def gitorious_import_enabled?
-    current_application_settings.import_sources.include?('gitorious')
-  end
-
   def google_code_import_enabled?
     current_application_settings.import_sources.include?('google_code')
   end
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index a7af3cb..e06d12c 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -7,19 +7,14 @@ module Ci
 
     def create
       @content = params[:content]
+      @error = Ci::GitlabCiYamlProcessor.validation_message(@content)
+      @status = @error.blank?
 
-      if @content.blank?
-        @status = false
-        @error = "Please provide content of .gitlab-ci.yml"
-      else
+      if @error.blank?
         @config_processor = Ci::GitlabCiYamlProcessor.new(@content)
         @stages = @config_processor.stages
         @builds = @config_processor.builds
-        @status = true
       end
-    rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
-      @error = e.message
-      @status = false
     rescue
       @error = 'Undefined error'
       @status = false
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index ba07cea..d5a8a96 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -62,6 +62,7 @@ module AuthenticatesWithTwoFactor
       session.delete(:otp_user_id)
       session.delete(:challenges)
 
+      remember_me(user) if user_params[:remember_me] == '1'
       sign_in(user)
     else
       flash.now[:alert] = 'Authentication via U2F device failed.'
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index f2b8f29..dacb567 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -7,8 +7,7 @@ module CreatesCommit
     commit_params = @commit_params.merge(
       source_project: @project,
       source_branch: @ref,
-      target_branch: @target_branch,
-      previous_path: @previous_path
+      target_branch: @target_branch
     )
 
     result = service.new(@tree_edit_project, current_user, commit_params).execute
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index f40b624..bb32bc5 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -3,21 +3,54 @@ module IssuableActions
 
   included do
     before_action :authorize_destroy_issuable!, only: :destroy
+    before_action :authorize_admin_issuable!, only: :bulk_update
   end
 
   def destroy
     issuable.destroy
+    destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
+    TodoService.new.public_send(destroy_method, issuable, current_user)
 
     name = issuable.class.name.titleize.downcase
     flash[:notice] = "The #{name} was successfully deleted."
     redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
   end
 
+  def bulk_update
+    result = Issuable::BulkUpdateService.new(project, current_user, bulk_update_params).execute(resource_name)
+    quantity = result[:count]
+
+    render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
+  end
+
   private
 
   def authorize_destroy_issuable!
-    unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable)
+    unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
       return access_denied!
     end
   end
+
+  def authorize_admin_issuable!
+    unless can?(current_user, :"admin_#{resource_name}", @project)
+      return access_denied!
+    end
+  end
+
+  def bulk_update_params
+    params.require(:update).permit(
+      :issuable_ids,
+      :assignee_id,
+      :milestone_id,
+      :state_event,
+      :subscription_event,
+      label_ids: [],
+      add_label_ids: [],
+      remove_label_ids: []
+    )
+  end
+
+  def resource_name
+    @resource_name ||= controller_name.singularize
+  end
 end
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index a69877e..4cb3be4 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -13,7 +13,7 @@ module ServiceParams
                     # `issue_events` and `merge_request_events` (singular!)
                     # See app/helpers/services_helper.rb for how we
                     # make those event names plural as special case.
-                    :issues_events, :merge_requests_events,
+                    :issues_events, :confidential_issues_events, :merge_requests_events,
                     :notify_only_broken_builds, :notify_only_broken_pipelines,
                     :add_pusher, :send_from_committer_email, :disable_diffs,
                     :external_wiki_url, :notify, :color,
diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
index 036777c..3717c49 100644
--- a/app/controllers/concerns/toggle_award_emoji.rb
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -8,10 +8,16 @@ module ToggleAwardEmoji
   def toggle_award_emoji
     name = params.require(:name)
 
-    awardable.toggle_award_emoji(name, current_user)
-    TodoService.new.new_award_emoji(to_todoable(awardable), current_user)
+    if awardable.user_can_award?(current_user, name)
+      awardable.toggle_award_emoji(name, current_user)
 
-    render json: { ok: true }
+      todoable = to_todoable(awardable)
+      TodoService.new.new_award_emoji(todoable, current_user) if todoable
+
+      render json: { ok: true }
+    else
+      render json: { ok: false }
+    end
   end
 
   private
@@ -20,8 +26,10 @@ module ToggleAwardEmoji
     case awardable
     when Note
       awardable.noteable
-    else
+    when MergeRequest, Issue
       awardable
+    when Snippet
+      nil
     end
   end
 
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index cb82d62..b83c3a8 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -121,7 +121,17 @@ class GroupsController < Groups::ApplicationController
   end
 
   def group_params
-    params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock, :request_access_enabled)
+    params.require(:group).permit(
+      :avatar,
+      :description,
+      :lfs_enabled,
+      :name,
+      :path,
+      :public,
+      :request_access_enabled,
+      :share_with_group_lock,
+      :visibility_level
+    )
   end
 
   def load_events
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
index 7e8597a..256c41e 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -1,18 +1,17 @@
 class Import::BaseController < ApplicationController
   private
 
-  def get_or_create_namespace
+  def find_or_create_namespace(name, owner)
+    return current_user.namespace if name == owner
+    return current_user.namespace unless current_user.can_create_group?
+
     begin
-      namespace = Group.create!(name: @target_namespace, path: @target_namespace, owner: current_user)
+      name = params[:target_namespace].presence || name
+      namespace = Group.create!(name: name, path: name, owner: current_user)
       namespace.add_owner(current_user)
+      namespace
     rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
-      namespace = Namespace.find_by_path_or_name(@target_namespace)
-      unless current_user.can?(:create_projects, namespace)
-        @already_been_taken = true
-        return false
-      end
+      Namespace.find_by_path_or_name(name)
     end
-
-    namespace
   end
 end
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index 944c73d..6ea5474 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -35,23 +35,20 @@ class Import::BitbucketController < Import::BaseController
   end
 
   def create
-    @repo_id = params[:repo_id] || ""
-    repo = client.project(@repo_id.gsub("___", "/"))
-    @project_name = repo["slug"]
-
-    repo_owner = repo["owner"]
-    repo_owner = current_user.username if repo_owner == client.user["user"]["username"]
-    @target_namespace = params[:new_namespace].presence || repo_owner
-
-    namespace = get_or_create_namespace || (render and return)
+    @repo_id = params[:repo_id].to_s
+    repo = client.project(@repo_id.gsub('___', '/'))
+    @project_name = repo['slug']
+    @target_namespace = find_or_create_namespace(repo['owner'], client.user['user']['username'])
 
     unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user, access_params).execute
-      @access_denied = true
-      render
-      return
+      render 'deploy_key' and return
     end
 
-    @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute
+    if current_user.can?(:create_projects, @target_namespace)
+      @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute
+    else
+      render 'unauthorized'
+    end
   end
 
   private
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 9c1b0eb..ee7d498 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -40,15 +40,15 @@ class Import::GithubController < Import::BaseController
   def create
     @repo_id = params[:repo_id].to_i
     repo = client.repo(@repo_id)
-    @project_name = repo.name
-
-    repo_owner = repo.owner.login
-    repo_owner = current_user.username if repo_owner == client.user.login
-    @target_namespace = params[:new_namespace].presence || repo_owner
-
-    namespace = get_or_create_namespace || (render and return)
-
-    @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute
+    @project_name = params[:new_name].presence || repo.name
+    namespace_path = params[:target_namespace].presence || current_user.namespace_path
+    @target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
+
+    if current_user.can?(:create_projects, @target_namespace)
+      @project = Gitlab::GithubImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, access_params).execute
+    else
+      render 'unauthorized'
+    end
   end
 
   private
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 08130ee..73837ff 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -26,15 +26,14 @@ class Import::GitlabController < Import::BaseController
   def create
     @repo_id = params[:repo_id].to_i
     repo = client.project(@repo_id)
-    @project_name = repo["name"]
+    @project_name = repo['name']
+    @target_namespace = find_or_create_namespace(repo['namespace']['path'], client.user['username'])
 
-    repo_owner = repo["namespace"]["path"]
-    repo_owner = current_user.username if repo_owner == client.user["username"]
-    @target_namespace = params[:new_namespace].presence || repo_owner
-
-    namespace = get_or_create_namespace || (render and return)
-
-    @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute
+    if current_user.can?(:create_projects, @target_namespace)
+      @project = Gitlab::GitlabImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute
+    else
+      render 'unauthorized'
+    end
   end
 
   private
diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb
deleted file mode 100644
index a4c4ad2..0000000
--- a/app/controllers/import/gitorious_controller.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-class Import::GitoriousController < Import::BaseController
-  before_action :verify_gitorious_import_enabled
-
-  def new
-    redirect_to client.authorize_url(callback_import_gitorious_url)
-  end
-
-  def callback
-    session[:gitorious_repos] = params[:repos]
-    redirect_to status_import_gitorious_path
-  end
-
-  def status
-    @repos = client.repos
-
-    @already_added_projects = current_user.created_projects.where(import_type: "gitorious")
-    already_added_projects_names = @already_added_projects.pluck(:import_source)
-
-    @repos.reject! { |repo| already_added_projects_names.include? repo.full_name }
-  end
-
-  def jobs
-    jobs = current_user.created_projects.where(import_type: "gitorious").to_json(only: [:id, :import_status])
-    render json: jobs
-  end
-
-  def create
-    @repo_id = params[:repo_id]
-    repo = client.repo(@repo_id)
-    @target_namespace = params[:new_namespace].presence || repo.namespace
-    @project_name = repo.name
-
-    namespace = get_or_create_namespace || (render and return)
-
-    @project = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, current_user).execute
-  end
-
-  private
-
-  def client
-    @client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos])
-  end
-
-  def verify_gitorious_import_enabled
-    render_404 unless gitorious_import_enabled?
-  end
-end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 014b9b4..34d5d99 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -11,7 +11,8 @@ class JwtController < ApplicationController
     service = SERVICES[params[:service]]
     return head :not_found unless service
 
-    result = service.new(@project, @user, auth_params).execute
+    result = service.new(@authentication_result.project, @authentication_result.actor, auth_params).
+      execute(authentication_abilities: @authentication_result.authentication_abilities || [])
 
     render json: result, status: result[:http_status]
   end
@@ -19,31 +20,26 @@ class JwtController < ApplicationController
   private
 
   def authenticate_project_or_user
-    authenticate_with_http_basic do |login, password|
-      # if it's possible we first try to authenticate project with login and password
-      @project = authenticate_project(login, password)
-      return if @project
+    @authentication_result = Gitlab::Auth::Result.new
 
-      @user = authenticate_user(login, password)
-      return if @user
+    authenticate_with_http_basic do |login, password|
+      @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
 
-      render_403
+      render_403 unless @authentication_result.success? &&
+        (@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
     end
+  rescue Gitlab::Auth::MissingPersonalTokenError
+    render_missing_personal_token
   end
 
-  def auth_params
-    params.permit(:service, :scope, :account, :client_id)
+  def render_missing_personal_token
+    render plain: "HTTP Basic: Access denied\n" \
+                  "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
+                  "You can generate one at #{profile_personal_access_tokens_url}",
+           status: 401
   end
 
-  def authenticate_project(login, password)
-    if login == 'gitlab-ci-token'
-      Project.find_by(builds_enabled: true, runners_token: password)
-    end
-  end
-
-  def authenticate_user(login, password)
-    user = Gitlab::Auth.find_with_user_password(login, password)
-    Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
-    user
+  def auth_params
+    params.permit(:service, :scope, :account, :client_id)
   end
 end
diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb
index 5a94dcb..83eec1b 100644
--- a/app/controllers/namespaces_controller.rb
+++ b/app/controllers/namespaces_controller.rb
@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController
 
     if user
       redirect_to user_path(user)
-    elsif group && can?(current_user, :read_group, namespace)
+    elsif group && can?(current_user, :read_group, group)
       redirect_to group_path(group)
     elsif current_user.nil?
       authenticate_user!
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 91315a0..b2ff36f 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -88,6 +88,6 @@ class Projects::ApplicationController < ApplicationController
   end
 
   def builds_enabled
-    return render_404 unless @project.builds_enabled?
+    return render_404 unless @project.feature_available?(:builds, current_user)
   end
 end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 7241949..5922263 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -1,22 +1,25 @@
 class Projects::ArtifactsController < Projects::ApplicationController
+  include ExtractsPath
+
   layout 'project'
   before_action :authorize_read_build!
   before_action :authorize_update_build!, only: [:keep]
+  before_action :extract_ref_name_and_path
   before_action :validate_artifacts!
 
   def download
-    unless artifacts_file.file_storage?
-      return redirect_to artifacts_file.url
+    if artifacts_file.file_storage?
+      send_file artifacts_file.path, disposition: 'attachment'
+    else
+      redirect_to artifacts_file.url
     end
-
-    send_file artifacts_file.path, disposition: 'attachment'
   end
 
   def browse
     directory = params[:path] ? "#{params[:path]}/" : ''
     @entry = build.artifacts_metadata_entry(directory)
 
-    return render_404 unless @entry.exists?
+    render_404 unless @entry.exists?
   end
 
   def file
@@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController
     redirect_to namespace_project_build_path(project.namespace, project, build)
   end
 
+  def latest_succeeded
+    target_path = artifacts_action_path(@path, project, build)
+
+    if target_path
+      redirect_to(target_path)
+    else
+      render_404
+    end
+  end
+
   private
 
+  def extract_ref_name_and_path
+    return unless params[:ref_name_and_path]
+
+    @ref_name, @path = extract_ref(params[:ref_name_and_path])
+  end
+
   def validate_artifacts!
-    render_404 unless build.artifacts?
+    render_404 unless build && build.artifacts?
   end
 
   def build
-    @build ||= project.builds.find_by!(id: params[:build_id])
+    @build ||= build_from_id || build_from_ref
+  end
+
+  def build_from_id
+    project.builds.find_by(id: params[:build_id]) if params[:build_id]
+  end
+
+  def build_from_ref
+    return unless @ref_name
+
+    builds = project.latest_successful_builds_for(@ref_name)
+    builds.find_by(name: params[:job])
   end
 
   def artifacts_file
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 5962f74..ada7db3 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -4,7 +4,7 @@ class Projects::AvatarsController < Projects::ApplicationController
   before_action :authorize_admin_project!, only: [:destroy]
 
   def show
-    @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
+    @blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git)
     if @blob
       headers['X-Content-Type-Options'] = 'nosniff'
 
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index cdf9a04..b78cc65 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -38,12 +38,7 @@ class Projects::BlobController < Projects::ApplicationController
   end
 
   def update
-    if params[:file_path].present?
-      @previous_path = @path
-      @path = params[:file_path]
-      @commit_params[:file_path] = @path
-    end
-
+    @path = params[:file_path] if params[:file_path].present?
     after_edit_path =
       if from_merge_request && @target_branch == @ref
         diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
@@ -143,6 +138,8 @@ class Projects::BlobController < Projects::ApplicationController
           params[:file_name] = params[:file].original_filename
         end
         File.join(@path, params[:file_name])
+      elsif params[:file_path].present?
+        params[:file_path]
       else
         @path
       end
@@ -155,6 +152,7 @@ class Projects::BlobController < Projects::ApplicationController
     @commit_params = {
       file_path: @file_path,
       commit_message: params[:commit_message],
+      previous_path: @path,
       file_content: params[:content],
       file_content_encoding: params[:encoding],
       last_commit_sha: params[:last_commit_sha]
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
index 1a4f6b5..9404612 100644
--- a/app/controllers/projects/boards/issues_controller.rb
+++ b/app/controllers/projects/boards/issues_controller.rb
@@ -8,12 +8,15 @@ module Projects
         issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute
         issues = issues.page(params[:page])
 
-        render json: issues.as_json(
-          only: [:iid, :title, :confidential],
-          include: {
-            assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
-            labels:   { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
-          })
+        render json: {
+          issues: issues.as_json(
+            only: [:iid, :title, :confidential],
+            include: {
+              assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
+              labels:   { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
+            }),
+          size: issues.total_count
+        }
       end
 
       def update
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 48fe81b..2de8ada 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -15,6 +15,13 @@ class Projects::BranchesController < Projects::ApplicationController
       diverging_commit_counts = repository.diverging_commit_counts(branch)
       [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
     end
+
+    respond_to do |format|
+      format.html
+      format.json do
+        render json: @repository.branch_names
+      end
+    end
   end
 
   def recent
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 12195c3..3b2e35a 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -35,7 +35,11 @@ class Projects::BuildsController < Projects::ApplicationController
     respond_to do |format|
       format.html
       format.json do
-        render json: @build.to_json(methods: :trace_html)
+        render json: {
+          id: @build.id,
+          status: @build.status,
+          trace_html: @build.trace_html
+        }
       end
     end
   end
@@ -74,12 +78,12 @@ class Projects::BuildsController < Projects::ApplicationController
   def erase
     @build.erase(erased_by: current_user)
     redirect_to namespace_project_build_path(project.namespace, project, @build),
-                notice: "Build has been sucessfully erased!"
+                notice: "Build has been successfully erased!"
   end
 
   def raw
-    if @build.has_trace?
-      send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline'
+    if @build.has_trace_file?
+      send_file @build.trace_file_path, type: 'text/plain; charset=utf-8', disposition: 'inline'
     else
       render_404
     end
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
new file mode 100644
index 0000000..16a7b1f
--- /dev/null
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -0,0 +1,67 @@
+class Projects::CycleAnalyticsController < Projects::ApplicationController
+  include ActionView::Helpers::DateHelper
+  include ActionView::Helpers::TextHelper
+
+  before_action :authorize_read_cycle_analytics!
+
+  def show
+    @cycle_analytics = CycleAnalytics.new(@project, from: parse_start_date)
+
+    respond_to do |format|
+      format.html
+      format.json { render json: cycle_analytics_json }
+    end
+  end
+
+  private
+
+  def parse_start_date
+    case cycle_analytics_params[:start_date]
+    when '30' then 30.days.ago
+    when '90' then 90.days.ago
+    else 90.days.ago
+    end
+  end
+
+  def cycle_analytics_params
+    return {} unless params[:cycle_analytics].present?
+
+    { start_date: params[:cycle_analytics][:start_date] }
+  end
+
+  def cycle_analytics_json
+    cycle_analytics_view_data = [[:issue, "Issue", "Time before an issue gets scheduled"],
+                                 [:plan, "Plan", "Time before an issue starts implementation"],
+                                 [:code, "Code", "Time until first merge request"],
+                                 [:test, "Test", "Total test time for all commits/merges"],
+                                 [:review, "Review", "Time between merge request creation and merge/close"],
+                                 [:staging, "Staging", "From merge request merge until deploy to production"],
+                                 [:production, "Production", "From issue creation until deploy to production"]]
+
+    stats = cycle_analytics_view_data.reduce([]) do |stats, (stage_method, stage_text, stage_description)|
+      value = @cycle_analytics.send(stage_method).presence
+
+      stats << {
+        title: stage_text,
+        description: stage_description,
+        value: value && !value.zero? ? distance_of_time_in_words(value) : nil
+      }
+      stats
+    end
+
+    issues = @cycle_analytics.summary.new_issues
+    commits = @cycle_analytics.summary.commits
+    deploys = @cycle_analytics.summary.deploys
+
+    summary = [
+      { title: "New Issue".pluralize(issues), value: issues },
+      { title: "Commit".pluralize(commits), value: commits },
+      { title: "Deploy".pluralize(deploys), value: deploys }
+    ]
+
+    {
+      summary: summary,
+      stats: stats
+    }
+  end
+end
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index b2e8733..d174e11 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -38,6 +38,6 @@ class Projects::DiscussionsController < Projects::ApplicationController
   end
 
   def module_enabled
-    render_404 unless @project.merge_requests_enabled
+    render_404 unless @project.feature_available?(:merge_requests, current_user)
   end
 end
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index a5b4031..383e184 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -4,7 +4,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   include ActionController::HttpAuthentication::Basic
   include KerberosSpnegoHelper
 
-  attr_reader :user
+  attr_reader :authentication_result
+
+  delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
+
+  alias_method :user, :actor
 
   # Git clients will not know what authenticity token to send along
   skip_before_action :verify_authenticity_token
@@ -15,32 +19,25 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   private
 
   def authenticate_user
+    @authentication_result = Gitlab::Auth::Result.new
+
     if project && project.public? && download_request?
       return # Allow access
     end
 
     if allow_basic_auth? && basic_auth_provided?
       login, password = user_name_and_password(request)
-      auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
-
-      if auth_result.type == :ci && download_request?
-        @ci = true
-      elsif auth_result.type == :oauth && !download_request?
-        # Not allowed
-      elsif auth_result.type == :missing_personal_token
-        render_missing_personal_token
-        return # Render above denied access, nothing left to do
-      else
-        @user = auth_result.user
-      end
 
-      if ci? || user
+      if handle_basic_authentication(login, password)
         return # Allow access
       end
     elsif allow_kerberos_spnego_auth? && spnego_provided?
-      @user = find_kerberos_user
+      kerberos_user = find_kerberos_user
+
+      if kerberos_user
+        @authentication_result = Gitlab::Auth::Result.new(
+          kerberos_user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities)
 
-      if user
         send_final_spnego_response
         return # Allow access
       end
@@ -48,6 +45,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
 
     send_challenges
     render plain: "HTTP Basic: Access denied\n", status: 401
+  rescue Gitlab::Auth::MissingPersonalTokenError
+    render_missing_personal_token
   end
 
   def basic_auth_provided?
@@ -114,7 +113,44 @@ class Projects::GitHttpClientController < Projects::ApplicationController
     render plain: 'Not Found', status: :not_found
   end
 
+  def handle_basic_authentication(login, password)
+    @authentication_result = Gitlab::Auth.find_for_git_client(
+      login, password, project: project, ip: request.ip)
+
+    return false unless @authentication_result.success?
+
+    if download_request?
+      authentication_has_download_access?
+    else
+      authentication_has_upload_access?
+    end
+  end
+
   def ci?
-    @ci.present?
+    authentication_result.ci?(project)
+  end
+
+  def lfs_deploy_token?
+    authentication_result.lfs_deploy_token?(project)
+  end
+
+  def authentication_has_download_access?
+    has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code)
+  end
+
+  def authentication_has_upload_access?
+    has_authentication_ability?(:push_code)
+  end
+
+  def has_authentication_ability?(capability)
+    (authentication_abilities || []).include?(capability)
+  end
+
+  def authentication_project
+    authentication_result.project
+  end
+
+  def verify_workhorse_api!
+    Gitlab::Workhorse.verify_api_request!(request.headers)
   end
 end
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index b4373ef..662d38b 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -1,6 +1,8 @@
 # This file should be identical in GitLab Community Edition and Enterprise Edition
 
 class Projects::GitHttpController < Projects::GitHttpClientController
+  before_action :verify_workhorse_api!
+
   # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
   # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
   def info_refs
@@ -56,6 +58,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
   end
 
   def render_ok
+    set_workhorse_internal_api_content_type
     render json: Gitlab::Workhorse.git_http_ok(repository, user)
   end
 
@@ -83,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
   end
 
   def access
-    @access ||= Gitlab::GitAccess.new(user, project, 'http')
+    @access ||= Gitlab::GitAccess.new(user, project, 'http', authentication_abilities: authentication_abilities)
   end
 
   def access_check
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index b562404..0ae8ff9 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -59,6 +59,7 @@ class Projects::HooksController < Projects::ApplicationController
       :pipeline_events,
       :enable_ssl_verification,
       :issues_events,
+      :confidential_issues_events,
       :merge_requests_events,
       :note_events,
       :push_events,
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c519772..3eb13a1 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -20,24 +20,12 @@ class Projects::IssuesController < Projects::ApplicationController
   # Allow modify issue
   before_action :authorize_update_issue!, only: [:edit, :update]
 
-  # Allow issues bulk update
-  before_action :authorize_admin_issues!, only: [:bulk_update]
-
   respond_to :html
 
   def index
-    terms = params['issue_search']
     @issues = issues_collection
-
-    if terms.present?
-      if terms =~ /\A#(\d+)\z/
-        @issues = @issues.where(iid: $1)
-      else
-        @issues = @issues.full_search(terms)
-      end
-    end
-
     @issues = @issues.page(params[:page])
+
     @labels = @project.labels.where(title: params[:label_name])
 
     respond_to do |format|
@@ -125,6 +113,10 @@ class Projects::IssuesController < Projects::ApplicationController
         render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
       end
     end
+
+  rescue ActiveRecord::StaleObjectError
+    @conflict = true
+    render :edit
   end
 
   def referenced_merge_requests
@@ -164,16 +156,6 @@ class Projects::IssuesController < Projects::ApplicationController
     end
   end
 
-  def bulk_update
-    result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
-
-    respond_to do |format|
-      format.json do
-        render json: { notice: "#{result[:count]} issues updated" }
-      end
-    end
-  end
-
   protected
 
   def issue
@@ -197,7 +179,7 @@ class Projects::IssuesController < Projects::ApplicationController
   end
 
   def module_enabled
-    return render_404 unless @project.issues_enabled && @project.default_issues_tracker?
+    return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker?
   end
 
   def redirect_to_external_issue_tracker
@@ -230,20 +212,7 @@ class Projects::IssuesController < Projects::ApplicationController
   def issue_params
     params.require(:issue).permit(
       :title, :assignee_id, :position, :description, :confidential,
-      :milestone_id, :due_date, :state_event, :task_num, label_ids: []
-    )
-  end
-
-  def bulk_update_params
-    params.require(:update).permit(
-      :issues_ids,
-      :assignee_id,
-      :milestone_id,
-      :state_event,
-      :subscription_event,
-      label_ids: [],
-      add_label_ids: [],
-      remove_label_ids: []
+      :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: []
     )
   end
 end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 0ca6756..28fa4a5 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -99,7 +99,7 @@ class Projects::LabelsController < Projects::ApplicationController
   protected
 
   def module_enabled
-    unless @project.issues_enabled || @project.merge_requests_enabled
+    unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
       return render_404
     end
   end
diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb
index 69066cb..9005b10 100644
--- a/app/controllers/projects/lfs_storage_controller.rb
+++ b/app/controllers/projects/lfs_storage_controller.rb
@@ -3,6 +3,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
 
   before_action :require_lfs_enabled!
   before_action :lfs_check_access!
+  before_action :verify_workhorse_api!, only: [:upload_authorize]
 
   def download
     lfs_object = LfsObject.find_by_oid(oid)
@@ -15,14 +16,8 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
   end
 
   def upload_authorize
-    render(
-      json: {
-        StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload",
-        LfsOid: oid,
-        LfsSize: size,
-      },
-      content_type: 'application/json; charset=utf-8'
-    )
+    set_workhorse_internal_api_content_type
+    render json: Gitlab::Workhorse.lfs_upload_ok(oid, size)
   end
 
   def upload_finalize
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d3fe441..935417d 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -31,17 +31,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts]
 
   def index
-    terms = params['issue_search']
     @merge_requests = merge_requests_collection
-
-    if terms.present?
-      if terms =~ /\A[#!](\d+)\z/
-        @merge_requests = @merge_requests.where(iid: $1)
-      else
-        @merge_requests = @merge_requests.full_search(terms)
-      end
-    end
-
     @merge_requests = @merge_requests.page(params[:page])
     @merge_requests = @merge_requests.preload(:target_project)
 
@@ -83,12 +73,33 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   def diffs
     apply_diff_view_cookie!
 
-    @merge_request_diff = @merge_request.merge_request_diff
+    @merge_request_diff =
+      if params[:diff_id]
+        @merge_request.merge_request_diffs.find(params[:diff_id])
+      else
+        @merge_request.merge_request_diff
+      end
+
+    @merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff
+    @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
+
+    if params[:start_sha].present?
+      @start_sha = params[:start_sha]
+      @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
+
+      unless @start_version
+        render_404
+      end
+    end
 
     respond_to do |format|
       format.html { define_discussion_vars }
       format.json do
-        @diffs = @merge_request.diffs(diff_options)
+        if @start_sha
+          compared_diff_version
+        else
+          original_diff_version
+        end
 
         render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
       end
@@ -258,6 +269,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     else
       render "edit"
     end
+  rescue ActiveRecord::StaleObjectError
+    @conflict = true
+    render :edit
   end
 
   def remove_wip
@@ -400,21 +414,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   end
 
   def module_enabled
-    return render_404 unless @project.merge_requests_enabled
+    return render_404 unless @project.feature_available?(:merge_requests, current_user)
   end
 
   def validates_merge_request
-    # If source project was removed (Ex. mr from fork to origin)
-    return invalid_mr unless @merge_request.source_project
+    # If source project was removed and merge request for some reason
+    # wasn't close (Ex. mr from fork to origin)
+    return invalid_mr if !@merge_request.source_project && @merge_request.open?
 
     # Show git not found page
     # if there is no saved commits between source & target branch
     if @merge_request.commits.blank?
       # and if target branch doesn't exist
       return invalid_mr unless @merge_request.target_branch_exists?
-
-      # or if source branch doesn't exist
-      return invalid_mr unless @merge_request.source_branch_exists?
     end
   end
 
@@ -493,7 +505,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
       :title, :assignee_id, :source_project_id, :source_branch,
       :target_project_id, :target_branch, :milestone_id,
       :state_event, :description, :task_num, :force_remove_source_branch,
-      label_ids: []
+      :lock_version, label_ids: []
     )
   end
 
@@ -516,4 +528,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
     @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
   end
+
+  def compared_diff_version
+    @diff_notes_disabled = true
+    @diffs = @merge_request_diff.compare_with(@start_sha).diffs(diff_options)
+  end
+
+  def original_diff_version
+    @diff_notes_disabled = !@merge_request_diff.latest?
+    @diffs = @merge_request_diff.diffs(diff_options)
+  end
 end
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index da2892b..ff63f22 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -106,7 +106,7 @@ class Projects::MilestonesController < Projects::ApplicationController
   end
 
   def module_enabled
-    unless @project.issues_enabled || @project.merge_requests_enabled
+    unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
       return render_404
     end
   end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index b0c72cf..371cc37 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -7,11 +7,10 @@ class Projects::PipelinesController < Projects::ApplicationController
 
   def index
     @scope = params[:scope]
-    all_pipelines = project.pipelines
-    @pipelines_count = all_pipelines.count
-    @running_or_pending_count = all_pipelines.running_or_pending.count
-    @pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope)
-    @pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30)
+    @pipelines = PipelinesFinder.new(project).execute(scope: @scope).page(params[:page]).per(30)
+
+    @running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count
+    @pipelines_count = PipelinesFinder.new(project).execute.count
   end
 
   def new
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 6a227d8..97e6e94 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -20,9 +20,8 @@ class Projects::ServicesController < Projects::ApplicationController
   def update
     if @service.update_attributes(service_params[:service])
       redirect_to(
-        edit_namespace_project_service_path(@project.namespace, @project,
-                                            @service.to_param, notice:
-                                            'Successfully updated.')
+        edit_namespace_project_service_path(@project.namespace, @project, @service.to_param),
+        notice: 'Successfully updated.'
       )
     else
       render 'edit'
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 6d0a7ee..e290a0e 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -1,6 +1,8 @@
 class Projects::SnippetsController < Projects::ApplicationController
+  include ToggleAwardEmoji
+
   before_action :module_enabled
-  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
+  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji]
 
   # Allow read any snippet
   before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
@@ -80,6 +82,7 @@ class Projects::SnippetsController < Projects::ApplicationController
   def snippet
     @snippet ||= @project.snippets.find(params[:id])
   end
+  alias_method :awardable, :snippet
 
   def authorize_read_project_snippet!
     return render_404 unless can?(current_user, :read_project_snippet, @snippet)
@@ -94,7 +97,7 @@ class Projects::SnippetsController < Projects::ApplicationController
   end
 
   def module_enabled
-    return render_404 unless @project.snippets_enabled
+    return render_404 unless @project.feature_available?(:snippets, current_user)
   end
 
   def snippet_params
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 8592579..6ea8ee6 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -1,4 +1,6 @@
 class Projects::TagsController < Projects::ApplicationController
+  include SortingHelper
+
   # Authorize
   before_action :require_non_empty_project
   before_action :authorize_download_code!
@@ -6,8 +8,10 @@ class Projects::TagsController < Projects::ApplicationController
   before_action :authorize_admin_project!, only: [:destroy]
 
   def index
-    @sort = params[:sort] || 'name'
-    @tags = @repository.tags_sorted_by(@sort)
+    params[:sort] = params[:sort].presence || 'name'
+
+    @sort = params[:sort]
+    @tags = TagsFinder.new(@repository, params).execute
     @tags = Kaminari.paginate_array(@tags).page(params[:page])
 
     @releases = project.releases.where(tag: @tags.map(&:name))
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 2a6385c..eaa38fa 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -5,7 +5,7 @@ class ProjectsController < Projects::ApplicationController
   before_action :project, except: [:new, :create]
   before_action :repository, except: [:new, :create]
   before_action :assign_ref_vars, only: [:show], if: :repo_exists?
-  before_action :assign_tree_vars, only: [:show], if: [:repo_exists?, :project_view_files?]
+  before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
 
   # Authorize
   before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
@@ -303,13 +303,23 @@ class ProjectsController < Projects::ApplicationController
   end
 
   def project_params
+    project_feature_attributes =
+      {
+        project_feature_attributes:
+          [
+            :issues_access_level, :builds_access_level,
+            :wiki_access_level, :merge_requests_access_level, :snippets_access_level
+          ]
+      }
+
     params.require(:project).permit(
       :name, :path, :description, :issues_tracker, :tag_list, :runners_token,
-      :issues_enabled, :merge_requests_enabled, :snippets_enabled, :container_registry_enabled,
+      :container_registry_enabled,
       :issues_tracker_id, :default_branch,
-      :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
-      :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
-      :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled
+      :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
+      :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
+      :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled,
+      :lfs_enabled, project_feature_attributes
     )
   end
 
@@ -332,11 +342,4 @@ class ProjectsController < Projects::ApplicationController
   def get_id
     project.repository.root_ref
   end
-
-  # ExtractsPath will set @id = project.path on the show route, but it has to be the
-  # branch name for the tree view to work correctly.
-  def assign_tree_vars
-    @id = get_id
-    tree
-  end
 end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 61517d2..d01e0de 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -6,8 +6,6 @@ class SearchController < ApplicationController
   layout 'search'
 
   def show
-    return if params[:search].nil? || params[:search].blank?
-
     if params[:project_id].present?
       @project = Project.find_by(id: params[:project_id])
       @project = nil unless can?(current_user, :download_code, @project)
@@ -18,6 +16,8 @@ class SearchController < ApplicationController
       @group = nil unless can?(current_user, :read_group, @group)
     end
 
+    return if params[:search].nil? || params[:search].blank?
+
     @search_term = params[:search]
 
     @scope = params[:scope]
diff --git a/app/controllers/sent_notifications_controller.rb b/app/controllers/sent_notifications_controller.rb
index 7271c93..3085ff3 100644
--- a/app/controllers/sent_notifications_controller.rb
+++ b/app/controllers/sent_notifications_controller.rb
@@ -3,12 +3,19 @@ class SentNotificationsController < ApplicationController
 
   def unsubscribe
     @sent_notification = SentNotification.for(params[:id])
+
     return render_404 unless @sent_notification && @sent_notification.unsubscribable?
+    return unsubscribe_and_redirect if current_user || params[:force]
+  end
 
+  private
+
+  def unsubscribe_and_redirect
     noteable = @sent_notification.noteable
     noteable.unsubscribe(@sent_notification.recipient)
 
     flash[:notice] = "You have been unsubscribed from this thread."
+
     if current_user
       case noteable
       when Issue
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 2a17c1f..d198782 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,4 +1,6 @@
 class SnippetsController < ApplicationController
+  include ToggleAwardEmoji
+
   before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
 
   # Allow read snippet
@@ -85,6 +87,7 @@ class SnippetsController < ApplicationController
                    PersonalSnippet.find(params[:id])
                  end
   end
+  alias_method :awardable, :snippet
 
   def authorize_read_snippet!
     authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index a996324..a4bedb3 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -73,7 +73,7 @@ class UsersController < ApplicationController
 
   def calendar
     calendar = contributions_calendar
-    @timestamps = calendar.timestamps
+    @activity_dates = calendar.activity_dates
 
     render 'calendar', layout: false
   end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 33daac0..8f9ef8f 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -64,7 +64,7 @@ class IssuableFinder
     if project?
       @project = Project.find(params[:project_id])
 
-      unless Ability.abilities.allowed?(current_user, :read_project, @project)
+      unless Ability.allowed?(current_user, :read_project, @project)
         @project = nil
       end
     else
@@ -216,7 +216,14 @@ class IssuableFinder
   end
 
   def by_search(items)
-    items = items.search(search) if search
+    if search
+      items =
+        if search =~ iid_pattern
+          items.where(iid: $~[:iid])
+        else
+          items.full_search(search)
+        end
+    end
 
     items
   end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index c2befa5..be00a21 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -25,4 +25,8 @@ class IssuesFinder < IssuableFinder
   def init_collection
     Issue.visible_to_user(current_user)
   end
+
+  def iid_pattern
+    @iid_pattern ||= %r{\A#{Regexp.escape(Issue.reference_prefix)}(?<iid>\d+)\z}
+  end
 end
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index b258216..3b254e7 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -19,4 +19,14 @@ class MergeRequestsFinder < IssuableFinder
   def klass
     MergeRequest
   end
+
+  private
+
+  def iid_pattern
+    @iid_pattern ||= %r{\A[
+      #{Regexp.escape(MergeRequest.reference_prefix)}
+      #{Regexp.escape(Issue.reference_prefix)}
+      ](?<iid>\d+)\z
+    }x
+  end
 end
diff --git a/app/finders/move_to_project_finder.rb b/app/finders/move_to_project_finder.rb
index 3334b85..79eb455 100644
--- a/app/finders/move_to_project_finder.rb
+++ b/app/finders/move_to_project_finder.rb
@@ -1,4 +1,6 @@
 class MoveToProjectFinder
+  PAGE_SIZE = 50
+
   def initialize(user)
     @user = user
   end
@@ -8,6 +10,10 @@ class MoveToProjectFinder
     projects = projects.search(search) if search.present?
     projects = projects.excluding_project(from_project)
 
+    # infinite scroll using offset
+    projects = projects.where('projects.id < ?', offset_id) if offset_id.present?
+    projects = projects.limit(PAGE_SIZE)
+
     # to ask for Project#name_with_namespace
     projects.includes(namespace: :owner)
   end
diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb
index 641fbf8..32aea75 100644
--- a/app/finders/pipelines_finder.rb
+++ b/app/finders/pipelines_finder.rb
@@ -1,30 +1,34 @@
 class PipelinesFinder
-  attr_reader :project
+  attr_reader :project, :pipelines
 
   def initialize(project)
     @project = project
+    @pipelines = project.pipelines
   end
 
-  def execute(pipelines, scope)
-    case scope
-    when 'running'
-      pipelines.running_or_pending
-    when 'branches'
-      from_ids(pipelines, ids_for_ref(pipelines, branches))
-    when 'tags'
-      from_ids(pipelines, ids_for_ref(pipelines, tags))
-    else
-      pipelines
-    end
+  def execute(scope: nil)
+    scoped_pipelines =
+      case scope
+      when 'running'
+        pipelines.running_or_pending
+      when 'branches'
+        from_ids(ids_for_ref(branches))
+      when 'tags'
+        from_ids(ids_for_ref(tags))
+      else
+        pipelines
+      end
+
+    scoped_pipelines.order(id: :desc)
   end
 
   private
 
-  def ids_for_ref(pipelines, refs)
+  def ids_for_ref(refs)
     pipelines.where(ref: refs).group(:ref).select('max(id)')
   end
 
-  def from_ids(pipelines, ids)
+  def from_ids(ids)
     pipelines.unscoped.where(id: ids)
   end
 
diff --git a/app/finders/tags_finder.rb b/app/finders/tags_finder.rb
new file mode 100644
index 0000000..b474f08
--- /dev/null
+++ b/app/finders/tags_finder.rb
@@ -0,0 +1,29 @@
+class TagsFinder
+  def initialize(repository, params)
+    @repository = repository
+    @params = params
+  end
+
+  def execute
+    tags = @repository.tags_sorted_by(sort)
+    filter_by_name(tags)
+  end
+
+  private
+
+  def sort
+    @params[:sort].presence
+  end
+
+  def search
+    @params[:search].presence
+  end
+
+  def filter_by_name(tags)
+    if search
+      tags.select { |tag| tag.name.include?(search) }
+    else
+      tags
+    end
+  end
+end
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 06b3e8a..a93a63b 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -83,7 +83,7 @@ class TodosFinder
     if project?
       @project = Project.find(params[:project_id])
 
-      unless Ability.abilities.allowed?(current_user, :read_project, @project)
+      unless Ability.allowed?(current_user, :read_project, @project)
         @project = nil
       end
     else
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f3733b0..1df430e 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -110,7 +110,7 @@ module ApplicationHelper
     project = event.project
 
     # Skip if project repo is empty or MR disabled
-    return false unless project && !project.empty_repo? && project.merge_requests_enabled
+    return false unless project && !project.empty_repo? && project.feature_available?(:merge_requests, current_user)
 
     # Skip if user already created appropriate MR
     return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
@@ -249,7 +249,7 @@ module ApplicationHelper
       milestone_title: params[:milestone_title],
       assignee_id: params[:assignee_id],
       author_id: params[:author_id],
-      issue_search: params[:issue_search],
+      search: params[:search],
       label_name: params[:label_name]
     }
 
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index aa8acbe..df41473 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -14,7 +14,8 @@ module AvatarsHelper
       avatar_icon(options[:user] || options[:user_email], avatar_size),
       class: "avatar has-tooltip hidden-xs s#{avatar_size}",
       alt: "#{user_name}'s avatar",
-      title: user_name
+      title: user_name,
+      data: { container: 'body' }
     )
 
     if options[:user]
diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb
new file mode 100644
index 0000000..aa134ce
--- /dev/null
+++ b/app/helpers/award_emoji_helper.rb
@@ -0,0 +1,9 @@
+module AwardEmojiHelper
+  def toggle_award_url(awardable)
+    if @project
+      url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
+    else
+      url_for([:toggle_award_emoji, awardable])
+    end
+  end
+end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index bb285a1..639deb7 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -25,6 +25,11 @@ module CiStatusHelper
     end
   end
 
+  def ci_status_for_statuseable(subject)
+    status = subject.try(:status) || 'not found'
+    status.humanize
+  end
+
   def ci_icon_for_status(status)
     icon_name =
       case status
@@ -41,7 +46,7 @@ module CiStatusHelper
       when 'play'
         'icon_play'
       when 'created'
-        'icon_status_pending'
+        'icon_status_created'
       else
         'icon_status_cancel'
       end
@@ -66,10 +71,10 @@ module CiStatusHelper
       Ci::Runner.shared.blank?
   end
 
-  def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '')
+  def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body')
     klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
     title = "#{type.titleize}: #{ci_label_for_status(status)}"
-    data = { toggle: 'tooltip', placement: tooltip_placement }
+    data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
 
     if path
       link_to ci_icon_for_status(status), path,
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index f1dc906..aa54ee0 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -3,7 +3,7 @@ module CompareHelper
     from.present? &&
       to.present? &&
       from != to &&
-      project.merge_requests_enabled &&
+      project.feature_available?(:merge_requests, current_user) &&
       project.repository.branch_names.include?(from) &&
       project.repository.branch_names.include?(to)
   end
diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb
index 0968495..8ab3943 100644
--- a/app/helpers/git_helper.rb
+++ b/app/helpers/git_helper.rb
@@ -2,4 +2,8 @@ module GitHelper
   def strip_gpg_signature(text)
     text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
   end
+
+  def short_sha(text)
+    Commit.truncate_sha(text)
+  end
 end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 5386dda..670a7ca 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -46,6 +46,10 @@ module GitlabRoutingHelper
     namespace_project_environments_path(project.namespace, project, *args)
   end
 
+  def project_cycle_analytics_path(project, *args)
+    namespace_project_cycle_analytics_path(project.namespace, project, *args)
+  end
+
   def project_builds_path(project, *args)
     namespace_project_builds_path(project.namespace, project, *args)
   end
@@ -66,6 +70,10 @@ module GitlabRoutingHelper
     namespace_project_runner_path(@project.namespace, @project, runner, *args)
   end
 
+  def environment_path(environment, *args)
+    namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+  end
+
   def issue_path(entity, *args)
     namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
   end
@@ -98,6 +106,14 @@ module GitlabRoutingHelper
     end
   end
 
+  def toggle_award_emoji_personal_snippet_path(*args)
+    toggle_award_emoji_snippet_path(*args)
+  end
+
+  def toggle_award_emoji_namespace_project_project_snippet_path(*args)
+    toggle_award_emoji_namespace_project_snippet_path(*args)
+  end
+
   ## Members
   def project_members_url(project, *args)
     namespace_project_project_members_url(project.namespace, project)
@@ -149,4 +165,20 @@ module GitlabRoutingHelper
   def resend_invite_group_member_path(group_member, *args)
     resend_invite_group_group_member_path(group_member.source, group_member)
   end
+
+  # Artifacts
+
+  def artifacts_action_path(path, project, build)
+    action, path_params = path.split('/', 2)
+    args = [project.namespace, project, build, path_params]
+
+    case action
+    when 'download'
+      download_namespace_project_build_artifacts_path(*args)
+    when 'browse'
+      browse_namespace_project_build_artifacts_path(*args)
+    when 'file'
+      file_namespace_project_build_artifacts_path(*args)
+    end
+  end
 end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index b9211e8..ab880ed 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -23,4 +23,29 @@ module GroupsHelper
       full_title
     end
   end
+
+  def projects_lfs_status(group)
+    lfs_status =
+      if group.lfs_enabled?
+        group.projects.select(&:lfs_enabled?).size
+      else
+        group.projects.reject(&:lfs_enabled?).size
+      end
+
+    size = group.projects.size
+
+    if lfs_status == size
+      'for all projects'
+    else
+      "for #{lfs_status} out of #{pluralize(size, 'project')}"
+    end
+  end
+
+  def group_lfs_status(group)
+    status = group.lfs_enabled? ? 'enabled' : 'disabled'
+
+    content_tag(:span, class: "lfs-#{status}") do
+      "#{status.humanize} #{projects_lfs_status(group)}"
+    end
+  end
 end
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index 109bc1a..021d2b1 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -1,4 +1,9 @@
 module ImportHelper
+  def import_project_target(owner, name)
+    namespace = current_user.can_create_group? ? owner : current_user.namespace_path
+    "#{namespace}/#{name}"
+  end
+
   def github_project_link(path_with_namespace)
     link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank'
   end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index b9baeb1..5c04bba 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -49,6 +49,19 @@ module IssuablesHelper
     end
   end
 
+  def project_dropdown_label(project_id, default_label)
+    return default_label if project_id.nil?
+    return "Any project" if project_id == "0"
+
+    project = Project.find_by(id: project_id)
+
+    if project
+      project.name_with_namespace
+    else
+      default_label
+    end
+  end
+
   def milestone_dropdown_label(milestone_title, default_label = "Milestone")
     if milestone_title == Milestone::Upcoming.name
       milestone_title = Milestone::Upcoming.title
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 2e82b44..8b212b0 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -114,9 +114,17 @@ module IssuesHelper
   end
 
   def award_user_list(awards, current_user)
-    awards.map do |award|
-      award.user == current_user ? 'me' : award.user.name
-    end.join(', ')
+    names = awards.map do |award|
+      award.user == current_user ? 'You' : award.user.name
+    end
+
+    # Take first 9 OR current user + first 9
+    current_user_name = names.delete('You')
+    names = names.first(9).insert(0, current_user_name).compact
+
+    names << "#{awards.size - names.size} more." if awards.size > names.size
+
+    names.to_sentence
   end
 
   def award_active_class(awards, current_user)
diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb
index eb651e3..c15ecc8 100644
--- a/app/helpers/lfs_helper.rb
+++ b/app/helpers/lfs_helper.rb
@@ -23,11 +23,23 @@ module LfsHelper
   end
 
   def lfs_download_access?
-    project.public? || ci? || (user && user.can?(:download_code, project))
+    return false unless project.lfs_enabled?
+
+    project.public? || ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code?
+  end
+
+  def user_can_download_code?
+    has_authentication_ability?(:download_code) && can?(user, :download_code, project)
+  end
+
+  def build_can_download_code?
+    has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project)
   end
 
   def lfs_upload_access?
-    user && user.can?(:push_code, project)
+    return false unless project.lfs_enabled?
+
+    has_authentication_ability?(:push_code) && can?(user, :push_code, project)
   end
 
   def render_lfs_forbidden
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index db6e731..8abe786 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -98,6 +98,16 @@ module MergeRequestsHelper
   end
 
   def merge_request_button_visibility(merge_request, closed)
-    return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?)
+    return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
+  end
+
+  def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil)
+    diffs_namespace_project_merge_request_path(
+      project.namespace, project, merge_request,
+      diff_id: merge_request_diff.id, start_sha: start_sha)
+  end
+
+  def version_index(merge_request_diff)
+    @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff)
   end
 end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 94c6b54..e0b8dc1 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,6 +1,9 @@
 module NamespacesHelper
-  def namespaces_options(selected = :current_user, display_path: false)
+  def namespaces_options(selected = :current_user, display_path: false, extra_group: nil)
     groups = current_user.owned_groups + current_user.masters_groups
+
+    groups << extra_group if extra_group && !Group.exists?(name: extra_group.name)
+
     users = [current_user.namespace]
 
     data_attr_group = { 'data-options-parent' => 'groups' }
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 6c1cc6e..df87fac 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,21 +1,7 @@
 module NavHelper
-  def nav_menu_collapsed?
-    cookies[:collapsed_nav] == 'true'
-  end
-
-  def nav_sidebar_class
-    if nav_menu_collapsed?
-      "sidebar-collapsed"
-    else
-      "sidebar-expanded"
-    end
-  end
-
   def page_sidebar_class
     if pinned_nav?
       "page-sidebar-expanded page-sidebar-pinned"
-    else
-      "page-sidebar-collapsed"
     end
   end
 
@@ -25,6 +11,7 @@ module NavHelper
       current_path?('merge_requests#commits') ||
       current_path?('merge_requests#builds') ||
       current_path?('merge_requests#conflicts') ||
+      current_path?('merge_requests#pipelines') ||
       current_path?('issues#show')
       if cookies[:collapsed_gutter] == 'true'
         "page-gutter right-sidebar-collapsed"
@@ -41,9 +28,7 @@ module NavHelper
     class_name << " with-horizontal-nav" if defined?(nav) && nav
 
     if pinned_nav?
-      class_name << " header-expanded header-pinned-nav"
-    else
-      class_name << " header-collapsed"
+      class_name << " header-sidebar-expanded header-sidebar-pinned"
     end
 
     class_name
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index da230f7..b0331f3 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -10,6 +10,10 @@ module NotesHelper
     Ability.can_edit_note?(current_user, note)
   end
 
+  def note_supports_slash_commands?(note)
+    Notes::SlashCommandsService.supported?(note, current_user)
+  end
+
   def noteable_json(noteable)
     {
       id: noteable.id,
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 356f27f..5647773 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -27,7 +27,7 @@ module ProjectsHelper
     author_html =  ""
 
     # Build avatar image tag
-    author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar]
+    author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]} #{opts[:avatar_class] if opts[:avatar_class]}", alt: '') if opts[:avatar]
 
     # Build name span tag
     if opts[:by_username]
@@ -61,7 +61,9 @@ module ProjectsHelper
     project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
 
     if current_user
-      project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" })
+      project_link << button_tag(type: 'button', class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) do
+        icon("chevron-down")
+      end
     end
 
     full_title = "#{namespace_link} / #{project_link}".html_safe
@@ -127,6 +129,19 @@ module ProjectsHelper
     current_user.recent_push(project_ids)
   end
 
+  def project_feature_access_select(field)
+    # Don't show option "everyone with access" if project is private
+    options = project_feature_options
+
+    if @project.private?
+      options.delete('Everyone with access')
+      highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED
+    end
+
+    options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field))
+    content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe
+  end
+
   private
 
   def get_project_nav_tabs(project, current_user)
@@ -187,6 +202,18 @@ module ProjectsHelper
     nav_tabs.flatten
   end
 
+  def project_lfs_status(project)
+    if project.lfs_enabled?
+      content_tag(:span, class: 'lfs-enabled') do
+        'Enabled'
+      end
+    else
+      content_tag(:span, class: 'lfs-disabled') do
+        'Disabled'
+      end
+    end
+  end
+
   def git_user_name
     if current_user
       current_user.name
@@ -400,4 +427,12 @@ module ProjectsHelper
 
     message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
   end
+
+  def project_feature_options
+    {
+      'Disabled' => ProjectFeature::DISABLED,
+      'Only team members' => ProjectFeature::PRIVATE,
+      'Everyone with access' => ProjectFeature::ENABLED
+    }
+  end
 end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index c019571..8a7446b 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -7,8 +7,10 @@ module SearchHelper
       projects_autocomplete(term)
     ].flatten
 
+    search_pattern = Regexp.new(Regexp.escape(term), "i")
+
     generic_results = project_autocomplete + default_autocomplete + help_autocomplete
-    generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") }
+    generic_results.select! { |result| result[:label] =~ search_pattern }
 
     [
       resources_results,
@@ -28,6 +30,37 @@ module SearchHelper
     "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
   end
 
+  def parse_search_result(result)
+    ref = nil
+    filename = nil
+    basename = nil
+    startline = 0
+
+    result.each_line.each_with_index do |line, index|
+      if line =~ /^.*:.*:\d+:/
+        ref, filename, startline = line.split(':')
+        startline = startline.to_i - index
+        extname = Regexp.escape(File.extname(filename))
+        basename = filename.sub(/#{extname}$/, '')
+        break
+      end
+    end
+
+    data = ""
+
+    result.each_line do |line|
+      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
+    end
+
+    OpenStruct.new(
+      filename: filename,
+      basename: basename,
+      ref: ref,
+      startline: startline,
+      data: data
+    )
+  end
+
   private
 
   # Autocomplete results for various settings pages
@@ -44,7 +77,7 @@ module SearchHelper
   def help_autocomplete
     [
       { category: "Help", label: "API Help",           url: help_page_path("api/README") },
-      { category: "Help", label: "Markdown Help",      url: help_page_path("markdown/markdown") },
+      { category: "Help", label: "Markdown Help",      url: help_page_path("user/markdown") },
       { category: "Help", label: "Permissions Help",   url: help_page_path("user/permissions") },
       { category: "Help", label: "Public Access Help", url: help_page_path("public_access/public_access") },
       { category: "Help", label: "Rake Tasks Help",    url: help_page_path("raketasks/README") },
diff --git a/app/helpers/sentry_helper.rb b/app/helpers/sentry_helper.rb
new file mode 100644
index 0000000..3d255df
--- /dev/null
+++ b/app/helpers/sentry_helper.rb
@@ -0,0 +1,9 @@
+module SentryHelper
+  def sentry_enabled?
+    Gitlab::Sentry.enabled?
+  end
+
+  def sentry_context
+    Gitlab::Sentry.context(current_user)
+  end
+end
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index 2dd0bf5..3d4abf7 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -8,7 +8,9 @@ module ServicesHelper
     when "note"
       "Event will be triggered when someone adds a comment"
     when "issue"
-      "Event will be triggered when an issue is created/updated/merged"
+      "Event will be triggered when an issue is created/updated/closed"
+    when "confidential_issue"
+      "Event will be triggered when a confidential issue is created/updated/closed"
     when "merge_request"
       "Event will be triggered when a merge request is created/updated/merged"
     when "build"
@@ -19,7 +21,7 @@ module ServicesHelper
   end
 
   def service_event_field_name(event)
-    event = event.pluralize if %w[merge_request issue].include?(event)
+    event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
     "#{event}_events"
   end
 end
diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb
new file mode 100644
index 0000000..d440edc
--- /dev/null
+++ b/app/helpers/sidekiq_helper.rb
@@ -0,0 +1,19 @@
+module SidekiqHelper
+  SIDEKIQ_PS_REGEXP = /\A
+    (?<pid>\d+)\s+
+    (?<cpu>[\d\.,]+)\s+
+    (?<mem>[\d\.,]+)\s+
+    (?<state>[DRSTWXZNLsl\+<]+)\s+
+    (?<start>.+)\s+
+    (?<command>sidekiq.*\])\s+
+    \z/x
+
+  def parse_sidekiq_ps(line)
+    match = line.match(SIDEKIQ_PS_REGEXP)
+    if match
+      match[1..6]
+    else
+      %w[? ? ? ? ? ?]
+    end
+  end
+end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 0a5a8eb..7e33a56 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,10 +1,10 @@
 module SnippetsHelper
-  def reliable_snippet_path(snippet)
+  def reliable_snippet_path(snippet, opts = nil)
     if snippet.project_id?
       namespace_project_snippet_path(snippet.project.namespace,
-                                     snippet.project, snippet)
+                                     snippet.project, snippet, opts)
     else
-      snippet_path(snippet)
+      snippet_path(snippet, opts)
     end
   end
 
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index fb85544..c0ec163 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -3,6 +3,16 @@ module TagsHelper
     "/tags/#{tag}"
   end
 
+  def filter_tags_path(options = {})
+    exist_opts = {
+      search: params[:search],
+      sort: params[:sort]
+    }
+
+    options = exist_opts.merge(options)
+    namespace_project_tags_path(@project.namespace, @project, @id, options)
+  end
+
   def tag_list(project)
     html = ''
     project.tag_list.each do |tag|
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 0465327..1e86f64 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -78,13 +78,11 @@ module TodosHelper
   end
 
   def todo_actions_options
-    actions = [
-      OpenStruct.new(id: '', title: 'Any Action'),
-      OpenStruct.new(id: Todo::ASSIGNED, title: 'Assigned'),
-      OpenStruct.new(id: Todo::MENTIONED, title: 'Mentioned')
+    [
+      { id: '', text: 'Any Action' },
+      { id: Todo::ASSIGNED, text: 'Assigned' },
+      { id: Todo::MENTIONED, text: 'Mentioned' }
     ]
-
-    options_from_collection_for_select(actions, 'id', 'title', params[:action_id])
   end
 
   def todo_projects_options
@@ -92,22 +90,28 @@ module TodosHelper
     projects = projects.includes(:namespace)
 
     projects = projects.map do |project|
-      OpenStruct.new(id: project.id, title: project.name_with_namespace)
+      { id: project.id, text: project.name_with_namespace }
     end
 
-    projects.unshift(OpenStruct.new(id: '', title: 'Any Project'))
-
-    options_from_collection_for_select(projects, 'id', 'title', params[:project_id])
+    projects.unshift({ id: '', text: 'Any Project' }).to_json
   end
 
   def todo_types_options
-    types = [
-      OpenStruct.new(title: 'Any Type', name: ''),
-      OpenStruct.new(title: 'Issue', name: 'Issue'),
-      OpenStruct.new(title: 'Merge Request', name: 'MergeRequest')
+    [
+      { id: '', text: 'Any Type' },
+      { id: 'Issue', text: 'Issue' },
+      { id: 'MergeRequest', text: 'Merge Request' }
     ]
+  end
+
+  def todo_actions_dropdown_label(selected_action_id, default_action)
+    selected_action = todo_actions_options.find { |action| action[:id] == selected_action_id.to_i}
+    selected_action ? selected_action[:text] : default_action
+  end
 
-    options_from_collection_for_select(types, 'name', 'title', params[:type])
+  def todo_types_dropdown_label(selected_type, default_type)
+    selected_type = todo_types_options.find { |type| type[:id] == selected_type && type[:id] != '' }
+    selected_type ? selected_type[:text] : default_type
   end
 
   private
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index d887cda..88f374b 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -34,4 +34,8 @@ module WorkhorseHelper
     headers.store(*Gitlab::Workhorse.send_artifacts_entry(build, entry))
     head :ok
   end
+
+  def set_workhorse_internal_api_content_type
+    headers['Content-Type'] = Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+  end
 end
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 8b83bbd..61a574d 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -9,7 +9,7 @@ class BaseMailer < ActionMailer::Base
   default reply_to: Proc.new { default_reply_to_address.format }
 
   def can?
-    Ability.abilities.allowed?(current_user, action, subject)
+    Ability.allowed?(current_user, action, subject)
   end
 
   private
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0cc709f..9799f1d 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -108,6 +108,12 @@ class Notify < BaseMailer
     headers["X-GitLab-#{model.class.name}-ID"] = model.id
     headers['X-GitLab-Reply-Key'] = reply_key
 
+    if !@labels_url && @sent_notification && @sent_notification.unsubscribable?
+      headers['List-Unsubscribe'] = unsubscribe_sent_notification_url(@sent_notification, force: true)
+
+      @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
+    end
+
     if Gitlab::IncomingEmail.enabled?
       address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
       address.display_name = @project.name_with_namespace
diff --git a/app/models/ability.rb b/app/models/ability.rb
index a49dd70..fa8f8bc 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,34 +1,5 @@
 class Ability
   class << self
-    # rubocop: disable Metrics/CyclomaticComplexity
-    def allowed(user, subject)
-      return anonymous_abilities(user, subject) if user.nil?
-      return [] unless user.is_a?(User)
-      return [] if user.blocked?
-
-      abilities_by_subject_class(user: user, subject: subject)
-    end
-
-    def abilities_by_subject_class(user:, subject:)
-      case subject
-      when CommitStatus then commit_status_abilities(user, subject)
-      when Project then project_abilities(user, subject)
-      when Issue then issue_abilities(user, subject)
-      when Note then note_abilities(user, subject)
-      when ProjectSnippet then project_snippet_abilities(user, subject)
-      when PersonalSnippet then personal_snippet_abilities(user, subject)
-      when MergeRequest then merge_request_abilities(user, subject)
-      when Group then group_abilities(user, subject)
-      when Namespace then namespace_abilities(user, subject)
-      when GroupMember then group_member_abilities(user, subject)
-      when ProjectMember then project_member_abilities(user, subject)
-      when User then user_abilities
-      when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project)
-      when Ci::Runner then runner_abilities(user, subject)
-      else []
-      end.concat(global_abilities(user))
-    end
-
     # Given a list of users and a project this method returns the users that can
     # read the given project.
     def users_that_can_read_project(users, project)
@@ -61,359 +32,7 @@ class Ability
       issues.select { |issue| issue.visible_to_user?(user) }
     end
 
-    # List of possible abilities for anonymous user
-    def anonymous_abilities(user, subject)
-      if subject.is_a?(PersonalSnippet)
-        anonymous_personal_snippet_abilities(subject)
-      elsif subject.is_a?(ProjectSnippet)
-        anonymous_project_snippet_abilities(subject)
-      elsif subject.is_a?(CommitStatus)
-        anonymous_commit_status_abilities(subject)
-      elsif subject.is_a?(Project) || subject.respond_to?(:project)
-        anonymous_project_abilities(subject)
-      elsif subject.is_a?(Group) || subject.respond_to?(:group)
-        anonymous_group_abilities(subject)
-      elsif subject.is_a?(User)
-        anonymous_user_abilities
-      else
-        []
-      end
-    end
-
-    def anonymous_project_abilities(subject)
-      project = if subject.is_a?(Project)
-                  subject
-                else
-                  subject.project
-                end
-
-      if project && project.public?
-        rules = [
-          :read_project,
-          :read_board,
-          :read_list,
-          :read_wiki,
-          :read_label,
-          :read_milestone,
-          :read_project_snippet,
-          :read_project_member,
-          :read_merge_request,
-          :read_note,
-          :read_pipeline,
-          :read_commit_status,
-          :read_container_image,
-          :download_code
-        ]
-
-        # Allow to read builds by anonymous user if guests are allowed
-        rules << :read_build if project.public_builds?
-
-        # Allow to read issues by anonymous user if issue is not confidential
-        rules << :read_issue unless subject.is_a?(Issue) && subject.confidential?
-
-        rules - project_disabled_features_rules(project)
-      else
-        []
-      end
-    end
-
-    def anonymous_commit_status_abilities(subject)
-      rules = anonymous_project_abilities(subject.project)
-      # If subject is Ci::Build which inherits from CommitStatus filter the abilities
-      rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
-      rules
-    end
-
-    def anonymous_group_abilities(subject)
-      rules = []
-
-      group = if subject.is_a?(Group)
-                subject
-              else
-                subject.group
-              end
-
-      rules << :read_group if group.public?
-
-      rules
-    end
-
-    def anonymous_personal_snippet_abilities(snippet)
-      if snippet.public?
-        [:read_personal_snippet]
-      else
-        []
-      end
-    end
-
-    def anonymous_project_snippet_abilities(snippet)
-      if snippet.public?
-        [:read_project_snippet]
-      else
-        []
-      end
-    end
-
-    def anonymous_user_abilities
-      [:read_user] unless restricted_public_level?
-    end
-
-    def global_abilities(user)
-      rules = []
-      rules << :create_group if user.can_create_group
-      rules << :read_users_list
-      rules
-    end
-
-    def project_abilities(user, project)
-      key = "/user/#{user.id}/project/#{project.id}"
-
-      if RequestStore.active?
-        RequestStore.store[key] ||= uncached_project_abilities(user, project)
-      else
-        uncached_project_abilities(user, project)
-      end
-    end
-
-    def uncached_project_abilities(user, project)
-      rules = []
-      # Push abilities on the users team role
-      rules.push(*project_team_rules(project.team, user))
-
-      owner = user.admin? ||
-              project.owner == user ||
-              (project.group && project.group.has_owner?(user))
-
-      if owner
-        rules.push(*project_owner_rules)
-      end
-
-      if project.public? || (project.internal? && !user.external?)
-        rules.push(*public_project_rules)
-
-        # Allow to read builds for internal projects
-        rules << :read_build if project.public_builds?
-
-        unless owner || project.team.member?(user) || project_group_member?(project, user)
-          rules << :request_access if project.request_access_enabled
-        end
-      end
-
-      if project.archived?
-        rules -= project_archived_rules
-      end
-
-      (rules - project_disabled_features_rules(project)).uniq
-    end
-
-    def project_team_rules(team, user)
-      # Rules based on role in project
-      if team.master?(user)
-        project_master_rules
-      elsif team.developer?(user)
-        project_dev_rules
-      elsif team.reporter?(user)
-        project_report_rules
-      elsif team.guest?(user)
-        project_guest_rules
-      else
-        []
-      end
-    end
-
-    def public_project_rules
-      @public_project_rules ||= project_guest_rules + [
-        :download_code,
-        :fork_project,
-        :read_commit_status,
-        :read_pipeline,
-        :read_container_image
-      ]
-    end
-
-    def project_guest_rules
-      @project_guest_rules ||= [
-        :read_project,
-        :read_wiki,
-        :read_issue,
-        :read_board,
-        :read_list,
-        :read_label,
-        :read_milestone,
-        :read_project_snippet,
-        :read_project_member,
-        :read_merge_request,
-        :read_note,
-        :create_project,
-        :create_issue,
-        :create_note,
-        :upload_file
-      ]
-    end
-
-    def project_report_rules
-      @project_report_rules ||= project_guest_rules + [
-        :download_code,
-        :fork_project,
-        :create_project_snippet,
-        :update_issue,
-        :admin_issue,
-        :admin_label,
-        :admin_list,
-        :read_commit_status,
-        :read_build,
-        :read_container_image,
-        :read_pipeline,
-        :read_environment,
-        :read_deployment
-      ]
-    end
-
-    def project_dev_rules
-      @project_dev_rules ||= project_report_rules + [
-        :admin_merge_request,
-        :update_merge_request,
-        :create_commit_status,
-        :update_commit_status,
-        :create_build,
-        :update_build,
-        :create_pipeline,
-        :update_pipeline,
-        :create_merge_request,
-        :create_wiki,
-        :push_code,
-        :resolve_note,
-        :create_container_image,
-        :update_container_image,
-        :create_environment,
-        :create_deployment
-      ]
-    end
-
-    def project_archived_rules
-      @project_archived_rules ||= [
-        :create_merge_request,
-        :push_code,
-        :push_code_to_protected_branches,
-        :update_merge_request,
-        :admin_merge_request
-      ]
-    end
-
-    def project_master_rules
-      @project_master_rules ||= project_dev_rules + [
-        :push_code_to_protected_branches,
-        :update_project_snippet,
-        :update_environment,
-        :update_deployment,
-        :admin_milestone,
-        :admin_project_snippet,
-        :admin_project_member,
-        :admin_merge_request,
-        :admin_note,
-        :admin_wiki,
-        :admin_project,
-        :admin_commit_status,
-        :admin_build,
-        :admin_container_image,
-        :admin_pipeline,
-        :admin_environment,
-        :admin_deployment
-      ]
-    end
-
-    def project_owner_rules
-      @project_owner_rules ||= project_master_rules + [
-        :change_namespace,
-        :change_visibility_level,
-        :rename_project,
-        :remove_project,
-        :archive_project,
-        :remove_fork_project,
-        :destroy_merge_request,
-        :destroy_issue
-      ]
-    end
-
-    def project_disabled_features_rules(project)
-      rules = []
-
-      unless project.issues_enabled
-        rules += named_abilities('issue')
-      end
-
-      unless project.merge_requests_enabled
-        rules += named_abilities('merge_request')
-      end
-
-      unless project.issues_enabled or project.merge_requests_enabled
-        rules += named_abilities('label')
-        rules += named_abilities('milestone')
-      end
-
-      unless project.snippets_enabled
-        rules += named_abilities('project_snippet')
-      end
-
-      unless project.wiki_enabled
-        rules += named_abilities('wiki')
-      end
-
-      unless project.builds_enabled
-        rules += named_abilities('build')
-        rules += named_abilities('pipeline')
-        rules += named_abilities('environment')
-        rules += named_abilities('deployment')
-      end
-
-      unless project.container_registry_enabled
-        rules += named_abilities('container_image')
-      end
-
-      rules
-    end
-
-    def group_abilities(user, group)
-      rules = []
-      rules << :read_group if can_read_group?(user, group)
-
-      owner = user.admin? || group.has_owner?(user)
-      master = owner || group.has_master?(user)
-
-      # Only group masters and group owners can create new projects
-      if master
-        rules += [
-          :create_projects,
-          :admin_milestones
-        ]
-      end
-
-      # Only group owner and administrators can admin group
-      if owner
-        rules += [
-          :admin_group,
-          :admin_namespace,
-          :admin_group_member,
-          :change_visibility_level
-        ]
-      end
-
-      if group.public? || (group.internal? && !user.external?)
-        rules << :request_access if group.request_access_enabled && group.users.exclude?(user)
-      end
-
-      rules.flatten
-    end
-
-    def can_read_group?(user, group)
-      return true if user.admin?
-      return true if group.public?
-      return true if group.internal? && !user.external?
-      return true if group.users.include?(user)
-
-      GroupProjectsFinder.new(group).execute(user).any?
-    end
-
+    # TODO: make this private and use the actual abilities stuff for this
     def can_edit_note?(user, note)
       return false if !note.editable? || !user.present?
       return true if note.author == user || user.admin?
@@ -426,207 +45,23 @@ class Ability
       end
     end
 
-    def namespace_abilities(user, namespace)
-      rules = []
-
-      # Only namespace owner and administrators can admin it
-      if namespace.owner == user || user.admin?
-        rules += [
-          :create_projects,
-          :admin_namespace
-        ]
-      end
-
-      rules.flatten
-    end
-
-    [:issue, :merge_request].each do |name|
-      define_method "#{name}_abilities" do |user, subject|
-        rules = []
-
-        if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
-          rules += [
-            :"read_#{name}",
-            :"update_#{name}",
-          ]
-        end
-
-        rules += project_abilities(user, subject.project)
-        rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
-        rules
-      end
-    end
-
-    def note_abilities(user, note)
-      rules = []
-
-      if note.author == user
-        rules += [
-          :read_note,
-          :update_note,
-          :admin_note,
-          :resolve_note
-        ]
-      end
-
-      if note.respond_to?(:project) && note.project
-        rules += project_abilities(user, note.project)
-      end
-
-      if note.for_merge_request? && note.noteable.author == user
-        rules << :resolve_note
-      end
-
-      rules
-    end
-
-    def personal_snippet_abilities(user, snippet)
-      rules = []
-
-      if snippet.author == user
-        rules += [
-          :read_personal_snippet,
-          :update_personal_snippet,
-          :admin_personal_snippet
-        ]
-      end
-
-      if snippet.public? || (snippet.internal? && !user.external?)
-        rules << :read_personal_snippet
-      end
-
-      rules
+    def allowed?(user, action, subject)
+      allowed(user, subject).include?(action)
     end
 
-    def project_snippet_abilities(user, snippet)
-      rules = []
-
-      if snippet.author == user || user.admin?
-        rules += [
-          :read_project_snippet,
-          :update_project_snippet,
-          :admin_project_snippet
-        ]
-      end
-
-      if snippet.public? || (snippet.internal? && !user.external?) || (snippet.private? && snippet.project.team.member?(user))
-        rules << :read_project_snippet
-      end
-
-      rules
-    end
-
-    def group_member_abilities(user, subject)
-      rules = []
-      target_user = subject.user
-      group = subject.group
-
-      unless group.last_owner?(target_user)
-        can_manage = group_abilities(user, group).include?(:admin_group_member)
-
-        if can_manage
-          rules << :update_group_member
-          rules << :destroy_group_member
-        elsif user == target_user
-          rules << :destroy_group_member
-        end
-      end
-
-      rules
-    end
-
-    def project_member_abilities(user, subject)
-      rules = []
-      target_user = subject.user
-      project = subject.project
-
-      unless target_user == project.owner
-        can_manage = project_abilities(user, project).include?(:admin_project_member)
-
-        if can_manage
-          rules << :update_project_member
-          rules << :destroy_project_member
-        elsif user == target_user
-          rules << :destroy_project_member
-        end
-      end
-
-      rules
-    end
-
-    def commit_status_abilities(user, subject)
-      rules = project_abilities(user, subject.project)
-      # If subject is Ci::Build which inherits from CommitStatus filter the abilities
-      rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
-      rules
-    end
-
-    def filter_build_abilities(rules)
-      # If we can't read build we should also not have that
-      # ability when looking at this in context of commit_status
-      %w(read create update admin).each do |rule|
-        rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
-      end
-      rules
-    end
-
-    def runner_abilities(user, runner)
-      if user.is_admin?
-        [:assign_runner]
-      elsif runner.is_shared? || runner.locked?
-        []
-      elsif user.ci_authorized_runners.include?(runner)
-        [:assign_runner]
-      else
-        []
-      end
-    end
+    def allowed(user, subject)
+      return uncached_allowed(user, subject) unless RequestStore.active?
 
-    def user_abilities
-      [:read_user]
-    end
-
-    def abilities
-      @abilities ||= begin
-        abilities = Six.new
-        abilities << self
-        abilities
-      end
+      user_key = user ? user.id : 'anonymous'
+      subject_key = subject ? "#{subject.class.name}/#{subject.id}" : 'global'
+      key = "/ability/#{user_key}/#{subject_key}"
+      RequestStore[key] ||= uncached_allowed(user, subject).freeze
     end
 
     private
 
-    def restricted_public_level?
-      current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
-    end
-
-    def named_abilities(name)
-      [
-        :"read_#{name}",
-        :"create_#{name}",
-        :"update_#{name}",
-        :"admin_#{name}"
-      ]
-    end
-
-    def filter_confidential_issues_abilities(user, issue, rules)
-      return rules if user.admin? || !issue.confidential?
-
-      unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER)
-        rules.delete(:admin_issue)
-        rules.delete(:read_issue)
-        rules.delete(:update_issue)
-      end
-
-      rules
-    end
-
-    def project_group_member?(project, user)
-      project.group &&
-      (
-        project.group.members.exists?(user_id: user.id) ||
-        project.group.requesters.exists?(user_id: user.id)
-      )
+    def uncached_allowed(user, subject)
+      BasePolicy.class_for(subject).abilities(user, subject)
     end
   end
 end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index f0bcb2d..55d2e07 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -146,7 +146,7 @@ class ApplicationSetting < ActiveRecord::Base
       default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
       default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
       domain_whitelist: Settings.gitlab['domain_whitelist'],
-      import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project],
+      import_sources: Gitlab::ImportSources.values,
       shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
       max_artifacts_size: Settings.artifacts['max_size'],
       require_two_factor_authentication: false,
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 12cc5aa..ab92e82 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -22,6 +22,18 @@ class Blob < SimpleDelegator
     new(blob)
   end
 
+  # Returns the data of the blob.
+  #
+  # If the blob is a text based blob the content is converted to UTF-8 and any
+  # invalid byte sequences are replaced.
+  def data
+    if binary?
+      super
+    else
+      @data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
+    end
+  end
+
   def no_highlighting?
     size && size > 1.megabyte
   end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 096b3b8..cb87b43 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -1,5 +1,7 @@
 module Ci
   class Build < CommitStatus
+    include TokenAuthenticatable
+
     belongs_to :runner, class_name: 'Ci::Runner'
     belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
     belongs_to :erased_by, class_name: 'User'
@@ -23,7 +25,10 @@ module Ci
 
     acts_as_taggable
 
+    add_authentication_token_field :token
+
     before_save :update_artifacts_size, if: :artifacts_file_changed?
+    before_save :ensure_token
     before_destroy { project }
 
     after_create :execute_hooks
@@ -38,6 +43,7 @@ module Ci
         new_build.status = 'pending'
         new_build.runner_id = nil
         new_build.trigger_request_id = nil
+        new_build.token = nil
         new_build.save
       end
 
@@ -79,11 +85,14 @@ module Ci
 
       after_transition any => [:success] do |build|
         if build.environment.present?
-          service = CreateDeploymentService.new(build.project, build.user,
-                                                environment: build.environment,
-                                                sha: build.sha,
-                                                ref: build.ref,
-                                                tag: build.tag)
+          service = CreateDeploymentService.new(
+            build.project, build.user,
+            environment: build.environment,
+            sha: build.sha,
+            ref: build.ref,
+            tag: build.tag,
+            options: build.options[:environment],
+            variables: build.variables)
           service.execute(build)
         end
       end
@@ -148,6 +157,7 @@ module Ci
       variables += runner.predefined_variables if runner
       variables += project.container_registry_variables
       variables += yaml_variables
+      variables += user_variables
       variables += project.secret_variables
       variables += trigger_request.user_variables if trigger_request
       variables
@@ -172,7 +182,7 @@ module Ci
     end
 
     def repo_url
-      auth = "gitlab-ci-token:#{token}@"
+      auth = "gitlab-ci-token:#{ensure_token!}@"
       project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
         prefix + auth
       end
@@ -208,29 +218,33 @@ module Ci
       end
     end
 
+    def has_trace_file?
+      File.exist?(path_to_trace) || has_old_trace_file?
+    end
+
     def has_trace?
       raw_trace.present?
     end
 
     def raw_trace
-      if File.file?(path_to_trace)
-        File.read(path_to_trace)
-      elsif project.ci_id && File.file?(old_path_to_trace)
-        # Temporary fix for build trace data integrity
-        File.read(old_path_to_trace)
+      if File.exist?(trace_file_path)
+        File.read(trace_file_path)
       else
         # backward compatibility
         read_attribute :trace
       end
     end
 
+    ##
+    # Deprecated
+    #
+    # This is a hotfix for CI build data integrity, see #4246
+    def has_old_trace_file?
+      project.ci_id && File.exist?(old_path_to_trace)
+    end
+
     def trace
-      trace = raw_trace
-      if project && trace.present? && project.runners_token.present?
-        trace.gsub(project.runners_token, 'xxxxxx')
-      else
-        trace
-      end
+      hide_secrets(raw_trace)
     end
 
     def trace_length
@@ -243,6 +257,7 @@ module Ci
 
     def trace=(trace)
       recreate_trace_dir
+      trace = hide_secrets(trace)
       File.write(path_to_trace, trace)
     end
 
@@ -256,12 +271,22 @@ module Ci
     def append_trace(trace_part, offset)
       recreate_trace_dir
 
+      trace_part = hide_secrets(trace_part)
+
       File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
       File.open(path_to_trace, 'ab') do |f|
         f.write(trace_part)
       end
     end
 
+    def trace_file_path
+      if has_old_trace_file?
+        old_path_to_trace
+      else
+        path_to_trace
+      end
+    end
+
     def dir_to_trace
       File.join(
         Settings.gitlab_ci.builds_path,
@@ -323,12 +348,8 @@ module Ci
       )
     end
 
-    def token
-      project.runners_token
-    end
-
     def valid_token?(token)
-      project.valid_runners_token?(token)
+      self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
     end
 
     def has_tags?
@@ -352,7 +373,7 @@ module Ci
     end
 
     def artifacts?
-      !artifacts_expired? && artifacts_file.exists?
+      !artifacts_expired? && self[:artifacts_file].present?
     end
 
     def artifacts_metadata?
@@ -417,6 +438,15 @@ module Ci
       read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || []
     end
 
+    def user_variables
+      return [] if user.blank?
+
+      [
+        { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
+        { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }
+      ]
+    end
+
     private
 
     def update_artifacts_size
@@ -452,6 +482,7 @@ module Ci
       ]
       variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
       variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
+      variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if manual?
       variables
     end
 
@@ -460,5 +491,14 @@ module Ci
 
       pipeline.config_processor.build_attributes(name)
     end
+
+    def hide_secrets(trace)
+      return unless trace
+
+      trace = trace.dup
+      Ci::MaskSecret.mask!(trace, project.runners_token) if project
+      Ci::MaskSecret.mask!(trace, token)
+      trace
+    end
   end
 end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 087abe4..663c5b1 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1,7 +1,8 @@
 module Ci
   class Pipeline < ActiveRecord::Base
     extend Ci::Model
-    include Statuseable
+    include HasStatus
+    include Importable
 
     self.table_name = 'ci_commits'
 
@@ -12,12 +13,12 @@ module Ci
     has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
     has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
 
-    validates_presence_of :sha
-    validates_presence_of :ref
-    validates_presence_of :status
-    validate :valid_commit_sha
+    validates_presence_of :sha, unless: :importing?
+    validates_presence_of :ref, unless: :importing?
+    validates_presence_of :status, unless: :importing?
+    validate :valid_commit_sha, unless: :importing?
 
-    after_save :keep_around_commits
+    after_save :keep_around_commits, unless: :importing?
 
     delegate :stages, to: :statuses
 
@@ -55,6 +56,16 @@ module Ci
         pipeline.finished_at = Time.now
       end
 
+      after_transition [:created, :pending] => :running do |pipeline|
+        MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
+          update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil)
+      end
+
+      after_transition any => [:success] do |pipeline|
+        MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
+          update_all(latest_build_finished_at: pipeline.finished_at)
+      end
+
       before_transition do |pipeline|
         pipeline.update_duration
       end
@@ -65,8 +76,8 @@ module Ci
     end
 
     # ref can't be HEAD or SHA, can only be branch/tag name
-    scope :latest_successful_for, ->(ref = default_branch) do
-      where(ref: ref).success.order(id: :desc).limit(1)
+    def self.latest_successful_for(ref)
+      where(ref: ref).order(id: :desc).success.first
     end
 
     def self.truncate_sha(sha)
@@ -83,7 +94,7 @@ module Ci
     end
 
     def stages_with_latest_statuses
-      statuses.latest.order(:stage_idx).group_by(&:stage)
+      statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage)
     end
 
     def project_id
@@ -241,13 +252,16 @@ module Ci
     end
 
     def build_updated
-      case latest_builds_status
-      when 'pending' then enqueue
-      when 'running' then run
-      when 'success' then succeed
-      when 'failed' then drop
-      when 'canceled' then cancel
-      when 'skipped' then skip
+      with_lock do
+        reload
+        case latest_builds_status
+        when 'pending' then enqueue
+        when 'running' then run
+        when 'success' then succeed
+        when 'failed' then drop
+        when 'canceled' then cancel
+        when 'skipped' then skip
+        end
       end
     end
 
@@ -257,8 +271,17 @@ module Ci
       ]
     end
 
+    def queued_duration
+      return unless started_at
+
+      seconds = (started_at - created_at).to_i
+      seconds unless seconds.zero?
+    end
+
     def update_duration
-      self.duration = calculate_duration
+      return unless started_at
+
+      self.duration = Gitlab::Ci::PipelineDuration.from_pipeline(self)
     end
 
     def execute_hooks
@@ -267,6 +290,16 @@ module Ci
       project.execute_services(data, :pipeline_hooks)
     end
 
+    # Merge requests for which the current pipeline is running against
+    # the merge request's latest commit.
+    def merge_requests
+      @merge_requests ||=
+        begin
+          project.merge_requests.where(source_branch: self.ref).
+            select { |merge_request| merge_request.pipeline.try(:id) == self.id }
+        end
+    end
+
     private
 
     def pipeline_data
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 49f05f8..ed5d4b1 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -2,7 +2,7 @@ module Ci
   class Runner < ActiveRecord::Base
     extend Ci::Model
 
-    LAST_CONTACT_TIME = 5.minutes.ago
+    LAST_CONTACT_TIME = 2.hours.ago
     AVAILABLE_SCOPES = %w[specific shared active paused online]
     FORM_EDITABLE = %i[description tag_list active run_untagged locked]
 
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index c9c47ec..6959223 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -1,7 +1,7 @@
 module Ci
   class Variable < ActiveRecord::Base
     extend Ci::Model
-    
+
     belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
 
     validates_uniqueness_of :key, scope: :gl_project_id
@@ -11,7 +11,9 @@ module Ci
       format: { with: /\A[a-zA-Z0-9_]+\z/,
                 message: "can contain only letters, digits and '_'." }
 
-    attr_encrypted :value, 
+    scope :order_key_asc, -> { reorder(key: :asc) }
+
+    attr_encrypted :value,
        mode: :per_attribute_iv_and_salt,
        insecure_mode: true,
        key: Gitlab::Application.secrets.db_key_base,
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 817d063..e64fd1e 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -108,15 +108,6 @@ class Commit
     @diff_line_count
   end
 
-  # Returns a string describing the commit for use in a link title
-  #
-  # Example
-  #
-  #   "Commit: Alex Denisov - Project git clone panel"
-  def link_title
-    "Commit: #{author_name} - #{title}"
-  end
-
   # Returns the commits title.
   #
   # Usually, the commit title is the first line of the commit message.
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 630ee96..656a242 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -4,12 +4,10 @@
 #
 #   range = CommitRange.new('f3f85602...e86e1013', project)
 #   range.exclude_start?  # => false
-#   range.reference_title # => "Commits f3f85602 through e86e1013"
 #   range.to_s            # => "f3f85602...e86e1013"
 #
 #   range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
 #   range.exclude_start?  # => true
-#   range.reference_title # => "Commits f3f85602^ through e86e1013"
 #   range.to_param        # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
 #   range.to_s            # => "f3f85602..e86e1013"
 #
@@ -109,11 +107,6 @@ class CommitRange
     reference
   end
 
-  # Returns a String for use in a link's title attribute
-  def reference_title
-    "Commits #{sha_start} through #{sha_to}"
-  end
-
   # Return a Hash of parameters for passing to a URL helper
   #
   # See `namespace_project_compare_url`
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 84ceeac..736db1a 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,5 +1,5 @@
 class CommitStatus < ActiveRecord::Base
-  include Statuseable
+  include HasStatus
   include Importable
 
   self.table_name = 'ci_builds'
@@ -25,6 +25,8 @@ class CommitStatus < ActiveRecord::Base
   scope :retried, -> { where.not(id: latest) }
   scope :ordered, -> { order(:name) }
   scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
+  scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) }
+  scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) }
 
   state_machine :status do
     event :enqueue do
@@ -67,11 +69,9 @@ class CommitStatus < ActiveRecord::Base
       commit_status.update_attributes finished_at: Time.now
     end
 
-    # We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed
-    around_transition any => [:success, :failed, :canceled] do |commit_status, block|
-      block.call
-
+    after_transition any => [:success, :failed, :canceled] do |commit_status|
       commit_status.pipeline.try(:process!)
+      true
     end
 
     after_transition do |commit_status, transition|
@@ -93,6 +93,10 @@ class CommitStatus < ActiveRecord::Base
     pipeline.before_sha || Gitlab::Git::BLANK_SHA
   end
 
+  def group_name
+    name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
+  end
+
   def self.stages
     # We group by stage name, but order stages by theirs' index
     unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
@@ -111,6 +115,10 @@ class CommitStatus < ActiveRecord::Base
     allow_failure? && (failed? || canceled?)
   end
 
+  def playable?
+    false
+  end
+
   def duration
     calculate_duration
   end
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 800a16a..073ac4c 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -2,7 +2,7 @@ module Awardable
   extend ActiveSupport::Concern
 
   included do
-    has_many :award_emoji, -> { includes(:user) }, as: :awardable, dependent: :destroy
+    has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy
 
     if self < Participable
       # By default we always load award_emoji user association
@@ -59,6 +59,24 @@ module Awardable
     true
   end
 
+  def awardable_votes?(name)
+    AwardEmoji::UPVOTE_NAME == name || AwardEmoji::DOWNVOTE_NAME == name
+  end
+
+  def user_can_award?(current_user, name)
+    if user_authored?(current_user)
+      !awardable_votes?(normalize_name(name))
+    else
+      true
+    end
+  end
+
+  def user_authored?(current_user)
+    author = self.respond_to?(:author) ? self.author : self.user
+
+    author == current_user
+  end
+
   def awarded_emoji?(emoji_name, current_user)
     award_emoji.where(name: emoji_name, user: current_user).exists?
   end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
new file mode 100644
index 0000000..0fa4df0
--- /dev/null
+++ b/app/models/concerns/has_status.rb
@@ -0,0 +1,94 @@
+module HasStatus
+  extend ActiveSupport::Concern
+
+  AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
+  STARTED_STATUSES = %w[running success failed skipped]
+  ACTIVE_STATUSES = %w[pending running]
+  COMPLETED_STATUSES = %w[success failed canceled]
+
+  class_methods do
+    def status_sql
+      scope = all
+      builds = scope.select('count(*)').to_sql
+      created = scope.created.select('count(*)').to_sql
+      success = scope.success.select('count(*)').to_sql
+      ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
+      ignored ||= '0'
+      pending = scope.pending.select('count(*)').to_sql
+      running = scope.running.select('count(*)').to_sql
+      canceled = scope.canceled.select('count(*)').to_sql
+      skipped = scope.skipped.select('count(*)').to_sql
+
+      deduce_status = "(CASE
+        WHEN (#{builds})=(#{created}) THEN 'created'
+        WHEN (#{builds})=(#{skipped}) THEN 'skipped'
+        WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
+        WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending'
+        WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
+        WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
+        ELSE 'failed'
+      END)"
+
+      deduce_status
+    end
+
+    def status
+      all.pluck(self.status_sql).first
+    end
+
+    def started_at
+      all.minimum(:started_at)
+    end
+
+    def finished_at
+      all.maximum(:finished_at)
+    end
+  end
+
+  included do
+    validates :status, inclusion: { in: AVAILABLE_STATUSES }
+
+    state_machine :status, initial: :created do
+      state :created, value: 'created'
+      state :pending, value: 'pending'
+      state :running, value: 'running'
+      state :failed, value: 'failed'
+      state :success, value: 'success'
+      state :canceled, value: 'canceled'
+      state :skipped, value: 'skipped'
+    end
+
+    scope :created, -> { where(status: 'created') }
+    scope :relevant, -> { where.not(status: 'created') }
+    scope :running, -> { where(status: 'running') }
+    scope :pending, -> { where(status: 'pending') }
+    scope :success, -> { where(status: 'success') }
+    scope :failed, -> { where(status: 'failed')  }
+    scope :canceled, -> { where(status: 'canceled')  }
+    scope :skipped, -> { where(status: 'skipped')  }
+    scope :running_or_pending, -> { where(status: [:running, :pending]) }
+    scope :finished, -> { where(status: [:success, :failed, :canceled]) }
+  end
+
+  def started?
+    STARTED_STATUSES.include?(status) && started_at
+  end
+
+  def active?
+    ACTIVE_STATUSES.include?(status)
+  end
+
+  def complete?
+    COMPLETED_STATUSES.include?(status)
+  end
+
+  private
+
+  def calculate_duration
+    if started_at && finished_at
+      finished_at - started_at
+    elsif started_at
+      Time.now - started_at
+    end
+  end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index afb5ce3..ff465d2 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -28,10 +28,13 @@ module Issuable
         loaded? && to_a.all? { |note| note.association(:award_emoji).loaded? }
       end
     end
+
     has_many :label_links, as: :target, dependent: :destroy
     has_many :labels, through: :label_links
     has_many :todos, as: :target, dependent: :destroy
 
+    has_one :metrics
+
     validates :author, presence: true
     validates :title, presence: true, length: { within: 0..255 }
 
@@ -81,12 +84,19 @@ module Issuable
     acts_as_paranoid
 
     after_save :update_assignee_cache_counts, if: :assignee_id_changed?
+    after_save :record_metrics
 
     def update_assignee_cache_counts
       # make sure we flush the cache for both the old *and* new assignee
       User.find(assignee_id_was).update_cache_counts if assignee_id_was
       assignee.update_cache_counts if assignee
     end
+
+    # We want to use optimistic lock for cases when only title or description are involved
+    # http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
+    def locking_enabled?
+      title_changed? || description_changed?
+    end
   end
 
   module ClassMethods
@@ -276,4 +286,9 @@ module Issuable
   def can_move?(*)
     false
   end
+
+  def record_metrics
+    metrics = self.metrics || create_metrics
+    metrics.record!
+  end
 end
diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb
index a881fb8..b8dd27a 100644
--- a/app/models/concerns/note_on_diff.rb
+++ b/app/models/concerns/note_on_diff.rb
@@ -28,4 +28,8 @@ module NoteOnDiff
   def can_be_award_emoji?
     false
   end
+
+  def to_discussion
+    Discussion.new([self])
+  end
 end
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
new file mode 100644
index 0000000..9216122
--- /dev/null
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -0,0 +1,37 @@
+# Makes api V3 compatible with old project features permissions methods
+#
+# After migrating issues_enabled merge_requests_enabled builds_enabled snippets_enabled and wiki_enabled
+# fields to a new table "project_features", support for the old fields is still needed in the API.
+
+module ProjectFeaturesCompatibility
+  extend ActiveSupport::Concern
+
+  def wiki_enabled=(value)
+    write_feature_attribute(:wiki_access_level, value)
+  end
+
+  def builds_enabled=(value)
+    write_feature_attribute(:builds_access_level, value)
+  end
+
+  def merge_requests_enabled=(value)
+    write_feature_attribute(:merge_requests_access_level, value)
+  end
+
+  def issues_enabled=(value)
+    write_feature_attribute(:issues_access_level, value)
+  end
+
+  def snippets_enabled=(value)
+    write_feature_attribute(:snippets_access_level, value)
+  end
+
+  private
+
+  def write_feature_attribute(field, value)
+    build_project_feature unless project_feature
+
+    access_level = value == "true" ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
+    project_feature.update_attribute(field, access_level)
+  end
+end
diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb
deleted file mode 100644
index 750f937..0000000
--- a/app/models/concerns/statuseable.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-module Statuseable
-  extend ActiveSupport::Concern
-
-  AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
-  STARTED_STATUSES = %w[running success failed skipped]
-  ACTIVE_STATUSES = %w[pending running]
-  COMPLETED_STATUSES = %w[success failed canceled]
-
-  class_methods do
-    def status_sql
-      scope = all.relevant
-      builds = scope.select('count(*)').to_sql
-      success = scope.success.select('count(*)').to_sql
-      ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
-      ignored ||= '0'
-      pending = scope.pending.select('count(*)').to_sql
-      running = scope.running.select('count(*)').to_sql
-      canceled = scope.canceled.select('count(*)').to_sql
-      skipped = scope.skipped.select('count(*)').to_sql
-
-      deduce_status = "(CASE
-        WHEN (#{builds})=0 THEN NULL
-        WHEN (#{builds})=(#{skipped}) THEN 'skipped'
-        WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
-        WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending'
-        WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
-        WHEN (#{running})+(#{pending})>0 THEN 'running'
-        ELSE 'failed'
-      END)"
-
-      deduce_status
-    end
-
-    def status
-      all.pluck(self.status_sql).first
-    end
-
-    def started_at
-      all.minimum(:started_at)
-    end
-
-    def finished_at
-      all.maximum(:finished_at)
-    end
-  end
-
-  included do
-    validates :status, inclusion: { in: AVAILABLE_STATUSES }
-
-    state_machine :status, initial: :created do
-      state :created, value: 'created'
-      state :pending, value: 'pending'
-      state :running, value: 'running'
-      state :failed, value: 'failed'
-      state :success, value: 'success'
-      state :canceled, value: 'canceled'
-      state :skipped, value: 'skipped'
-    end
-
-    scope :created, -> { where(status: 'created') }
-    scope :relevant, -> { where.not(status: 'created') }
-    scope :running, -> { where(status: 'running') }
-    scope :pending, -> { where(status: 'pending') }
-    scope :success, -> { where(status: 'success') }
-    scope :failed, -> { where(status: 'failed')  }
-    scope :canceled, -> { where(status: 'canceled')  }
-    scope :skipped, -> { where(status: 'skipped')  }
-    scope :running_or_pending, -> { where(status: [:running, :pending]) }
-    scope :finished, -> { where(status: [:success, :failed, :canceled]) }
-  end
-
-  def started?
-    STARTED_STATUSES.include?(status) && started_at
-  end
-
-  def active?
-    ACTIVE_STATUSES.include?(status)
-  end
-
-  def complete?
-    COMPLETED_STATUSES.include?(status)
-  end
-
-  private
-
-  def calculate_duration
-    if started_at && finished_at
-      finished_at - started_at
-    elsif started_at
-      Time.now - started_at
-    end
-  end
-end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index df2a9e3..a3ac577 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -52,11 +52,11 @@ module Taskable
   end
 
   # Return a string that describes the current state of this Taskable's task
-  # list items, e.g. "20 tasks (12 completed, 8 remaining)"
+  # list items, e.g. "12 of 20 tasks completed"
   def task_status
     return '' if description.blank?
 
     sum = tasks.summary
-    "#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)"
+    "#{sum.complete_count} of #{sum.item_count} #{'task'.pluralize(sum.item_count)} completed"
   end
 end
diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
new file mode 100644
index 0000000..be29548
--- /dev/null
+++ b/app/models/cycle_analytics.rb
@@ -0,0 +1,97 @@
+class CycleAnalytics
+  include Gitlab::Database::Median
+  include Gitlab::Database::DateTime
+
+  def initialize(project, from:)
+    @project = project
+    @from = from
+  end
+
+  def summary
+    @summary ||= Summary.new(@project, from: @from)
+  end
+
+  def issue
+    calculate_metric(:issue,
+                     Issue.arel_table[:created_at],
+                     [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
+                      Issue::Metrics.arel_table[:first_added_to_board_at]])
+  end
+
+  def plan
+    calculate_metric(:plan,
+                     [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
+                      Issue::Metrics.arel_table[:first_added_to_board_at]],
+                     Issue::Metrics.arel_table[:first_mentioned_in_commit_at])
+  end
+
+  def code
+    calculate_metric(:code,
+                     Issue::Metrics.arel_table[:first_mentioned_in_commit_at],
+                     MergeRequest.arel_table[:created_at])
+  end
+
+  def test
+    calculate_metric(:test,
+                     MergeRequest::Metrics.arel_table[:latest_build_started_at],
+                     MergeRequest::Metrics.arel_table[:latest_build_finished_at])
+  end
+
+  def review
+    calculate_metric(:review,
+                     MergeRequest.arel_table[:created_at],
+                     MergeRequest::Metrics.arel_table[:merged_at])
+  end
+
+  def staging
+    calculate_metric(:staging,
+                     MergeRequest::Metrics.arel_table[:merged_at],
+                     MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
+  end
+
+  def production
+    calculate_metric(:production,
+                     Issue.arel_table[:created_at],
+                     MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
+  end
+
+  private
+
+  def calculate_metric(name, start_time_attrs, end_time_attrs)
+    cte_table = Arel::Table.new("cte_table_for_#{name}")
+
+    # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
+    # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
+    # We compute the (end_time - start_time) interval, and give it an alias based on the current
+    # cycle analytics stage.
+    interval_query = Arel::Nodes::As.new(
+      cte_table,
+      subtract_datetimes(base_query, end_time_attrs, start_time_attrs, name.to_s))
+
+    median_datetime(cte_table, interval_query, name)
+  end
+
+  # Join table with a row for every <issue,merge_request> pair (where the merge request
+  # closes the given issue) with issue and merge request metrics included. The metrics
+  # are loaded with an inner join, so issues / merge requests without metrics are
+  # automatically excluded.
+  def base_query
+    arel_table = MergeRequestsClosingIssues.arel_table
+
+    # Load issues
+    query = arel_table.join(Issue.arel_table).on(Issue.arel_table[:id].eq(arel_table[:issue_id])).
+            join(Issue::Metrics.arel_table).on(Issue.arel_table[:id].eq(Issue::Metrics.arel_table[:issue_id])).
+            where(Issue.arel_table[:project_id].eq(@project.id)).
+            where(Issue.arel_table[:deleted_at].eq(nil)).
+            where(Issue.arel_table[:created_at].gteq(@from))
+
+    # Load merge_requests
+    query = query.join(MergeRequest.arel_table, Arel::Nodes::OuterJoin).
+            on(MergeRequest.arel_table[:id].eq(arel_table[:merge_request_id])).
+            join(MergeRequest::Metrics.arel_table).
+            on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id]))
+
+    # Limit to merge requests that have been deployed to production after `@from`
+    query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from))
+  end
+end
diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb
new file mode 100644
index 0000000..53b2cac
--- /dev/null
+++ b/app/models/cycle_analytics/summary.rb
@@ -0,0 +1,24 @@
+class CycleAnalytics
+  class Summary
+    def initialize(project, from:)
+      @project = project
+      @from = from
+    end
+
+    def new_issues
+      @project.issues.created_after(@from).count
+    end
+
+    def commits
+      repository = @project.repository.raw_repository
+
+      if @project.default_branch
+        repository.log(ref: @project.default_branch, after: @from).count
+      end
+    end
+
+    def deploys
+      @project.deployments.where("created_at > ?", @from).count
+    end
+  end
+end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 1e33888..07d7e19 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -42,4 +42,38 @@ class Deployment < ActiveRecord::Base
 
     project.repository.is_ancestor?(commit.id, sha)
   end
+
+  def update_merge_request_metrics!
+    return unless environment.update_merge_request_metrics?
+
+    merge_requests = project.merge_requests.
+                     joins(:metrics).
+                     where(target_branch: self.ref, merge_request_metrics: { first_deployed_to_production_at: nil }).
+                     where("merge_request_metrics.merged_at <= ?", self.created_at)
+
+    if previous_deployment
+      merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.created_at)
+    end
+
+    # Need to use `map` instead of `select` because MySQL doesn't allow `SELECT`ing from the same table
+    # that we're updating.
+    merge_request_ids =
+      if Gitlab::Database.postgresql?
+        merge_requests.select(:id)
+      elsif Gitlab::Database.mysql?
+        merge_requests.map(&:id)
+      end
+
+    MergeRequest::Metrics.
+      where(merge_request_id: merge_request_ids, first_deployed_to_production_at: nil).
+      update_all(first_deployed_to_production_at: self.created_at)
+  end
+
+  def previous_deployment
+    @previous_deployment ||=
+      project.deployments.joins(:environment).
+      where(environments: { name: self.environment.name }, ref: self.ref).
+      where.not(id: self.id).
+      take
+  end
 end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index c8320ff..559b307 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -13,6 +13,11 @@ class DiffNote < Note
   validate :positions_complete
   validate :verify_supported
 
+  # Keep this scope in sync with the logic in `#resolvable?`
+  scope :resolvable, -> { user.where(noteable_type: 'MergeRequest') }
+  scope :resolved, -> { resolvable.where.not(resolved_at: nil) }
+  scope :unresolved, -> { resolvable.where(resolved_at: nil) }
+
   after_initialize :ensure_original_discussion_id
   before_validation :set_original_position, :update_position, on: :create
   before_validation :set_line_code, :set_original_discussion_id
@@ -25,6 +30,16 @@ class DiffNote < Note
     def build_discussion_id(noteable_type, noteable_id, position)
       [super(noteable_type, noteable_id), *position.key].join("-")
     end
+
+    # This method must be kept in sync with `#resolve!`
+    def resolve!(current_user)
+      unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id)
+    end
+
+    # This method must be kept in sync with `#unresolve!`
+    def unresolve!
+      resolved.update_all(resolved_at: nil, resolved_by_id: nil)
+    end
   end
 
   def new_diff_note?
@@ -73,6 +88,7 @@ class DiffNote < Note
     self.position.diff_refs == diff_refs
   end
 
+  # If you update this method remember to also update the scope `resolvable`
   def resolvable?
     !system? && for_merge_request?
   end
@@ -83,6 +99,7 @@ class DiffNote < Note
     self.resolved_at.present?
   end
 
+  # If you update this method remember to also update `.resolve!`
   def resolve!(current_user)
     return unless resolvable?
     return if resolved?
@@ -92,6 +109,7 @@ class DiffNote < Note
     save!
   end
 
+  # If you update this method remember to also update `.unresolve!`
   def unresolve!
     return unless resolvable?
     return unless resolved?
@@ -107,10 +125,6 @@ class DiffNote < Note
     self.noteable.find_diff_discussion(self.discussion_id)
   end
 
-  def to_discussion
-    Discussion.new([self])
-  end
-
   private
 
   def supported?
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index 9676bc0..de06c13 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -1,7 +1,7 @@
 class Discussion
   NUMBER_OF_TRUNCATED_DIFF_LINES = 16
 
-  attr_reader :first_note, :last_note, :notes
+  attr_reader :notes
 
   delegate  :created_at,
             :project,
@@ -36,8 +36,6 @@ class Discussion
   end
 
   def initialize(notes)
-    @first_note = notes.first
-    @last_note = notes.last
     @notes = notes
   end
 
@@ -70,17 +68,25 @@ class Discussion
   end
 
   def resolvable?
-    return @resolvable if defined?(@resolvable)
+    return @resolvable if @resolvable.present?
 
     @resolvable = diff_discussion? && notes.any?(&:resolvable?)
   end
 
   def resolved?
-    return @resolved if defined?(@resolved)
+    return @resolved if @resolved.present?
 
     @resolved = resolvable? && notes.none?(&:to_be_resolved?)
   end
 
+  def first_note
+    @first_note ||= @notes.first
+  end
+
+  def last_note
+    @last_note ||= @notes.last
+  end
+
   def resolved_notes
     notes.select(&:resolved?)
   end
@@ -100,17 +106,13 @@ class Discussion
   def resolve!(current_user)
     return unless resolvable?
 
-    notes.each do |note|
-      note.resolve!(current_user) if note.resolvable?
-    end
+    update { |notes| notes.resolve!(current_user) }
   end
 
   def unresolve!
     return unless resolvable?
 
-    notes.each do |note|
-      note.unresolve! if note.resolvable?
-    end
+    update { |notes| notes.unresolve! }
   end
 
   def for_target?(target)
@@ -118,7 +120,7 @@ class Discussion
   end
 
   def active?
-    return @active if defined?(@active)
+    return @active if @active.present?
 
     @active = first_note.active?
   end
@@ -174,4 +176,17 @@ class Discussion
 
     prev_lines
   end
+
+  private
+
+  def update
+    notes_relation = DiffNote.where(id: notes.map(&:id)).fresh
+    yield(notes_relation)
+
+    # Set the notes array to the updated notes
+    @notes = notes_relation.to_a
+
+    # Reset the memoized values
+    @last_resolved_note = @resolvable = @resolved = @first_note = @last_note = nil
+  end
 end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 75e6f86..49e0a20 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -4,6 +4,7 @@ class Environment < ActiveRecord::Base
   has_many :deployments
 
   before_validation :nullify_external_url
+  before_save :set_environment_type
 
   validates :name,
             presence: true,
@@ -26,9 +27,24 @@ class Environment < ActiveRecord::Base
     self.external_url = nil if self.external_url.blank?
   end
 
+  def set_environment_type
+    names = name.split('/')
+
+    self.environment_type =
+      if names.many?
+        names.first
+      else
+        nil
+      end
+  end
+
   def includes_commit?(commit)
     return false unless last_deployment
 
     last_deployment.includes_commit?(commit)
   end
+
+  def update_merge_request_metrics?
+    self.name == "production"
+  end
 end
diff --git a/app/models/event.rb b/app/models/event.rb
index fd736d1..55a76e2 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -13,6 +13,8 @@ class Event < ActiveRecord::Base
   LEFT      = 9 # User left project
   DESTROYED = 10
 
+  RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
+
   delegate :name, :email, to: :author, prefix: true, allow_nil: true
   delegate :title, to: :issue, prefix: true, allow_nil: true
   delegate :title, to: :merge_request, prefix: true, allow_nil: true
@@ -65,7 +67,7 @@ class Event < ActiveRecord::Base
     elsif created_project?
       true
     elsif issue? || issue_note?
-      Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target)
+      Ability.allowed?(user, :read_issue, note? ? note_target : target)
     else
       ((merge_request? || note?) && target.present?) || milestone?
     end
@@ -324,8 +326,27 @@ class Event < ActiveRecord::Base
   end
 
   def reset_project_activity
-    if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain
-      project.update_column(:last_activity_at, self.created_at)
-    end
+    return unless project
+
+    # Don't even bother obtaining a lock if the last update happened less than
+    # 60 minutes ago.
+    return if recent_update?
+
+    return unless try_obtain_lease
+
+    project.update_column(:last_activity_at, created_at)
+  end
+
+  private
+
+  def recent_update?
+    project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
+  end
+
+  def try_obtain_lease
+    Gitlab::ExclusiveLease.
+      new("project:update_last_activity_at:#{project.id}",
+          timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i).
+      try_obtain
   end
 end
diff --git a/app/models/group.rb b/app/models/group.rb
index c48869a..aefb94b 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -95,6 +95,13 @@ class Group < Namespace
     end
   end
 
+  def lfs_enabled?
+    return false unless Gitlab.config.lfs.enabled
+    return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?
+
+    self[:lfs_enabled]
+  end
+
   def add_users(user_ids, access_level, current_user: nil, expires_at: nil)
     user_ids.each do |user_id|
       Member.add_user(
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 836a75b..c631e7a 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -2,6 +2,7 @@ class ProjectHook < WebHook
   belongs_to :project
 
   scope :issue_hooks, -> { where(issues_events: true) }
+  scope :confidential_issue_hooks, -> { where(confidential_issues_events: true) }
   scope :note_hooks, -> { where(note_events: true) }
   scope :merge_request_hooks, -> { where(merge_requests_events: true) }
   scope :build_hooks, -> { where(build_events: true) }
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index f365dee..595602e 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base
 
   default_value_for :push_events, true
   default_value_for :issues_events, false
+  default_value_for :confidential_issues_events, false
   default_value_for :note_events, false
   default_value_for :merge_requests_events, false
   default_value_for :tag_push_events, false
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 7886113..abd58e0 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -23,6 +23,8 @@ class Issue < ActiveRecord::Base
 
   has_many :events, as: :target, dependent: :destroy
 
+  has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
+
   validates :project, presence: true
 
   scope :cared, ->(user) { where(assignee_id: user) }
@@ -36,6 +38,8 @@ class Issue < ActiveRecord::Base
   scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
   scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
 
+  scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
+
   attr_spammable :title, spam_title: true
   attr_spammable :description, spam_description: true
 
diff --git a/app/models/issue/metrics.rb b/app/models/issue/metrics.rb
new file mode 100644
index 0000000..012d545
--- /dev/null
+++ b/app/models/issue/metrics.rb
@@ -0,0 +1,21 @@
+class Issue::Metrics < ActiveRecord::Base
+  belongs_to :issue
+
+  def record!
+    if issue.milestone_id.present? && self.first_associated_with_milestone_at.blank?
+      self.first_associated_with_milestone_at = Time.now
+    end
+
+    if issue_assigned_to_list_label? && self.first_added_to_board_at.blank?
+      self.first_added_to_board_at = Time.now
+    end
+
+    self.save
+  end
+
+  private
+
+  def issue_assigned_to_list_label?
+    issue.labels.any? { |label| label.lists.present? }
+  end
+end
diff --git a/app/models/member.rb b/app/models/member.rb
index 64e0d33..6940637 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -28,17 +28,34 @@ class Member < ActiveRecord::Base
       allow_nil: true
     }
 
+  # This scope encapsulates (most of) the conditions a row in the member table
+  # must satisfy if it is a valid permission. Of particular note:
+  #
+  #   * Access requests must be excluded
+  #   * Blocked users must be excluded
+  #   * Invitations take effect immediately
+  #   * expires_at is not implemented. A background worker purges expired rows
+  scope :active, -> do
+    is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil))
+    user_is_active = User.arel_table[:state].eq(:active)
+
+    includes(:user).references(:users)
+      .where(is_external_invite.or(user_is_active))
+      .where(requested_at: nil)
+  end
+
   scope :invite, -> { where.not(invite_token: nil) }
   scope :non_invite, -> { where(invite_token: nil) }
   scope :request, -> { where.not(requested_at: nil) }
-  scope :has_access, -> { where('access_level > 0') }
-
-  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) }
-  scope :owners_and_masters,  -> { where(access_level: [OWNER, MASTER]) }
+
+  scope :has_access, -> { active.where('access_level > 0') }
+
+  scope :guests, -> { active.where(access_level: GUEST) }
+  scope :reporters, -> { active.where(access_level: REPORTER) }
+  scope :developers, -> { active.where(access_level: DEVELOPER) }
+  scope :masters,  -> { active.where(access_level: MASTER) }
+  scope :owners,  -> { active.where(access_level: OWNER) }
+  scope :owners_and_masters,  -> { active.where(access_level: [OWNER, MASTER]) }
 
   before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
 
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 19a684d..2dcf7f8 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -10,14 +10,18 @@ class MergeRequest < ActiveRecord::Base
   belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
   belongs_to :merge_user, class_name: "User"
 
-  has_one :merge_request_diff, dependent: :destroy
+  has_many :merge_request_diffs, dependent: :destroy
+  has_one :merge_request_diff,
+    -> { order('merge_request_diffs.id DESC') }
 
   has_many :events, as: :target, dependent: :destroy
 
+  has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
+
   serialize :merge_params, Hash
 
-  after_create :create_merge_request_diff, unless: :importing?
-  after_update :update_merge_request_diff
+  after_create :ensure_merge_request_diff, unless: :importing?
+  after_update :reload_diff_if_branch_changed
 
   delegate :commits, :real_size, to: :merge_request_diff, prefix: nil
 
@@ -89,13 +93,13 @@ class MergeRequest < ActiveRecord::Base
     end
   end
 
-  validates :source_project, presence: true, unless: [:allow_broken, :importing?]
+  validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
   validates :source_branch, presence: true
   validates :target_project, presence: true
   validates :target_branch, presence: true
   validates :merge_user, presence: true, if: :merge_when_build_succeeds?
-  validate :validate_branches, unless: [:allow_broken, :importing?]
-  validate :validate_fork
+  validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
+  validate :validate_fork, unless: :closed_without_fork?
 
   scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
   scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
@@ -170,10 +174,10 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def diffs(diff_options = nil)
-    if self.compare
-      self.compare.diffs(diff_options)
+    if compare
+      compare.diffs(diff_options)
     else
-      Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options)
+      merge_request_diff.diffs(diff_options)
     end
   end
 
@@ -184,8 +188,8 @@ class MergeRequest < ActiveRecord::Base
   def diff_base_commit
     if persisted?
       merge_request_diff.base_commit
-    elsif diff_start_commit && diff_head_commit
-      self.target_project.merge_base_commit(diff_start_sha, diff_head_sha)
+    else
+      branch_merge_base_commit
     end
   end
 
@@ -238,12 +242,21 @@ class MergeRequest < ActiveRecord::Base
 
   def source_branch_head
     source_branch_ref = @source_branch_sha || source_branch
-    source_project.repository.commit(source_branch) if source_branch_ref
+    source_project.repository.commit(source_branch_ref) if source_branch_ref
   end
 
   def target_branch_head
     target_branch_ref = @target_branch_sha || target_branch
-    target_project.repository.commit(target_branch) if target_branch_ref
+    target_project.repository.commit(target_branch_ref) if target_branch_ref
+  end
+
+  def branch_merge_base_commit
+    start_sha = target_branch_sha
+    head_sha  = source_branch_sha
+
+    if start_sha && head_sha
+      target_project.merge_base_commit(start_sha, head_sha)
+    end
   end
 
   def target_branch_sha
@@ -267,16 +280,16 @@ class MergeRequest < ActiveRecord::Base
   # Return diff_refs instance trying to not touch the git repository
   def diff_sha_refs
     if merge_request_diff && merge_request_diff.diff_refs_by_sha?
-      return Gitlab::Diff::DiffRefs.new(
-        base_sha:  merge_request_diff.base_commit_sha,
-        start_sha: merge_request_diff.start_commit_sha,
-        head_sha:  merge_request_diff.head_commit_sha
-      )
+      merge_request_diff.diff_refs
     else
       diff_refs
     end
   end
 
+  def branch_merge_base_sha
+    branch_merge_base_commit.try(:sha)
+  end
+
   def validate_branches
     if target_project == source_project && target_branch == source_branch
       errors.add :branch_conflict, "You can not use same project/branch for source and target"
@@ -294,36 +307,59 @@ class MergeRequest < ActiveRecord::Base
 
   def validate_fork
     return true unless target_project && source_project
+    return true if target_project == source_project
+    return true unless forked_source_project_missing?
 
-    if target_project == source_project
-      true
-    else
-      # If source and target projects are different
-      # we should check if source project is actually a fork of target project
-      if source_project.forked_from?(target_project)
-        true
-      else
-        errors.add :validate_fork,
-                   'Source project is not a fork of target project'
-      end
-    end
+    errors.add :validate_fork,
+               'Source project is not a fork of the target project'
   end
 
-  def update_merge_request_diff
+  def closed_without_fork?
+    closed? && forked_source_project_missing?
+  end
+
+  def closed_without_source_project?
+    closed? && !source_project
+  end
+
+  def forked_source_project_missing?
+    return false unless for_fork?
+    return true unless source_project
+
+    !source_project.forked_from?(target_project)
+  end
+
+  def reopenable?
+    return false if closed_without_fork? || closed_without_source_project? || merged?
+
+    closed?
+  end
+
+  def ensure_merge_request_diff
+    merge_request_diff || create_merge_request_diff
+  end
+
+  def create_merge_request_diff
+    merge_request_diffs.create
+    reload_merge_request_diff
+  end
+
+  def reload_merge_request_diff
+    merge_request_diff(true)
+  end
+
+  def reload_diff_if_branch_changed
     if source_branch_changed? || target_branch_changed?
       reload_diff
     end
   end
 
   def reload_diff
-    return unless merge_request_diff && open?
+    return unless open?
 
     old_diff_refs = self.diff_refs
-
-    merge_request_diff.reload_content
-
+    create_merge_request_diff
     MergeRequests::MergeRequestDiffCacheService.new.execute(self)
-
     new_diff_refs = self.diff_refs
 
     update_diff_notes_positions(
@@ -387,7 +423,7 @@ class MergeRequest < ActiveRecord::Base
   def can_remove_source_branch?(current_user)
     !source_project.protected_branch?(source_branch) &&
       !source_project.root_ref?(source_branch) &&
-      Ability.abilities.allowed?(current_user, :push_code, source_project) &&
+      Ability.allowed?(current_user, :push_code, source_project) &&
       diff_head_commit == source_branch_head
   end
 
@@ -467,6 +503,19 @@ class MergeRequest < ActiveRecord::Base
     target_project
   end
 
+  # If the merge request closes any issues, save this information in the
+  # `MergeRequestsClosingIssues` model. This is a performance optimization.
+  # Calculating this information for a number of merge requests requires
+  # running `ReferenceExtractor` on each of them separately.
+  def cache_merge_request_closes_issues!(current_user = self.author)
+    transaction do
+      self.merge_requests_closing_issues.delete_all
+      closes_issues(current_user).each do |issue|
+        self.merge_requests_closing_issues.create!(issue: issue)
+      end
+    end
+  end
+
   def closes_issue?(issue)
     closes_issues.include?(issue)
   end
@@ -474,7 +523,8 @@ class MergeRequest < ActiveRecord::Base
   # Return the set of issues that will be closed if this merge request is accepted.
   def closes_issues(current_user = self.author)
     if target_branch == project.default_branch
-      messages = commits.map(&:safe_message) << description
+      messages = [description]
+      messages.concat(commits.map(&:safe_message)) if merge_request_diff
 
       Gitlab::ClosingIssueExtractor.new(project, current_user).
         closed_by_message(messages.join("\n"))
@@ -618,11 +668,14 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def environments
-    return unless diff_head_commit
+    return [] unless diff_head_commit
 
-    target_project.environments.select do |environment|
-      environment.includes_commit?(diff_head_commit)
-    end
+    environments = source_project.environments_for(
+      source_branch, diff_head_commit)
+    environments += target_project.environments_for(
+      target_branch, diff_head_commit, with_tags: true)
+
+    environments.uniq
   end
 
   def state_human_name
@@ -705,14 +758,29 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def pipeline
-    @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project
+    return unless diff_head_sha && source_project
+
+    @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
   end
 
   def all_pipelines
-    @all_pipelines ||=
-      if diff_head_sha && source_project
-        source_project.pipelines.order(id: :desc).where(sha: commits_sha, ref: source_branch)
-      end
+    return unless source_project
+
+    @all_pipelines ||= begin
+      sha = if persisted?
+              all_commits_sha
+            else
+              diff_head_sha
+            end
+
+      source_project.pipelines.order(id: :desc).
+        where(sha: sha, ref: source_branch)
+    end
+  end
+
+  # Note that this could also return SHA from now dangling commits
+  def all_commits_sha
+    merge_request_diffs.flat_map(&:commits_sha).uniq
   end
 
   def merge_commit
diff --git a/app/models/merge_request/metrics.rb b/app/models/merge_request/metrics.rb
new file mode 100644
index 0000000..99c49a0
--- /dev/null
+++ b/app/models/merge_request/metrics.rb
@@ -0,0 +1,11 @@
+class MergeRequest::Metrics < ActiveRecord::Base
+  belongs_to :merge_request
+
+  def record!
+    if merge_request.merged? && self.merged_at.blank?
+      self.merged_at = Time.now
+    end
+
+    self.save
+  end
+end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 32cc6a3..36b8b70 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -8,8 +8,6 @@ class MergeRequestDiff < ActiveRecord::Base
 
   belongs_to :merge_request
 
-  delegate :source_branch_sha, :target_branch_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil
-
   state_machine :state, initial: :empty do
     state :collected
     state :overflow
@@ -24,12 +22,51 @@ class MergeRequestDiff < ActiveRecord::Base
   serialize :st_commits
   serialize :st_diffs
 
-  after_create :reload_content, unless: :importing?
-  after_save :keep_around_commits, unless: :importing?
+  # All diff information is collected from repository after object is created.
+  # It allows you to override variables like head_commit_sha before getting diff.
+  after_create :save_git_content, unless: :importing?
+
+  def self.select_without_diff
+    select(column_names - ['st_diffs'])
+  end
+
+  def st_commits
+    super || []
+  end
 
-  def reload_content
+  # Collect information about commits and diff from repository
+  # and save it to the database as serialized data
+  def save_git_content
+    ensure_commits_sha
+    save_commits
     reload_commits
-    reload_diffs
+    save_diffs
+    keep_around_commits
+  end
+
+  def ensure_commits_sha
+    merge_request.fetch_ref
+    self.start_commit_sha ||= merge_request.target_branch_sha
+    self.head_commit_sha  ||= merge_request.source_branch_sha
+    self.base_commit_sha  ||= find_base_sha
+    save
+  end
+
+  # Override head_commit_sha to keep compatibility with merge request diff
+  # created before version 8.4 that does not store head_commit_sha in separate db field.
+  def head_commit_sha
+    if persisted? && super.nil?
+      last_commit.try(:sha)
+    else
+      super
+    end
+  end
+
+  # This method will rely on repository branch sha
+  # in case start_commit_sha is nil. Its necesarry for old merge request diff
+  # created before version 8.4 to work
+  def safe_start_commit_sha
+    start_commit_sha || merge_request.target_branch_sha
   end
 
   def size
@@ -38,14 +75,11 @@ class MergeRequestDiff < ActiveRecord::Base
 
   def raw_diffs(options = {})
     if options[:ignore_whitespace_change]
-      @raw_diffs_no_whitespace ||= begin
-        compare = Gitlab::Git::Compare.new(
+      @diffs_no_whitespace ||=
+        Gitlab::Git::Compare.new(
           repository.raw_repository,
-          self.start_commit_sha || self.target_branch_sha,
-          self.head_commit_sha || self.source_branch_sha,
-        )
-        compare.diffs(options)
-      end
+          safe_start_commit_sha,
+          head_commit_sha).diffs(options)
     else
       @raw_diffs ||= {}
       @raw_diffs[options] ||= load_diffs(st_diffs, options)
@@ -53,7 +87,12 @@ class MergeRequestDiff < ActiveRecord::Base
   end
 
   def commits
-    @commits ||= load_commits(st_commits || [])
+    @commits ||= load_commits(st_commits)
+  end
+
+  def reload_commits
+    @commits = nil
+    commits
   end
 
   def last_commit
@@ -65,55 +104,72 @@ class MergeRequestDiff < ActiveRecord::Base
   end
 
   def base_commit
-    return unless self.base_commit_sha
+    return unless base_commit_sha
 
-    project.commit(self.base_commit_sha)
+    project.commit(base_commit_sha)
   end
 
   def start_commit
-    return unless self.start_commit_sha
+    return unless start_commit_sha
 
-    project.commit(self.start_commit_sha)
+    project.commit(start_commit_sha)
   end
 
   def head_commit
-    return last_commit unless self.head_commit_sha
+    return unless head_commit_sha
+
+    project.commit(head_commit_sha)
+  end
+
+  def commits_sha
+    if @commits
+      commits.map(&:sha)
+    else
+      st_commits.map { |commit| commit[:id] }
+    end
+  end
 
-    project.commit(self.head_commit_sha)
+  def diff_refs
+    return unless start_commit_sha || base_commit_sha
+
+    Gitlab::Diff::DiffRefs.new(
+      base_sha:  base_commit_sha,
+      start_sha: start_commit_sha,
+      head_sha:  head_commit_sha
+    )
   end
 
   def diff_refs_by_sha?
     base_commit_sha? && head_commit_sha? && start_commit_sha?
   end
 
-  def compare
-    @compare ||=
-      begin
-        # Update ref for merge request
-        merge_request.fetch_ref
-
-        Gitlab::Git::Compare.new(
-          repository.raw_repository,
-          self.target_branch_sha,
-          self.source_branch_sha
-        )
-      end
+  def diffs(diff_options = nil)
+    Gitlab::Diff::FileCollection::MergeRequestDiff.new(self, diff_options: diff_options)
   end
 
-  private
+  def project
+    merge_request.target_project
+  end
 
-  # Collect array of Git::Commit objects
-  # between target and source branches
-  def unmerged_commits
-    commits = compare.commits
+  def compare
+    @compare ||=
+      Gitlab::Git::Compare.new(
+        repository.raw_repository,
+        safe_start_commit_sha,
+        head_commit_sha
+      )
+  end
 
-    if commits.present?
-      commits = Commit.decorate(commits, merge_request.source_project).reverse
-    end
+  def latest?
+    self == merge_request.merge_request_diff
+  end
 
-    commits
+  def compare_with(sha)
+    CompareService.new.execute(project, head_commit_sha, project, sha)
   end
 
+  private
+
   def dump_commits(commits)
     commits.map(&:to_hash)
   end
@@ -122,26 +178,21 @@ class MergeRequestDiff < ActiveRecord::Base
     array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) }
   end
 
-  # Reload all commits related to current merge request from repo
+  # Load all commits related to current merge request diff from repo
   # and save it as array of hashes in st_commits db field
-  def reload_commits
+  def save_commits
     new_attributes = {}
 
-    commit_objects = unmerged_commits
+    commits = compare.commits
 
-    if commit_objects.present?
-      new_attributes[:st_commits] = dump_commits(commit_objects)
+    if commits.present?
+      commits = Commit.decorate(commits, merge_request.source_project).reverse
+      new_attributes[:st_commits] = dump_commits(commits)
     end
 
     update_columns_serialized(new_attributes)
   end
 
-  # Collect array of Git::Diff objects
-  # between target and source branches
-  def unmerged_diffs
-    compare.diffs(Commit.max_diff_options)
-  end
-
   def dump_diffs(diffs)
     if diffs.respond_to?(:map)
       diffs.map(&:to_hash)
@@ -162,16 +213,16 @@ class MergeRequestDiff < ActiveRecord::Base
     end
   end
 
-  # Reload diffs between branches related to current merge request from repo
+  # Load diffs between branches related to current merge request diff from repo
   # and save it as array of hashes in st_diffs db field
-  def reload_diffs
+  def save_diffs
     new_attributes = {}
     new_diffs = []
 
     if commits.size.zero?
       new_attributes[:state] = :empty
     else
-      diff_collection = unmerged_diffs
+      diff_collection = compare.diffs(Commit.max_diff_options)
 
       if diff_collection.overflow?
         # Set our state to 'overflow' to make the #empty? and #collected?
@@ -188,32 +239,17 @@ class MergeRequestDiff < ActiveRecord::Base
     end
 
     new_attributes[:st_diffs] = new_diffs
-
-    new_attributes[:start_commit_sha] = self.target_branch_sha
-    new_attributes[:head_commit_sha] = self.source_branch_sha
-    new_attributes[:base_commit_sha] = branch_base_sha
-
     update_columns_serialized(new_attributes)
-
-    keep_around_commits
-  end
-
-  def project
-    merge_request.target_project
   end
 
   def repository
     project.repository
   end
 
-  def branch_base_commit
-    return unless self.source_branch_sha && self.target_branch_sha
-
-    project.merge_base_commit(self.source_branch_sha, self.target_branch_sha)
-  end
+  def find_base_sha
+    return unless head_commit_sha && start_commit_sha
 
-  def branch_base_sha
-    branch_base_commit.try(:sha)
+    project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha)
   end
 
   def utf8_st_diffs
@@ -248,8 +284,8 @@ class MergeRequestDiff < ActiveRecord::Base
   end
 
   def keep_around_commits
-    repository.keep_around(target_branch_sha)
-    repository.keep_around(source_branch_sha)
-    repository.keep_around(branch_base_sha)
+    repository.keep_around(start_commit_sha)
+    repository.keep_around(head_commit_sha)
+    repository.keep_around(base_commit_sha)
   end
 end
diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb
new file mode 100644
index 0000000..ab597c3
--- /dev/null
+++ b/app/models/merge_requests_closing_issues.rb
@@ -0,0 +1,7 @@
+class MergeRequestsClosingIssues < ActiveRecord::Base
+  belongs_to :merge_request
+  belongs_to :issue
+
+  validates :merge_request_id, uniqueness: { scope: :issue_id }, presence: true
+  validates :issue_id, presence: true
+end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 7c29d27..919b3b1 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -141,6 +141,11 @@ class Namespace < ActiveRecord::Base
     projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id)
   end
 
+  def lfs_enabled?
+    # User namespace will always default to the global setting
+    Gitlab.config.lfs.enabled
+  end
+
   private
 
   def repository_storage_paths
diff --git a/app/models/project.rb b/app/models/project.rb
index 8cf093b..7265cb5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -11,24 +11,23 @@ class Project < ActiveRecord::Base
   include AfterCommitQueue
   include CaseSensitivity
   include TokenAuthenticatable
+  include ProjectFeaturesCompatibility
 
   extend Gitlab::ConfigHelper
 
   UNKNOWN_IMPORT_URL = 'http://unknown.git'
 
+  delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, to: :project_feature, allow_nil: true
+
   default_value_for :archived, false
   default_value_for :visibility_level, gitlab_config_features.visibility_level
-  default_value_for :issues_enabled, gitlab_config_features.issues
-  default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
-  default_value_for :builds_enabled, gitlab_config_features.builds
-  default_value_for :wiki_enabled, gitlab_config_features.wiki
-  default_value_for :snippets_enabled, gitlab_config_features.snippets
   default_value_for :container_registry_enabled, gitlab_config_features.container_registry
   default_value_for(:repository_storage) { current_application_settings.repository_storage }
   default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
 
   after_create :ensure_dir_exist
   after_save :ensure_dir_exist, if: :namespace_id_changed?
+  after_initialize :setup_project_feature
 
   # set last_activity_at to the same as created_at
   after_create :set_last_activity_at
@@ -59,13 +58,13 @@ class Project < ActiveRecord::Base
 
   # Relations
   belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
-  belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
+  belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
   belongs_to :namespace
 
-  has_one :board, dependent: :destroy
-
   has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
 
+  has_one :board, dependent: :destroy
+
   # Project services
   has_many :services
   has_one :campfire_service, dependent: :destroy
@@ -130,6 +129,7 @@ class Project < ActiveRecord::Base
   has_many :notification_settings, dependent: :destroy, as: :source
 
   has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
+  has_one :project_feature, dependent: :destroy
 
   has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id
   has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
@@ -142,6 +142,7 @@ class Project < ActiveRecord::Base
   has_many :deployments, dependent: :destroy
 
   accepts_nested_attributes_for :variables, allow_destroy: true
+  accepts_nested_attributes_for :project_feature
 
   delegate :name, to: :owner, allow_nil: true, prefix: true
   delegate :members, to: :team, prefix: true
@@ -159,8 +160,6 @@ class Project < ActiveRecord::Base
     length: { within: 0..255 },
     format: { with: Gitlab::Regex.project_path_regex,
               message: Gitlab::Regex.project_path_regex_message }
-  validates :issues_enabled, :merge_requests_enabled,
-            :wiki_enabled, inclusion: { in: [true, false] }
   validates :namespace, presence: true
   validates_uniqueness_of :name, scope: :namespace_id
   validates_uniqueness_of :path, scope: :namespace_id
@@ -196,6 +195,9 @@ class Project < ActiveRecord::Base
   scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
   scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
 
+  scope :with_builds_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0') }
+  scope :with_issues_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.issues_access_level IS NULL or project_features.issues_access_level > 0') }
+
   scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
   scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
 
@@ -390,6 +392,12 @@ class Project < ActiveRecord::Base
     end
   end
 
+  def lfs_enabled?
+    return namespace.lfs_enabled? if self[:lfs_enabled].nil?
+
+    self[:lfs_enabled] && Gitlab.config.lfs.enabled
+  end
+
   def repository_storage_path
     Gitlab.config.repositories.storages[repository_storage]
   end
@@ -436,7 +444,7 @@ class Project < ActiveRecord::Base
 
   # ref can't be HEAD, can only be branch/tag name or SHA
   def latest_successful_builds_for(ref = default_branch)
-    latest_pipeline = pipelines.latest_successful_for(ref).first
+    latest_pipeline = pipelines.latest_successful_for(ref)
 
     if latest_pipeline
       latest_pipeline.builds.latest.with_artifacts
@@ -680,6 +688,10 @@ class Project < ActiveRecord::Base
     update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
   end
 
+  def has_wiki?
+    wiki_enabled? || has_external_wiki?
+  end
+
   def external_wiki
     if has_external_wiki.nil?
       cache_has_external_wiki # Populate
@@ -1035,6 +1047,7 @@ class Project < ActiveRecord::Base
                                         "refs/heads/#{branch}",
                                         force: true)
     repository.copy_gitattributes(branch)
+    repository.expire_avatar_cache(branch)
     reload_default_branch
   end
 
@@ -1095,16 +1108,21 @@ class Project < ActiveRecord::Base
     !namespace.share_with_group_lock
   end
 
-  def pipeline(sha, ref)
+  def pipeline_for(ref, sha = nil)
+    sha ||= commit(ref).try(:sha)
+
+    return unless sha
+
     pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
   end
 
-  def ensure_pipeline(sha, ref, current_user = nil)
-    pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user)
+  def ensure_pipeline(ref, sha, current_user = nil)
+    pipeline_for(ref, sha) ||
+      pipelines.create(sha: sha, ref: ref, user: current_user)
   end
 
   def enable_ci
-    self.builds_enabled = true
+    project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
   end
 
   def any_runners?(&block)
@@ -1119,12 +1137,6 @@ class Project < ActiveRecord::Base
     self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
   end
 
-  # TODO (ayufan): For now we use runners_token (backward compatibility)
-  # In 8.4 every build will have its own individual token valid for time of build
-  def valid_build_token?(token)
-    self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
-  end
-
   def build_coverage_enabled?
     build_coverage_regex.present?
   end
@@ -1269,8 +1281,45 @@ class Project < ActiveRecord::Base
     end
   end
 
+  def pushes_since_gc
+    Gitlab::Redis.with { |redis| redis.get(pushes_since_gc_redis_key).to_i }
+  end
+
+  def increment_pushes_since_gc
+    Gitlab::Redis.with { |redis| redis.incr(pushes_since_gc_redis_key) }
+  end
+
+  def reset_pushes_since_gc
+    Gitlab::Redis.with { |redis| redis.del(pushes_since_gc_redis_key) }
+  end
+
+  def environments_for(ref, commit, with_tags: false)
+    environment_ids = deployments.group(:environment_id).
+      select(:environment_id)
+
+    environment_ids =
+      if with_tags
+        environment_ids.where('ref=? OR tag IS TRUE', ref)
+      else
+        environment_ids.where(ref: ref)
+      end
+
+    environments.where(id: environment_ids).select do |environment|
+      environment.includes_commit?(commit)
+    end
+  end
+
   private
 
+  def pushes_since_gc_redis_key
+    "projects/#{id}/pushes_since_gc"
+  end
+
+  # Prevents the creation of project_feature record for every project
+  def setup_project_feature
+    build_project_feature unless project_feature
+  end
+
   def default_branch_protected?
     current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
       current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
new file mode 100644
index 0000000..8c9534c
--- /dev/null
+++ b/app/models/project_feature.rb
@@ -0,0 +1,69 @@
+class ProjectFeature < ActiveRecord::Base
+  # == Project features permissions
+  #
+  # Grants access level to project tools
+  #
+  # Tools can be enabled only for users, everyone or disabled
+  # Access control is made only for non private projects
+  #
+  # levels:
+  #
+  # Disabled: not enabled for anyone
+  # Private:  enabled only for team members
+  # Enabled:  enabled for everyone able to access the project
+  #
+
+  # Permision levels
+  DISABLED = 0
+  PRIVATE  = 10
+  ENABLED  = 20
+
+  FEATURES = %i(issues merge_requests wiki snippets builds)
+
+  belongs_to :project
+
+  default_value_for :builds_access_level,         value: ENABLED, allows_nil: false
+  default_value_for :issues_access_level,         value: ENABLED, allows_nil: false
+  default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
+  default_value_for :snippets_access_level,       value: ENABLED, allows_nil: false
+  default_value_for :wiki_access_level,           value: ENABLED, allows_nil: false
+
+  def feature_available?(feature, user)
+    raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature)
+
+    get_permission(user, public_send("#{feature}_access_level"))
+  end
+
+  def builds_enabled?
+    return true unless builds_access_level
+
+    builds_access_level > DISABLED
+  end
+
+  def wiki_enabled?
+    return true unless wiki_access_level
+
+    wiki_access_level > DISABLED
+  end
+
+  def merge_requests_enabled?
+    return true unless merge_requests_access_level
+
+    merge_requests_access_level > DISABLED
+  end
+
+  private
+
+  def get_permission(user, level)
+    case level
+    when DISABLED
+      false
+    when PRIVATE
+      user && (project.team.member?(user) || user.admin?)
+    when ENABLED
+      true
+    else
+      true
+    end
+  end
+end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index d7c986c..afebd3b 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -39,7 +39,7 @@ class HipchatService < Service
   end
 
   def supported_events
-    %w(push issue merge_request note tag_push build)
+    %w(push issue confidential_issue merge_request note tag_push build)
   end
 
   def execute(data)
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index abbc780..e1b9378 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -1,6 +1,6 @@
 class SlackService < Service
   prop_accessor :webhook, :username, :channel
-  boolean_accessor :notify_only_broken_builds
+  boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
   validates :webhook, presence: true, url: true, if: :activated?
 
   def initialize_properties
@@ -10,6 +10,7 @@ class SlackService < Service
     if properties.nil?
       self.properties = {}
       self.notify_only_broken_builds = true
+      self.notify_only_broken_pipelines = true
     end
   end
 
@@ -38,13 +39,15 @@ class SlackService < Service
         { type: 'text', name: 'username', placeholder: 'username' },
         { type: 'text', name: 'channel', placeholder: "#general" },
         { type: 'checkbox', name: 'notify_only_broken_builds' },
+        { type: 'checkbox', name: 'notify_only_broken_pipelines' },
       ]
 
     default_fields + build_event_channels
   end
 
   def supported_events
-    %w(push issue merge_request note tag_push build wiki_page)
+    %w[push issue confidential_issue merge_request note tag_push
+       build pipeline wiki_page]
   end
 
   def execute(data)
@@ -62,32 +65,22 @@ class SlackService < Service
     # 'close' action. Ignore update events for now to prevent duplicate
     # messages from arriving.
 
-    message = \
-      case object_kind
-      when "push", "tag_push"
-        PushMessage.new(data)
-      when "issue"
-        IssueMessage.new(data) unless is_update?(data)
-      when "merge_request"
-        MergeMessage.new(data) unless is_update?(data)
-      when "note"
-        NoteMessage.new(data)
-      when "build"
-        BuildMessage.new(data) if should_build_be_notified?(data)
-      when "wiki_page"
-        WikiPageMessage.new(data)
-      end
-
-    opt = {}
-
-    event_channel = get_channel_field(object_kind) || channel
-
-    opt[:channel] = event_channel if event_channel
-    opt[:username] = username if username
+    message = get_message(object_kind, data)
 
     if message
+      opt = {}
+
+      event_channel = get_channel_field(object_kind) || channel
+
+      opt[:channel] = event_channel if event_channel
+      opt[:username] = username if username
+
       notifier = Slack::Notifier.new(webhook, opt)
       notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
+
+      true
+    else
+      false
     end
   end
 
@@ -105,6 +98,25 @@ class SlackService < Service
 
   private
 
+  def get_message(object_kind, data)
+    case object_kind
+    when "push", "tag_push"
+      PushMessage.new(data)
+    when "issue"
+      IssueMessage.new(data) unless is_update?(data)
+    when "merge_request"
+      MergeMessage.new(data) unless is_update?(data)
+    when "note"
+      NoteMessage.new(data)
+    when "build"
+      BuildMessage.new(data) if should_build_be_notified?(data)
+    when "pipeline"
+      PipelineMessage.new(data) if should_pipeline_be_notified?(data)
+    when "wiki_page"
+      WikiPageMessage.new(data)
+    end
+  end
+
   def get_channel_field(event)
     field_name = event_channel_name(event)
     self.public_send(field_name)
@@ -142,6 +154,17 @@ class SlackService < Service
       false
     end
   end
+
+  def should_pipeline_be_notified?(data)
+    case data[:object_attributes][:status]
+    when 'success'
+      !notify_only_broken_pipelines?
+    when 'failed'
+      true
+    else
+      false
+    end
+  end
 end
 
 require "slack_service/issue_message"
@@ -149,4 +172,5 @@ require "slack_service/push_message"
 require "slack_service/merge_message"
 require "slack_service/note_message"
 require "slack_service/build_message"
+require "slack_service/pipeline_message"
 require "slack_service/wiki_page_message"
diff --git a/app/models/project_services/slack_service/build_message.rb b/app/models/project_services/slack_service/build_message.rb
index 69c21b3..0fca426 100644
--- a/app/models/project_services/slack_service/build_message.rb
+++ b/app/models/project_services/slack_service/build_message.rb
@@ -9,7 +9,7 @@ class SlackService
     attr_reader :user_name
     attr_reader :duration
 
-    def initialize(params, commit = true)
+    def initialize(params)
       @sha = params[:sha]
       @ref_type = params[:tag] ? 'tag' : 'branch'
       @ref = params[:ref]
@@ -36,7 +36,7 @@ class SlackService
 
     def message
       "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
-    end   
+    end
 
     def format(string)
       Slack::Notifier::LinkFormatter.format(string)
diff --git a/app/models/project_services/slack_service/pipeline_message.rb b/app/models/project_services/slack_service/pipeline_message.rb
new file mode 100644
index 0000000..f06b356
--- /dev/null
+++ b/app/models/project_services/slack_service/pipeline_message.rb
@@ -0,0 +1,79 @@
+class SlackService
+  class PipelineMessage < BaseMessage
+    attr_reader :sha, :ref_type, :ref, :status, :project_name, :project_url,
+                :user_name, :duration, :pipeline_id
+
+    def initialize(data)
+      pipeline_attributes = data[:object_attributes]
+      @sha = pipeline_attributes[:sha]
+      @ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
+      @ref = pipeline_attributes[:ref]
+      @status = pipeline_attributes[:status]
+      @duration = pipeline_attributes[:duration]
+      @pipeline_id = pipeline_attributes[:id]
+
+      @project_name = data[:project][:path_with_namespace]
+      @project_url = data[:project][:web_url]
+      @user_name = data[:commit] && data[:commit][:author_name]
+    end
+
+    def pretext
+      ''
+    end
+
+    def fallback
+      format(message)
+    end
+
+    def attachments
+      [{ text: format(message), color: attachment_color }]
+    end
+
+    private
+
+    def message
+      "#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
+    end
+
+    def format(string)
+      Slack::Notifier::LinkFormatter.format(string)
+    end
+
+    def humanized_status
+      case status
+      when 'success'
+        'passed'
+      else
+        status
+      end
+    end
+
+    def attachment_color
+      if status == 'success'
+        'good'
+      else
+        'danger'
+      end
+    end
+
+    def branch_url
+      "#{project_url}/commits/#{ref}"
+    end
+
+    def branch_link
+      "[#{ref}](#{branch_url})"
+    end
+
+    def project_link
+      "[#{project_name}](#{project_url})"
+    end
+
+    def pipeline_url
+      "#{project_url}/pipelines/#{pipeline_id}"
+    end
+
+    def pipeline_link
+      "[#{Commit.truncate_sha(sha)}](#{pipeline_url})"
+    end
+  end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index bdc3b9d..5155722 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -120,8 +120,21 @@ class Repository
     commits
   end
 
-  def find_branch(name)
-    raw_repository.branches.find { |branch| branch.name == name }
+  def find_branch(name, fresh_repo: true)
+    # Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may
+    # cause unintended side effects. Because finding a branch is a read-only operation, we can safely instantiate
+    # a new repo here to ensure a consistent state to avoid a libgit2 bug where concurrent access (e.g. via git gc)
+    # may cause the branch to "disappear" erroneously or have the wrong SHA.
+    #
+    # See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392
+    raw_repo =
+      if fresh_repo
+        Gitlab::Git::Repository.new(path_to_repo)
+      else
+        raw_repository
+      end
+
+    raw_repo.find_branch(name)
   end
 
   def find_tag(name)
@@ -136,7 +149,7 @@ class Repository
     return false unless target
 
     GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
-      rugged.branches.create(branch_name, target)
+      update_ref!(ref, target, oldrev)
     end
 
     after_create_branch
@@ -168,7 +181,7 @@ class Repository
     ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
 
     GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
-      rugged.branches.delete(branch_name)
+      update_ref!(ref, newrev, oldrev)
     end
 
     after_remove_branch
@@ -202,6 +215,21 @@ class Repository
     rugged.references.exist?(ref)
   end
 
+  def update_ref!(name, newrev, oldrev)
+    # We use 'git update-ref' because libgit2/rugged currently does not
+    # offer 'compare and swap' ref updates. Without compare-and-swap we can
+    # (and have!) accidentally reset the ref to an earlier state, clobbering
+    # commits. See also https://github.com/libgit2/libgit2/issues/1534.
+    command = %w[git update-ref --stdin -z]
+    _, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin|
+      stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00")
+    end
+
+    return if status.zero?
+
+    raise CommitError.new("Could not update branch #{name.sub('refs/heads/', '')}. Please refresh and try again.")
+  end
+
   # Makes sure a commit is kept around when Git garbage collection runs.
   # Git GC will delete commits from the repository that are no longer in any
   # branches or tags, but we want to keep some of these commits around, for
@@ -728,64 +756,61 @@ class Repository
     @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
   end
 
-  def commit_dir(user, path, message, branch)
-    commit_with_hooks(user, branch) do |ref|
-      committer = user_to_committer(user)
-      options = {}
-      options[:committer] = committer
-      options[:author] = committer
-
-      options[:commit] = {
-        message: message,
-        branch: ref,
-        update_ref: false,
+  def commit_dir(user, path, message, branch, author_email: nil, author_name: nil)
+    update_branch_with_hooks(user, branch) do |ref|
+      options = {
+        commit: {
+          branch: ref,
+          message: message,
+          update_ref: false
+        }
       }
 
+      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
+
       raw_repository.mkdir(path, options)
     end
   end
 
-  def commit_file(user, path, content, message, branch, update)
-    commit_with_hooks(user, branch) do |ref|
-      committer = user_to_committer(user)
-      options = {}
-      options[:committer] = committer
-      options[:author] = committer
-      options[:commit] = {
-        message: message,
-        branch: ref,
-        update_ref: false,
+  def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil)
+    update_branch_with_hooks(user, branch) do |ref|
+      options = {
+        commit: {
+          branch: ref,
+          message: message,
+          update_ref: false
+        },
+        file: {
+          content: content,
+          path: path,
+          update: update
+        }
       }
 
-      options[:file] = {
-        content: content,
-        path: path,
-        update: update
-      }
+      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
 
       Gitlab::Git::Blob.commit(raw_repository, options)
     end
   end
 
-  def update_file(user, path, content, branch:, previous_path:, message:)
-    commit_with_hooks(user, branch) do |ref|
-      committer = user_to_committer(user)
-      options = {}
-      options[:committer] = committer
-      options[:author] = committer
-      options[:commit] = {
-        message: message,
-        branch: ref,
-        update_ref: false
+  def update_file(user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil)
+    update_branch_with_hooks(user, branch) do |ref|
+      options = {
+        commit: {
+          branch: ref,
+          message: message,
+          update_ref: false
+        },
+        file: {
+          content: content,
+          path: path,
+          update: true
+        }
       }
 
-      options[:file] = {
-        content: content,
-        path: path,
-        update: true
-      }
+      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
 
-      if previous_path
+      if previous_path && previous_path != path
         options[:file][:previous_path] = previous_path
         Gitlab::Git::Blob.rename(raw_repository, options)
       else
@@ -794,34 +819,39 @@ class Repository
     end
   end
 
-  def remove_file(user, path, message, branch)
-    commit_with_hooks(user, branch) do |ref|
-      committer = user_to_committer(user)
-      options = {}
-      options[:committer] = committer
-      options[:author] = committer
-      options[:commit] = {
-        message: message,
-        branch: ref,
-        update_ref: false,
+  def remove_file(user, path, message, branch, author_email: nil, author_name: nil)
+    update_branch_with_hooks(user, branch) do |ref|
+      options = {
+        commit: {
+          branch: ref,
+          message: message,
+          update_ref: false
+        },
+        file: {
+          path: path
+        }
       }
 
-      options[:file] = {
-        path: path
-      }
+      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
 
       Gitlab::Git::Blob.remove(raw_repository, options)
     end
   end
 
-  def user_to_committer(user)
+  def get_committer_and_author(user, email: nil, name: nil)
+    committer = user_to_committer(user)
+    author = Gitlab::Git::committer_hash(email: email, name: name) || committer
+
     {
-      email: user.email,
-      name: user.name,
-      time: Time.now
+      author: author,
+      committer: committer
     }
   end
 
+  def user_to_committer(user)
+    Gitlab::Git::committer_hash(email: user.email, name: user.name)
+  end
+
   def can_be_merged?(source_sha, target_branch)
     our_commit = rugged.branches[target_branch].target
     their_commit = rugged.lookup(source_sha)
@@ -843,7 +873,7 @@ class Repository
     merge_index = rugged.merge_commits(our_commit, their_commit)
     return false if merge_index.conflicts?
 
-    commit_with_hooks(user, merge_request.target_branch) do
+    update_branch_with_hooks(user, merge_request.target_branch) do
       actual_options = options.merge(
         parents: [our_commit, their_commit],
         tree: merge_index.write_tree(rugged),
@@ -861,7 +891,7 @@ class Repository
 
     return false unless revert_tree_id
 
-    commit_with_hooks(user, base_branch) do
+    update_branch_with_hooks(user, base_branch) do
       committer = user_to_committer(user)
       source_sha = Rugged::Commit.create(rugged,
         message: commit.revert_message,
@@ -878,7 +908,7 @@ class Repository
 
     return false unless cherry_pick_tree_id
 
-    commit_with_hooks(user, base_branch) do
+    update_branch_with_hooks(user, base_branch) do
       committer = user_to_committer(user)
       source_sha = Rugged::Commit.create(rugged,
         message: commit.message,
@@ -894,7 +924,7 @@ class Repository
   end
 
   def resolve_conflicts(user, branch, params)
-    commit_with_hooks(user, branch) do
+    update_branch_with_hooks(user, branch) do
       committer = user_to_committer(user)
 
       Rugged::Commit.create(rugged, params.merge(author: committer, committer: committer))
@@ -962,54 +992,18 @@ class Repository
     Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
   end
 
-  def parse_search_result(result)
-    ref = nil
-    filename = nil
-    basename = nil
-    startline = 0
-
-    result.each_line.each_with_index do |line, index|
-      if line =~ /^.*:.*:\d+:/
-        ref, filename, startline = line.split(':')
-        startline = startline.to_i - index
-        extname = Regexp.escape(File.extname(filename))
-        basename = filename.sub(/#{extname}$/, '')
-        break
-      end
-    end
-
-    data = ""
-
-    result.each_line do |line|
-      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
-    end
-
-    OpenStruct.new(
-      filename: filename,
-      basename: basename,
-      ref: ref,
-      startline: startline,
-      data: data
-    )
-  end
-
   def fetch_ref(source_path, source_ref, target_ref)
     args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
     Gitlab::Popen.popen(args, path_to_repo)
   end
 
-  def commit_with_hooks(current_user, branch)
+  def update_branch_with_hooks(current_user, branch)
     update_autocrlf_option
 
-    oldrev = Gitlab::Git::BLANK_SHA
     ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
     target_branch = find_branch(branch)
     was_empty = empty?
 
-    if !was_empty && target_branch
-      oldrev = target_branch.target.id
-    end
-
     # Make commit
     newrev = yield(ref)
 
@@ -1017,24 +1011,19 @@ class Repository
       raise CommitError.new('Failed to create commit')
     end
 
+    if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil?
+      oldrev = Gitlab::Git::BLANK_SHA
+    else
+      oldrev = rugged.merge_base(newrev, target_branch.target.sha)
+    end
+
     GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
-      if was_empty || !target_branch
-        # Create branch
-        rugged.references.create(ref, newrev)
+      update_ref!(ref, newrev, oldrev)
 
+      if was_empty || !target_branch
         # If repo was empty expire cache
         after_create if was_empty
         after_create_branch
-      else
-        # Update head
-        current_head = find_branch(branch).target.id
-
-        # Make sure target branch was not changed during pre-receive hook
-        if current_head == oldrev
-          rugged.references.update(ref, newrev)
-        else
-          raise CommitError.new('Commit was rejected because branch received new push')
-        end
       end
     end
 
@@ -1065,7 +1054,7 @@ class Repository
 
     @avatar ||= cache.fetch(:avatar) do
       AVATAR_FILES.find do |file|
-        blob_at_branch('master', file)
+        blob_at_branch(root_ref, file)
       end
     end
   end
diff --git a/app/models/service.rb b/app/models/service.rb
index 09b4717..80de717 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -7,10 +7,12 @@ class Service < ActiveRecord::Base
   default_value_for :active, false
   default_value_for :push_events, true
   default_value_for :issues_events, true
+  default_value_for :confidential_issues_events, true
   default_value_for :merge_requests_events, true
   default_value_for :tag_push_events, true
   default_value_for :note_events, true
   default_value_for :build_events, true
+  default_value_for :pipeline_events, true
   default_value_for :wiki_page_events, true
 
   after_initialize :initialize_properties
@@ -33,6 +35,7 @@ class Service < ActiveRecord::Base
   scope :push_hooks, -> { where(push_events: true, active: true) }
   scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
   scope :issue_hooks, -> { where(issues_events: true, active: true) }
+  scope :confidential_issue_hooks, -> { where(confidential_issues_events: true, active: true) }
   scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
   scope :note_hooks, -> { where(note_events: true, active: true) }
   scope :build_hooks, -> { where(build_events: true, active: true) }
@@ -100,7 +103,7 @@ class Service < ActiveRecord::Base
   end
 
   def supported_events
-    %w(push tag_push issue merge_request wiki_page)
+    %w(push tag_push issue confidential_issue merge_request wiki_page)
   end
 
   def execute(data)
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 5ec9336..8a1730f 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -4,6 +4,7 @@ class Snippet < ActiveRecord::Base
   include Participable
   include Referable
   include Sortable
+  include Awardable
 
   default_value_for :visibility_level, Snippet::PRIVATE
 
diff --git a/app/models/user.rb b/app/models/user.rb
index ad3cfbc..6996740 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -433,7 +433,7 @@ class User < ActiveRecord::Base
   #
   # This logic is duplicated from `Ability#project_abilities` into a SQL form.
   def projects_where_can_admin_issues
-    authorized_projects(Gitlab::Access::REPORTER).non_archived.where.not(issues_enabled: false)
+    authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
   end
 
   def is_admin?
@@ -460,16 +460,12 @@ class User < ActiveRecord::Base
     can?(:create_group, nil)
   end
 
-  def abilities
-    Ability.abilities
-  end
-
   def can_select_namespace?
     several_namespaces? || admin
   end
 
   def can?(action, subject)
-    abilities.allowed?(self, action, subject)
+    Ability.allowed?(self, action, subject)
   end
 
   def first_name
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
new file mode 100644
index 0000000..118c100
--- /dev/null
+++ b/app/policies/base_policy.rb
@@ -0,0 +1,116 @@
+class BasePolicy
+  class RuleSet
+    attr_reader :can_set, :cannot_set
+    def initialize(can_set, cannot_set)
+      @can_set = can_set
+      @cannot_set = cannot_set
+    end
+
+    def size
+      to_set.size
+    end
+
+    def self.empty
+      new(Set.new, Set.new)
+    end
+
+    def can?(ability)
+      @can_set.include?(ability) && !@cannot_set.include?(ability)
+    end
+
+    def include?(ability)
+      can?(ability)
+    end
+
+    def to_set
+      @can_set - @cannot_set
+    end
+
+    def merge(other)
+      @can_set.merge(other.can_set)
+      @cannot_set.merge(other.cannot_set)
+    end
+
+    def can!(*abilities)
+      @can_set.merge(abilities)
+    end
+
+    def cannot!(*abilities)
+      @cannot_set.merge(abilities)
+    end
+
+    def freeze
+      @can_set.freeze
+      @cannot_set.freeze
+      super
+    end
+  end
+
+  def self.abilities(user, subject)
+    new(user, subject).abilities
+  end
+
+  def self.class_for(subject)
+    return GlobalPolicy if subject.nil?
+
+    subject.class.ancestors.each do |klass|
+      next unless klass.name
+
+      begin
+        policy_class = "#{klass.name}Policy".constantize
+
+        # NOTE: the < operator here tests whether policy_class
+        # inherits from BasePolicy
+        return policy_class if policy_class < BasePolicy
+      rescue NameError
+        nil
+      end
+    end
+
+    raise "no policy for #{subject.class.name}"
+  end
+
+  attr_reader :user, :subject
+  def initialize(user, subject)
+    @user = user
+    @subject = subject
+  end
+
+  def abilities
+    return RuleSet.empty if @user && @user.blocked?
+    return anonymous_abilities if @user.nil?
+    collect_rules { rules }
+  end
+
+  def anonymous_abilities
+    collect_rules { anonymous_rules }
+  end
+
+  def anonymous_rules
+    rules
+  end
+
+  def delegate!(new_subject)
+    @rule_set.merge(Ability.allowed(@user, new_subject))
+  end
+
+  def can?(rule)
+    @rule_set.can?(rule)
+  end
+
+  def can!(*rules)
+    @rule_set.can!(*rules)
+  end
+
+  def cannot!(*rules)
+    @rule_set.cannot!(*rules)
+  end
+
+  private
+
+  def collect_rules(&b)
+    @rule_set = RuleSet.empty
+    yield
+    @rule_set
+  end
+end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
new file mode 100644
index 0000000..2232e23
--- /dev/null
+++ b/app/policies/ci/build_policy.rb
@@ -0,0 +1,13 @@
+module Ci
+  class BuildPolicy < CommitStatusPolicy
+    def rules
+      super
+
+      # If we can't read build we should also not have that
+      # ability when looking at this in context of commit_status
+      %w(read create update admin).each do |rule|
+        cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build"
+      end
+    end
+  end
+end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
new file mode 100644
index 0000000..7edd383
--- /dev/null
+++ b/app/policies/ci/runner_policy.rb
@@ -0,0 +1,13 @@
+module Ci
+  class RunnerPolicy < BasePolicy
+    def rules
+      return unless @user
+
+      can! :assign_runner if @user.is_admin?
+
+      return if @subject.is_shared? || @subject.locked?
+
+      can! :assign_runner if @user.ci_authorized_runners.include?(@subject)
+    end
+  end
+end
diff --git a/app/policies/commit_status_policy.rb b/app/policies/commit_status_policy.rb
new file mode 100644
index 0000000..593df73
--- /dev/null
+++ b/app/policies/commit_status_policy.rb
@@ -0,0 +1,5 @@
+class CommitStatusPolicy < BasePolicy
+  def rules
+    delegate! @subject.project
+  end
+end
diff --git a/app/policies/deployment_policy.rb b/app/policies/deployment_policy.rb
new file mode 100644
index 0000000..163d070
--- /dev/null
+++ b/app/policies/deployment_policy.rb
@@ -0,0 +1,5 @@
+class DeploymentPolicy < BasePolicy
+  def rules
+    delegate! @subject.project
+  end
+end
diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb
new file mode 100644
index 0000000..f421956
--- /dev/null
+++ b/app/policies/environment_policy.rb
@@ -0,0 +1,5 @@
+class EnvironmentPolicy < BasePolicy
+  def rules
+    delegate! @subject.project
+  end
+end
diff --git a/app/policies/external_issue_policy.rb b/app/policies/external_issue_policy.rb
new file mode 100644
index 0000000..d9e28bd
--- /dev/null
+++ b/app/policies/external_issue_policy.rb
@@ -0,0 +1,5 @@
+class ExternalIssuePolicy < BasePolicy
+  def rules
+    delegate! @subject.project
+  end
+end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
new file mode 100644
index 0000000..3c2fbe6
--- /dev/null
+++ b/app/policies/global_policy.rb
@@ -0,0 +1,8 @@
+class GlobalPolicy < BasePolicy
+  def rules
+    return unless @user
+
+    can! :create_group if @user.can_create_group
+    can! :read_users_list
+  end
+end
diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb
new file mode 100644
index 0000000..6233552
--- /dev/null
+++ b/app/policies/group_member_policy.rb
@@ -0,0 +1,19 @@
+class GroupMemberPolicy < BasePolicy
+  def rules
+    return unless @user
+
+    target_user = @subject.user
+    group = @subject.group
+
+    return if group.last_owner?(target_user)
+
+    can_manage = Ability.allowed?(@user, :admin_group_member, group)
+
+    if can_manage
+      can! :update_group_member
+      can! :destroy_group_member
+    elsif @user == target_user
+      can! :destroy_group_member
+    end
+  end
+end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
new file mode 100644
index 0000000..97ff623
--- /dev/null
+++ b/app/policies/group_policy.rb
@@ -0,0 +1,45 @@
+class GroupPolicy < BasePolicy
+  def rules
+    can! :read_group if @subject.public?
+    return unless @user
+
+    globally_viewable = @subject.public? || (@subject.internal? && !@user.external?)
+    member = @subject.users.include?(@user)
+    owner = @user.admin? || @subject.has_owner?(@user)
+    master = owner || @subject.has_master?(@user)
+
+    can_read = false
+    can_read ||= globally_viewable
+    can_read ||= member
+    can_read ||= @user.admin?
+    can_read ||= GroupProjectsFinder.new(@subject).execute(@user).any?
+    can! :read_group if can_read
+
+    # Only group masters and group owners can create new projects
+    if master
+      can! :create_projects
+      can! :admin_milestones
+    end
+
+    # Only group owner and administrators can admin group
+    if owner
+      can! :admin_group
+      can! :admin_namespace
+      can! :admin_group_member
+      can! :change_visibility_level
+    end
+
+    if globally_viewable && @subject.request_access_enabled && !member
+      can! :request_access
+    end
+  end
+
+  def can_read_group?
+    return true if @subject.public?
+    return true if @user.admin?
+    return true if @subject.internal? && !@user.external?
+    return true if @subject.users.include?(@user)
+
+    GroupProjectsFinder.new(@subject).execute(@user).any?
+  end
+end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
new file mode 100644
index 0000000..c253f9a
--- /dev/null
+++ b/app/policies/issuable_policy.rb
@@ -0,0 +1,14 @@
+class IssuablePolicy < BasePolicy
+  def action_name
+    @subject.class.name.underscore
+  end
+
+  def rules
+    if @user && (@subject.author == @user || @subject.assignee == @user)
+      can! :"read_#{action_name}"
+      can! :"update_#{action_name}"
+    end
+
+    delegate! @subject.project
+  end
+end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
new file mode 100644
index 0000000..bd1811a
--- /dev/null
+++ b/app/policies/issue_policy.rb
@@ -0,0 +1,28 @@
+class IssuePolicy < IssuablePolicy
+  def issue
+    @subject
+  end
+
+  def rules
+    super
+
+    if @subject.confidential? && !can_read_confidential?
+      cannot! :read_issue
+      cannot! :admin_issue
+      cannot! :update_issue
+      cannot! :read_issue
+    end
+  end
+
+  private
+
+  def can_read_confidential?
+    return false unless @user
+    return true if @user.admin?
+    return true if @subject.author == @user
+    return true if @subject.assignee == @user
+    return true if @subject.project.team.member?(@user, Gitlab::Access::REPORTER)
+
+    false
+  end
+end
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
new file mode 100644
index 0000000..bc3afc6
--- /dev/null
+++ b/app/policies/merge_request_policy.rb
@@ -0,0 +1,3 @@
+class MergeRequestPolicy < IssuablePolicy
+  # pass
+end
diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb
new file mode 100644
index 0000000..29bb357
--- /dev/null
+++ b/app/policies/namespace_policy.rb
@@ -0,0 +1,10 @@
+class NamespacePolicy < BasePolicy
+  def rules
+    return unless @user
+
+    if @subject.owner == @user || @user.admin?
+      can! :create_projects
+      can! :admin_namespace
+    end
+  end
+end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
new file mode 100644
index 0000000..8384746
--- /dev/null
+++ b/app/policies/note_policy.rb
@@ -0,0 +1,19 @@
+class NotePolicy < BasePolicy
+  def rules
+    delegate! @subject.project
+
+    return unless @user
+
+    if @subject.author == @user
+      can! :read_note
+      can! :update_note
+      can! :admin_note
+      can! :resolve_note
+    end
+
+    if @subject.for_merge_request? &&
+       @subject.noteable.author == @user
+      can! :resolve_note
+    end
+  end
+end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
new file mode 100644
index 0000000..46c5aa1
--- /dev/null
+++ b/app/policies/personal_snippet_policy.rb
@@ -0,0 +1,16 @@
+class PersonalSnippetPolicy < BasePolicy
+  def rules
+    can! :read_personal_snippet if @subject.public?
+    return unless @user
+
+    if @subject.author == @user
+      can! :read_personal_snippet
+      can! :update_personal_snippet
+      can! :admin_personal_snippet
+    end
+
+    if @subject.internal? && !@user.external?
+      can! :read_personal_snippet
+    end
+  end
+end
diff --git a/app/policies/project_member_policy.rb b/app/policies/project_member_policy.rb
new file mode 100644
index 0000000..1c038dd
--- /dev/null
+++ b/app/policies/project_member_policy.rb
@@ -0,0 +1,22 @@
+class ProjectMemberPolicy < BasePolicy
+  def rules
+    # anonymous users have no abilities here
+    return unless @user
+
+    target_user = @subject.user
+    project = @subject.project
+
+    return if target_user == project.owner
+
+    can_manage = Ability.allowed?(@user, :admin_project_member, project)
+
+    if can_manage
+      can! :update_project_member
+      can! :destroy_project_member
+    end
+
+    if @user == target_user
+      can! :destroy_project_member
+    end
+  end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
new file mode 100644
index 0000000..be25c75
--- /dev/null
+++ b/app/policies/project_policy.rb
@@ -0,0 +1,235 @@
+class ProjectPolicy < BasePolicy
+  def rules
+    team_access!(user)
+
+    owner = user.admin? ||
+            project.owner == user ||
+            (project.group && project.group.has_owner?(user))
+
+    owner_access! if owner
+
+    if project.public? || (project.internal? && !user.external?)
+      guest_access!
+      public_access!
+
+      # Allow to read builds for internal projects
+      can! :read_build if project.public_builds?
+
+      if project.request_access_enabled &&
+         !(owner || project.team.member?(user) || project_group_member?(user))
+        can! :request_access
+      end
+    end
+
+    archived_access! if project.archived?
+
+    disabled_features!
+  end
+
+  def project
+    @subject
+  end
+
+  def guest_access!
+    can! :read_project
+    can! :read_board
+    can! :read_list
+    can! :read_wiki
+    can! :read_issue
+    can! :read_label
+    can! :read_milestone
+    can! :read_project_snippet
+    can! :read_project_member
+    can! :read_merge_request
+    can! :read_note
+    can! :create_project
+    can! :create_issue
+    can! :create_note
+    can! :upload_file
+    can! :read_cycle_analytics
+  end
+
+  def reporter_access!
+    can! :download_code
+    can! :fork_project
+    can! :create_project_snippet
+    can! :update_issue
+    can! :admin_issue
+    can! :admin_label
+    can! :admin_list
+    can! :read_commit_status
+    can! :read_build
+    can! :read_container_image
+    can! :read_pipeline
+    can! :read_environment
+    can! :read_deployment
+  end
+
+  # Permissions given when an user is team member of a project
+  def team_member_reporter_access!
+    can! :build_download_code
+    can! :build_read_container_image
+  end
+
+  def developer_access!
+    can! :admin_merge_request
+    can! :update_merge_request
+    can! :create_commit_status
+    can! :update_commit_status
+    can! :create_build
+    can! :update_build
+    can! :create_pipeline
+    can! :update_pipeline
+    can! :create_merge_request
+    can! :create_wiki
+    can! :push_code
+    can! :resolve_note
+    can! :create_container_image
+    can! :update_container_image
+    can! :create_environment
+    can! :create_deployment
+  end
+
+  def master_access!
+    can! :push_code_to_protected_branches
+    can! :update_project_snippet
+    can! :update_environment
+    can! :update_deployment
+    can! :admin_milestone
+    can! :admin_project_snippet
+    can! :admin_project_member
+    can! :admin_merge_request
+    can! :admin_note
+    can! :admin_wiki
+    can! :admin_project
+    can! :admin_commit_status
+    can! :admin_build
+    can! :admin_container_image
+    can! :admin_pipeline
+    can! :admin_environment
+    can! :admin_deployment
+  end
+
+  def public_access!
+    can! :download_code
+    can! :fork_project
+    can! :read_commit_status
+    can! :read_pipeline
+    can! :read_container_image
+    can! :build_download_code
+    can! :build_read_container_image
+  end
+
+  def owner_access!
+    guest_access!
+    reporter_access!
+    developer_access!
+    master_access!
+    can! :change_namespace
+    can! :change_visibility_level
+    can! :rename_project
+    can! :remove_project
+    can! :archive_project
+    can! :remove_fork_project
+    can! :destroy_merge_request
+    can! :destroy_issue
+  end
+
+  # Push abilities on the users team role
+  def team_access!(user)
+    access = project.team.max_member_access(user.id)
+
+    guest_access!                if access >= Gitlab::Access::GUEST
+    reporter_access!             if access >= Gitlab::Access::REPORTER
+    team_member_reporter_access! if access >= Gitlab::Access::REPORTER
+    developer_access!            if access >= Gitlab::Access::DEVELOPER
+    master_access!               if access >= Gitlab::Access::MASTER
+  end
+
+  def archived_access!
+    cannot! :create_merge_request
+    cannot! :push_code
+    cannot! :push_code_to_protected_branches
+    cannot! :update_merge_request
+    cannot! :admin_merge_request
+  end
+
+  def disabled_features!
+    unless project.feature_available?(:issues, user)
+      cannot!(*named_abilities(:issue))
+    end
+
+    unless project.feature_available?(:merge_requests, user)
+      cannot!(*named_abilities(:merge_request))
+    end
+
+    unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user)
+      cannot!(*named_abilities(:label))
+      cannot!(*named_abilities(:milestone))
+    end
+
+    unless project.feature_available?(:snippets, user)
+      cannot!(*named_abilities(:project_snippet))
+    end
+
+    unless project.feature_available?(:wiki, user) || project.has_external_wiki?
+      cannot!(*named_abilities(:wiki))
+    end
+
+    unless project.feature_available?(:builds, user)
+      cannot!(*named_abilities(:build))
+      cannot!(*named_abilities(:pipeline))
+      cannot!(*named_abilities(:environment))
+      cannot!(*named_abilities(:deployment))
+    end
+
+    unless project.container_registry_enabled
+      cannot!(*named_abilities(:container_image))
+    end
+  end
+
+  def anonymous_rules
+    return unless project.public?
+
+    can! :read_project
+    can! :read_board
+    can! :read_list
+    can! :read_wiki
+    can! :read_label
+    can! :read_milestone
+    can! :read_project_snippet
+    can! :read_project_member
+    can! :read_merge_request
+    can! :read_note
+    can! :read_pipeline
+    can! :read_commit_status
+    can! :read_container_image
+    can! :download_code
+    can! :read_cycle_analytics
+
+    # NOTE: may be overridden by IssuePolicy
+    can! :read_issue
+
+    # Allow to read builds by anonymous user if guests are allowed
+    can! :read_build if project.public_builds?
+
+    disabled_features!
+  end
+
+  def project_group_member?(user)
+    project.group &&
+    (
+      project.group.members.exists?(user_id: user.id) ||
+      project.group.requesters.exists?(user_id: user.id)
+    )
+  end
+
+  def named_abilities(name)
+    [
+      :"read_#{name}",
+      :"create_#{name}",
+      :"update_#{name}",
+      :"admin_#{name}"
+    ]
+  end
+end
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
new file mode 100644
index 0000000..57acccf
--- /dev/null
+++ b/app/policies/project_snippet_policy.rb
@@ -0,0 +1,20 @@
+class ProjectSnippetPolicy < BasePolicy
+  def rules
+    can! :read_project_snippet if @subject.public?
+    return unless @user
+
+    if @user && @subject.author == @user || @user.admin?
+      can! :read_project_snippet
+      can! :update_project_snippet
+      can! :admin_project_snippet
+    end
+
+    if @subject.internal? && !@user.external?
+      can! :read_project_snippet
+    end
+
+    if @subject.private? && @subject.project.team.member?(@user)
+      can! :read_project_snippet
+    end
+  end
+end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
new file mode 100644
index 0000000..03a2499
--- /dev/null
+++ b/app/policies/user_policy.rb
@@ -0,0 +1,11 @@
+class UserPolicy < BasePolicy
+  include Gitlab::CurrentSettings
+
+  def rules
+    can! :read_user if @user || !restricted_public_level?
+  end
+
+  def restricted_public_level?
+    current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
+  end
+end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 6072123..38ac663 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -4,7 +4,9 @@ module Auth
 
     AUDIENCE = 'container_registry'
 
-    def execute
+    def execute(authentication_abilities:)
+      @authentication_abilities = authentication_abilities
+
       return error('not found', 404) unless registry.enabled
 
       unless current_user || project
@@ -74,9 +76,9 @@ module Auth
 
       case requested_action
       when 'pull'
-        requested_project == project || can?(current_user, :read_container_image, requested_project)
+        requested_project.public? || build_can_pull?(requested_project) || user_can_pull?(requested_project)
       when 'push'
-        requested_project == project || can?(current_user, :create_container_image, requested_project)
+        build_can_push?(requested_project) || user_can_push?(requested_project)
       else
         false
       end
@@ -85,5 +87,29 @@ module Auth
     def registry
       Gitlab.config.registry
     end
+
+    def build_can_pull?(requested_project)
+      # Build can:
+      # 1. pull from its own project (for ex. a build)
+      # 2. read images from dependent projects if creator of build is a team member
+      @authentication_abilities.include?(:build_read_container_image) &&
+        (requested_project == project || can?(current_user, :build_read_container_image, requested_project))
+    end
+
+    def user_can_pull?(requested_project)
+      @authentication_abilities.include?(:read_container_image) &&
+        can?(current_user, :read_container_image, requested_project)
+    end
+
+    def build_can_push?(requested_project)
+      # Build can push only to the project from which it originates
+      @authentication_abilities.include?(:build_create_container_image) &&
+        requested_project == project
+    end
+
+    def user_can_push?(requested_project)
+      @authentication_abilities.include?(:create_container_image) &&
+        can?(current_user, :create_container_image, requested_project)
+    end
   end
 end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 0d55ba5..0c20815 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -7,12 +7,8 @@ class BaseService
     @project, @current_user, @params = project, user, params.dup
   end
 
-  def abilities
-    Ability.abilities
-  end
-
   def can?(object, action, subject)
-    abilities.allowed?(object, action, subject)
+    Ability.allowed?(object, action, subject)
   end
 
   def notification_service
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index 5cb408b..b188782 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -3,7 +3,10 @@ module Boards
     class CreateService < Boards::BaseService
       def execute
         List.transaction do
-          create_list_at(next_position)
+          label    = project.labels.find(params[:label_id])
+          position = next_position
+
+          create_list(label, position)
         end
       end
 
@@ -14,8 +17,8 @@ module Boards
         max_position.nil? ? 0 : max_position.succ
       end
 
-      def create_list_at(position)
-        board.lists.create(params.merge(list_type: :label, position: position))
+      def create_list(label, position)
+        board.lists.create(label: label, list_type: :label, position: position)
       end
     end
   end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 6f7610d..36c93dd 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -10,13 +10,15 @@ module Ci
         create_builds!
       end
 
-      new_builds =
-        stage_indexes_of_created_builds.map do |index|
-          process_stage(index)
-        end
+      @pipeline.with_lock do
+        new_builds =
+          stage_indexes_of_created_builds.map do |index|
+            process_stage(index)
+          end
 
-      # Return a flag if a when builds got enqueued
-      new_builds.flatten.any?
+        # Return a flag if a when builds got enqueued
+        new_builds.flatten.any?
+      end
     end
 
     private
@@ -29,13 +31,13 @@ module Ci
       current_status = status_for_prior_stages(index)
 
       created_builds_in_stage(index).select do |build|
-        process_build(build, current_status)
+        if HasStatus::COMPLETED_STATUSES.include?(current_status)
+          process_build(build, current_status)
+        end
       end
     end
 
     def process_build(build, current_status)
-      return false unless Statuseable::COMPLETED_STATUSES.include?(current_status)
-
       if valid_statuses_for_when(build.when).include?(current_status)
         build.enqueue
         true
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 9a187f5..6973191 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -8,16 +8,18 @@ module Ci
       builds =
         if current_runner.shared?
           builds.
-            # don't run projects which have not enabled shared runners
-            joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }).
+            # don't run projects which have not enabled shared runners and builds
+            joins(:project).where(projects: { shared_runners_enabled: true }).
+            joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
 
             # this returns builds that are ordered by number of running builds
             # we prefer projects that don't use shared runners at all
             joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
+            where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
             order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
         else
           # do run projects which are only assigned to this runner (FIFO)
-          builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC')
+          builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
         end
 
       build = builds.find do |build|
diff --git a/app/services/ci/web_hook_service.rb b/app/services/ci/web_hook_service.rb
deleted file mode 100644
index 92e6df4..0000000
--- a/app/services/ci/web_hook_service.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module Ci
-  class WebHookService
-    def build_end(build)
-      execute_hooks(build.project, build_data(build))
-    end
-
-    def execute_hooks(project, data)
-      project.web_hooks.each do |web_hook|
-        async_execute_hook(web_hook, data)
-      end
-    end
-
-    def async_execute_hook(hook, data)
-      Sidekiq::Client.enqueue(Ci::WebHookWorker, hook.id, data)
-    end
-
-    def build_data(build)
-      project = build.project
-      data = {}
-      data.merge!({
-        build_id: build.id,
-        build_name: build.name,
-        build_status: build.status,
-        build_started_at: build.started_at,
-        build_finished_at: build.finished_at,
-        project_id: project.id,
-        project_name: project.name,
-        gitlab_url: project.gitlab_url,
-        ref: build.ref,
-        before_sha: build.before_sha,
-        sha: build.sha,
-      })
-    end
-  end
-end
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index ed73d8c..1c82599 100644
--- a/app/services/commits/change_service.rb
+++ b/app/services/commits/change_service.rb
@@ -16,11 +16,29 @@ module Commits
       error(ex.message)
     end
 
+    private
+
     def commit
       raise NotImplementedError
     end
 
-    private
+    def commit_change(action)
+      raise NotImplementedError unless repository.respond_to?(action)
+
+      into = @create_merge_request ? @commit.public_send("#{action}_branch_name") : @target_branch
+      tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch)
+
+      if tree_id
+        create_target_branch(into) if @create_merge_request
+
+        repository.public_send(action, current_user, @commit, into, tree_id)
+        success
+      else
+        error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically.
+                     It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content."
+        raise ChangeError, error_msg
+      end
+    end
 
     def check_push_permissions
       allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
diff --git a/app/services/commits/cherry_pick_service.rb b/app/services/commits/cherry_pick_service.rb
index f9a4efa..605cca3 100644
--- a/app/services/commits/cherry_pick_service.rb
+++ b/app/services/commits/cherry_pick_service.rb
@@ -1,19 +1,7 @@
 module Commits
   class CherryPickService < ChangeService
     def commit
-      cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch
-      cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch)
-
-      if cherry_pick_tree_id
-        create_target_branch(cherry_pick_into) if @create_merge_request
-
-        repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id)
-        success
-      else
-        error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically.
-                     It may have already been cherry-picked, or a more recent commit may have updated some of its content."
-        raise ChangeError, error_msg
-      end
+      commit_change(:cherry_pick)
     end
   end
 end
diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb
index c7de9f6..addd55c 100644
--- a/app/services/commits/revert_service.rb
+++ b/app/services/commits/revert_service.rb
@@ -1,19 +1,7 @@
 module Commits
   class RevertService < ChangeService
     def commit
-      revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch
-      revert_tree_id = repository.check_revert_content(@commit, @target_branch)
-
-      if revert_tree_id
-        create_target_branch(revert_into) if @create_merge_request
-
-        repository.revert(current_user, @commit, revert_into, revert_tree_id)
-        success
-      else
-        error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically.
-                     It may have already been reverted, or a more recent commit may have updated some of its content."
-        raise ChangeError, error_msg
-      end
+      commit_change(:revert)
     end
   end
 end
diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb
index efeb9df..799ad3e 100644
--- a/app/services/create_deployment_service.rb
+++ b/app/services/create_deployment_service.rb
@@ -2,11 +2,9 @@ require_relative 'base_service'
 
 class CreateDeploymentService < BaseService
   def execute(deployable = nil)
-    environment = project.environments.find_or_create_by(
-      name: params[:environment]
-    )
+    environment = find_or_create_environment
 
-    project.deployments.create(
+    deployment = project.deployments.create(
       environment: environment,
       ref: params[:ref],
       tag: params[:tag],
@@ -14,5 +12,43 @@ class CreateDeploymentService < BaseService
       user: current_user,
       deployable: deployable
     )
+
+    deployment.update_merge_request_metrics!
+
+    deployment
+  end
+
+  private
+
+  def find_or_create_environment
+    project.environments.find_or_create_by(name: expanded_name) do |environment|
+      environment.external_url = expanded_url
+    end
+  end
+
+  def expanded_name
+    ExpandVariables.expand(name, variables)
+  end
+
+  def expanded_url
+    return unless url
+
+    @expanded_url ||= ExpandVariables.expand(url, variables)
+  end
+
+  def name
+    params[:environment]
+  end
+
+  def url
+    options[:url]
+  end
+
+  def options
+    params[:options] || {}
+  end
+
+  def variables
+    params[:variables] || []
   end
 end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index ea94818..e846572 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -16,6 +16,8 @@ module Files
                           params[:file_content]
                         end
       @last_commit_sha = params[:last_commit_sha]
+      @author_email    = params[:author_email]
+      @author_name     = params[:author_name]
 
       # Validate parameters
       validate
diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb
index 6107254..d00d78c 100644
--- a/app/services/files/create_dir_service.rb
+++ b/app/services/files/create_dir_service.rb
@@ -3,7 +3,7 @@ require_relative "base_service"
 module Files
   class CreateDirService < Files::BaseService
     def commit
-      repository.commit_dir(current_user, @file_path, @commit_message, @target_branch)
+      repository.commit_dir(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name)
     end
 
     def validate
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 8eaf6db..bf12784 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -3,7 +3,7 @@ require_relative "base_service"
 module Files
   class CreateService < Files::BaseService
     def commit
-      repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false)
+      repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false, author_email: @author_email, author_name: @author_name)
     end
 
     def validate
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 27c881c..8b27ad5 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -3,7 +3,7 @@ require_relative "base_service"
 module Files
   class DeleteService < Files::BaseService
     def commit
-      repository.remove_file(current_user, @file_path, @commit_message, @target_branch)
+      repository.remove_file(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name)
     end
   end
 end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 4fc3b64..9e9b5b6 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -8,7 +8,9 @@ module Files
       repository.update_file(current_user, @file_path, @file_content,
                              branch: @target_branch,
                              previous_path: @previous_path,
-                             message: @commit_message)
+                             message: @commit_message,
+                             author_email: @author_email,
+                             author_name: @author_name)
     end
 
     private
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 78feb37..c499427 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -87,7 +87,7 @@ class GitPushService < BaseService
     project.change_head(branch_name)
 
     # Set protection on the default branch if configured
-    if current_application_settings.default_branch_protection != PROTECTION_NONE
+    if current_application_settings.default_branch_protection != PROTECTION_NONE && !@project.protected_branch?(@project.default_branch)
 
       params = {
         name: @project.default_branch,
@@ -134,6 +134,7 @@ class GitPushService < BaseService
       end
 
       commit.create_cross_references!(authors[commit], closed_issues)
+      update_issue_metrics(commit, authors)
     end
   end
 
@@ -186,4 +187,11 @@ class GitPushService < BaseService
   def branch_name
     @branch_name ||= Gitlab::Git.ref_name(params[:ref])
   end
+
+  def update_issue_metrics(commit, authors)
+    mentioned_issues = commit.all_references(authors[commit]).issues
+
+    Issue::Metrics.where(issue_id: mentioned_issues.map(&:id), first_mentioned_in_commit_at: nil).
+      update_all(first_mentioned_in_commit_at: commit.committed_date)
+  end
 end
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
new file mode 100644
index 0000000..60891cb
--- /dev/null
+++ b/app/services/issuable/bulk_update_service.rb
@@ -0,0 +1,26 @@
+module Issuable
+  class BulkUpdateService < IssuableBaseService
+    def execute(type)
+      model_class = type.classify.constantize
+      update_class = type.classify.pluralize.constantize::UpdateService
+
+      ids = params.delete(:issuable_ids).split(",")
+      items = model_class.where(id: ids)
+
+      %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key|
+        params.delete(key) unless params[key].present?
+      end
+
+      items.each do |issuable|
+        next unless can?(current_user, :"update_#{type}", issuable)
+
+        update_class.new(issuable.project, current_user, params).execute(issuable)
+      end
+
+      {
+        count:    items.count,
+        success:  !items.count.zero?
+      }
+    end
+  end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index e06c37c..fbce467 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -45,6 +45,7 @@ class IssuableBaseService < BaseService
 
     unless can?(current_user, ability, project)
       params.delete(:milestone_id)
+      params.delete(:labels)
       params.delete(:add_label_ids)
       params.delete(:remove_label_ids)
       params.delete(:label_ids)
@@ -72,6 +73,7 @@ class IssuableBaseService < BaseService
     filter_labels_in_param(:add_label_ids)
     filter_labels_in_param(:remove_label_ids)
     filter_labels_in_param(:label_ids)
+    find_or_create_label_ids
   end
 
   def filter_labels_in_param(key)
@@ -80,6 +82,17 @@ class IssuableBaseService < BaseService
     params[key] = project.labels.where(id: params[key]).pluck(:id)
   end
 
+  def find_or_create_label_ids
+    labels = params.delete(:labels)
+    return unless labels
+
+    params[:label_ids] = labels.split(",").map do |label_name|
+      project.labels.create_with(color: Label::DEFAULT_COLOR)
+                    .find_or_create_by(title: label_name.strip)
+                    .id
+    end
+  end
+
   def process_label_ids(attributes, existing_label_ids: nil)
     label_ids = attributes.delete(:label_ids)
     add_label_ids = attributes.delete(:add_label_ids)
@@ -144,6 +157,10 @@ class IssuableBaseService < BaseService
     # To be overridden by subclasses
   end
 
+  def after_update(issuable)
+    # To be overridden by subclasses
+  end
+
   def update_issuable(issuable, attributes)
     issuable.with_transaction_returning_status do
       issuable.update(attributes.merge(updated_by: current_user))
@@ -162,8 +179,14 @@ class IssuableBaseService < BaseService
 
     if params.present? && update_issuable(issuable, params)
       issuable.reset_events_cache
-      handle_common_system_notes(issuable, old_labels: old_labels)
+
+      # We do not touch as it will affect a update on updated_at field
+      ActiveRecord::Base.no_touching do
+        handle_common_system_notes(issuable, old_labels: old_labels)
+      end
+
       handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
+      after_update(issuable)
       issuable.create_new_cross_references!(current_user)
       execute_hooks(issuable, 'update')
     end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 089b0f5..9ea3ce0 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -14,9 +14,10 @@ module Issues
     end
 
     def execute_hooks(issue, action = 'open')
-      issue_data = hook_data(issue, action)
-      issue.project.execute_hooks(issue_data, :issue_hooks)
-      issue.project.execute_services(issue_data, :issue_hooks)
+      issue_data  = hook_data(issue, action)
+      hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks
+      issue.project.execute_hooks(issue_data, hooks_scope)
+      issue.project.execute_services(issue_data, hooks_scope)
     end
   end
 end
diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb
deleted file mode 100644
index 7e19a73..0000000
--- a/app/services/issues/bulk_update_service.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Issues
-  class BulkUpdateService < BaseService
-    def execute
-      issues_ids   = params.delete(:issues_ids).split(",")
-      issue_params = params
-
-      %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key|
-        issue_params.delete(key) unless issue_params[key].present?
-      end
-
-      issues = Issue.where(id: issues_ids)
-
-      issues.each do |issue|
-        next unless can?(current_user, :update_issue, issue)
-
-        Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue)
-      end
-
-      {
-        count:    issues.count,
-        success:  !issues.count.zero?
-      }
-    end
-  end
-end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 290742f..e57791f 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -83,7 +83,7 @@ module MergeRequests
         closes_issue = "Closes ##{iid}"
 
         if merge_request.description.present?
-          merge_request.description += closes_issue.prepend("\n")
+          merge_request.description += closes_issue.prepend("\n\n")
         else
           merge_request.description = closes_issue
         end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 73247e6..b0ae2df 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -20,6 +20,7 @@ module MergeRequests
       event_service.open_mr(issuable, current_user)
       notification_service.new_merge_request(issuable, current_user)
       todo_service.new_merge_request(issuable, current_user)
+      issuable.cache_merge_request_closes_issues!(current_user)
     end
   end
 end
diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb
index 08c1f72..1262ecb 100644
--- a/app/services/merge_requests/get_urls_service.rb
+++ b/app/services/merge_requests/get_urls_service.rb
@@ -31,7 +31,7 @@ module MergeRequests
 
     def get_branches(changes)
       return [] if project.empty_repo?
-      return [] unless project.merge_requests_enabled
+      return [] unless project.merge_requests_enabled?
 
       changes_list = Gitlab::ChangesList.new(changes)
       changes_list.map do |change|
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 5cedd6f..22596b4 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -13,6 +13,7 @@ module MergeRequests
       reload_merge_requests
       reset_merge_when_build_succeeds
       mark_pending_todos_done
+      cache_merge_requests_closing_issues
 
       # Leave a system note if a branch was deleted/added
       if branch_added? || branch_removed?
@@ -141,6 +142,14 @@ module MergeRequests
       end
     end
 
+    # If the merge requests closes any issues, save this information in the
+    # `MergeRequestsClosingIssues` model (as a performance optimization).
+    def cache_merge_requests_closing_issues
+      @project.merge_requests.where(source_branch: @branch_name).each do |merge_request|
+        merge_request.cache_merge_request_closes_issues!(@current_user)
+      end
+    end
+
     def filter_merge_requests(merge_requests)
       merge_requests.uniq.select(&:source_project)
     end
diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb
index adc71b0..19caa03 100644
--- a/app/services/merge_requests/resolve_service.rb
+++ b/app/services/merge_requests/resolve_service.rb
@@ -1,11 +1,14 @@
 module MergeRequests
   class ResolveService < MergeRequests::BaseService
-    attr_accessor :conflicts, :rugged, :merge_index
+    attr_accessor :conflicts, :rugged, :merge_index, :merge_request
 
     def execute(merge_request)
       @conflicts = merge_request.conflicts
       @rugged = project.repository.rugged
       @merge_index = conflicts.merge_index
+      @merge_request = merge_request
+
+      fetch_their_commit!
 
       conflicts.files.each do |file|
         write_resolved_file_to_index(file, params[:sections])
@@ -27,5 +30,21 @@ module MergeRequests
       merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode)
       merge_index.conflict_remove(our_path)
     end
+
+    # If their commit (in the target project) doesn't exist in the source project, it
+    # can't be a parent for the merge commit we're about to create. If that's the case,
+    # fetch the target branch ref into the source project so the commit exists in both.
+    #
+    def fetch_their_commit!
+      return if rugged.include?(conflicts.their_commit.oid)
+
+      random_string = SecureRandom.hex
+
+      project.repository.fetch_ref(
+        merge_request.target_project.repository.path_to_repo,
+        "refs/heads/#{merge_request.target_branch}",
+        "refs/tmp/#{random_string}/head"
+      )
+    end
   end
 end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 30c5f24..f14f9e4 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -11,6 +11,10 @@ module MergeRequests
       params.except!(:target_project_id)
       params.except!(:source_branch)
 
+      if merge_request.closed_without_fork?
+        params.except!(:target_branch, :force_remove_source_branch)
+      end
+
       merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
 
       update(merge_request)
@@ -73,5 +77,9 @@ module MergeRequests
     def close_service
       MergeRequests::CloseService
     end
+
+    def after_update(issuable)
+      issuable.cache_merge_request_closes_issues!(current_user)
+    end
   end
 end
diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb
index 3b90399..b8e08c9 100644
--- a/app/services/milestones/create_service.rb
+++ b/app/services/milestones/create_service.rb
@@ -3,7 +3,7 @@ module Milestones
     def execute
       milestone = project.milestones.new(params)
 
-      if milestone.save!
+      if milestone.save
         event_service.open_milestone(milestone, current_user)
       end
 
diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/slash_commands_service.rb
index 4a9a8a6..2edbd39 100644
--- a/app/services/notes/slash_commands_service.rb
+++ b/app/services/notes/slash_commands_service.rb
@@ -5,9 +5,18 @@ module Notes
       'MergeRequest' => MergeRequests::UpdateService
     }
 
-    def supported?(note)
+    def self.noteable_update_service(note)
+      UPDATE_SERVICES[note.noteable_type]
+    end
+
+    def self.supported?(note, current_user)
       noteable_update_service(note) &&
-        can?(current_user, :"update_#{note.noteable_type.underscore}", note.noteable)
+        current_user &&
+        current_user.can?(:"update_#{note.noteable_type.underscore}", note.noteable)
+    end
+
+    def supported?(note)
+      self.class.supported?(note, current_user)
     end
 
     def extract_commands(note)
@@ -21,13 +30,7 @@ module Notes
       return if command_params.empty?
       return unless supported?(note)
 
-      noteable_update_service(note).new(project, current_user, command_params).execute(note.noteable)
-    end
-
-    private
-
-    def noteable_update_service(note)
-      UPDATE_SERVICES[note.noteable_type]
+      self.class.noteable_update_service(note).new(project, current_user, command_params).execute(note.noteable)
     end
   end
 end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 55956be..be749ba 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -7,7 +7,6 @@ module Projects
     def execute
       forked_from_project_id = params.delete(:forked_from_project_id)
       import_data = params.delete(:import_data)
-
       @project = Project.new(params)
 
       # Make sure that the user is allowed to use the specified visibility level
@@ -81,8 +80,7 @@ module Projects
       log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
 
       unless @project.gitlab_project_import?
-        @project.create_wiki if @project.wiki_enabled?
-
+        @project.create_wiki if @project.feature_available?(:wiki, current_user)
         @project.build_missing_services
 
         @project.create_labels
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 8a53f65..a08c6fc 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -27,6 +27,8 @@ module Projects
       # Git data (e.g. a list of branch names).
       flush_caches(project, wiki_path)
 
+      Projects::UnlinkForkService.new(project, current_user).execute
+
       Project.transaction do
         project.destroy!
 
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index de6dc38..a2de4dc 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -8,7 +8,6 @@ module Projects
         name:                   @project.name,
         path:                   @project.path,
         shared_runners_enabled: @project.shared_runners_enabled,
-        builds_enabled:         @project.builds_enabled,
         namespace_id:           @params[:namespace].try(:id) || current_user.namespace.id
       }
 
@@ -17,6 +16,9 @@ module Projects
       end
 
       new_project = CreateService.new(current_user, new_params).execute
+      builds_access_level = @project.project_feature.builds_access_level
+      new_project.project_feature.update_attributes(builds_access_level: builds_access_level)
+
       new_project
     end
 
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index 29b3981..c3dfc8c 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -30,10 +30,8 @@ module Projects
     end
 
     def increment!
-      if Gitlab::ExclusiveLease.new("project_housekeeping:increment!:#{@project.id}", timeout: 60).try_obtain
-        Gitlab::Metrics.measure(:increment_pushes_since_gc) do
-          update_pushes_since_gc(@project.pushes_since_gc + 1)
-        end
+      Gitlab::Metrics.measure(:increment_pushes_since_gc) do
+        @project.increment_pushes_since_gc
       end
     end
 
@@ -43,14 +41,10 @@ module Projects
       GitGarbageCollectWorker.perform_async(@project.id)
     ensure
       Gitlab::Metrics.measure(:reset_pushes_since_gc) do
-        update_pushes_since_gc(0)
+        @project.reset_pushes_since_gc
       end
     end
 
-    def update_pushes_since_gc(new_value)
-      @project.update_column(:pushes_since_gc, new_value)
-    end
-
     def try_obtain_lease
       Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
         lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 546a8f1..0c8446e 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -269,11 +269,11 @@ module SystemNoteService
   #
   # Example Note text:
   #
-  #   "mentioned in #1"
+  #   "Mentioned in #1"
   #
-  #   "mentioned in !2"
+  #   "Mentioned in !2"
   #
-  #   "mentioned in 54f7727c"
+  #   "Mentioned in 54f7727c"
   #
   # See cross_reference_note_content.
   #
@@ -308,7 +308,7 @@ module SystemNoteService
 
   # Check if a cross-reference is disallowed
   #
-  # This method prevents adding a "mentioned in !1" note on every single commit
+  # This method prevents adding a "Mentioned in !1" note on every single commit
   # in a merge request. Additionally, it prevents the creation of references to
   # external issues (which would fail).
   #
@@ -417,7 +417,7 @@ module SystemNoteService
   end
 
   def cross_reference_note_prefix
-    'mentioned in '
+    'Mentioned in '
   end
 
   def cross_reference_note_content(gfm_reference)
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index e0ccb65..776530a 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -31,6 +31,14 @@ class TodoService
     mark_pending_todos_as_done(issue, current_user)
   end
 
+  # When we destroy an issue we should:
+  #
+  #  * refresh the todos count cache for the current user
+  #
+  def destroy_issue(issue, current_user)
+    destroy_issuable(issue, current_user)
+  end
+
   # When we reassign an issue we should:
   #
   #  * create a pending todo for new assignee if issue is assigned
@@ -64,6 +72,14 @@ class TodoService
     mark_pending_todos_as_done(merge_request, current_user)
   end
 
+  # When we destroy a merge request we should:
+  #
+  #  * refresh the todos count cache for the current user
+  #
+  def destroy_merge_request(merge_request, current_user)
+    destroy_issuable(merge_request, current_user)
+  end
+
   # When we reassign a merge request we should:
   #
   #  * creates a pending todo for new assignee if merge request is assigned
@@ -148,7 +164,8 @@ class TodoService
   def mark_todos_as_done_by_ids(ids, current_user)
     todos = current_user.todos.where(id: ids)
 
-    marked_todos = todos.update_all(state: :done)
+    # Only return those that are not really on that state
+    marked_todos = todos.where.not(state: :done).update_all(state: :done)
     current_user.update_todos_count_cache
     marked_todos
   end
@@ -186,6 +203,10 @@ class TodoService
     create_mention_todos(issuable.project, issuable, author)
   end
 
+  def destroy_issuable(issuable, user)
+    user.update_todos_count_cache
+  end
+
   def toggling_tasks?(issuable)
     issuable.previous_changes.include?('description') &&
       issuable.tasks? && issuable.updated_tasks.any?
diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml
index dd2e7eb..56bf619 100644
--- a/app/views/admin/abuse_reports/_abuse_report.html.haml
+++ b/app/views/admin/abuse_reports/_abuse_report.html.haml
@@ -1,6 +1,8 @@
 - reporter = abuse_report.reporter
 - user = abuse_report.user
 %tr
+  %th.visible-xs-block.visible-sm-block
+    %strong User
   %td
     - if user
       = link_to user.name, user
@@ -9,6 +11,7 @@
     - else
       (removed)
   %td
+    %strong.subheading.visible-xs-block.visible-sm-block Reported by
     - if reporter
       = link_to reporter.name, reporter
     - else
@@ -16,16 +19,16 @@
     .light.small
       = time_ago_with_tooltip(abuse_report.created_at)
   %td
-    = markdown(abuse_report.message.squish!, pipeline: :single_line, author: reporter)
+    %strong.subheading.visible-xs-block.visible-sm-block Message
+    .message
+      = markdown(abuse_report.message.squish!, pipeline: :single_line, author: reporter)
   %td
     - if user
       = link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true),
-        data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, remote: true, method: :delete, class: "btn btn-xs btn-remove js-remove-tr"
-
-  %td
+        data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, remote: true, method: :delete, class: "btn btn-sm btn-block btn-remove js-remove-tr"
     - if user && !user.blocked?
-      = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
+      = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-sm btn-block"
     - else
-      .btn.btn-xs.disabled
+      .btn.btn-sm.disabled.btn-block
         Already Blocked
-    = link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
+    = link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-sm btn-block btn-close js-remove-tr"
diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml
index bc4a9ce..7bbc75d 100644
--- a/app/views/admin/abuse_reports/index.html.haml
+++ b/app/views/admin/abuse_reports/index.html.haml
@@ -1,17 +1,20 @@
-- page_title "Abuse Reports"
+- page_title 'Abuse Reports'
 %h3.page-title Abuse Reports
 %hr
-- if @abuse_reports.present?
-  .table-holder
-    %table.table
-      %thead
-        %tr
-          %th User
-          %th Reported by
-          %th Message
-          %th Primary action
-          %th
-      = render @abuse_reports
-  = paginate @abuse_reports
-- else
-  %h4 There are no abuse reports
+.abuse-reports
+  - if @abuse_reports.present?
+    .table-holder
+      %table.table
+        %thead.hidden-sm.hidden-xs
+          %tr
+            %th User
+            %th Reported by
+            %th.wide Message
+            %th Action
+        = render @abuse_reports
+  - else
+    .no-reports
+      %span.pull-left
+        There are no abuse reports!
+      .pull-left
+        = emoji_icon 'tada'
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index 92e2dae..9175b3d 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -13,7 +13,7 @@
     .col-sm-10
       = f.text_area :description, class: "form-control", rows: 10
       .hint
-        Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown/markdown'), target: '_blank'}.
+        Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}.
   .form-group
     = f.label :logo, class: 'control-label'
     .col-sm-10
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index d929364..0d79ca7 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -49,28 +49,6 @@
         = select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
         %span.help-block#clone-protocol-help
           Allow only the selected protocols to be used for Git access.
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :version_check_enabled do
-            = f.check_box :version_check_enabled
-            Version check enabled
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :email_author_in_body do
-            = f.check_box :email_author_in_body
-            Include author name in notification email body
-          .help-block
-            Some email servers do not support overriding the email sender name.
-            Enable this option to include the name of the author of the issue,
-            merge request or comment in the email body instead.
-    .form-group
-      = f.label :admin_notification_email, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_field :admin_notification_email, class: 'form-control'
-        .help-block
-          Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
 
   %fieldset
     %legend Account and Limit Settings
@@ -341,6 +319,15 @@
           %a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com
 
   %fieldset
+    %legend Abuse reports
+    .form-group
+      = f.label :admin_notification_email, 'Abuse reports notification email', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :admin_notification_email, class: 'form-control'
+        .help-block
+          Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
+
+  %fieldset
     %legend Error Reporting and Logging
     %p
       These settings require a restart to take effect.
@@ -407,6 +394,29 @@
           = succeed "." do
             = link_to "Koding administration documentation", help_page_path("administration/integration/koding")
 
+  %fieldset
+    %legend Usage statistics
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :version_check_enabled do
+            = f.check_box :version_check_enabled
+            Version check enabled
+          .help-block
+            Let GitLab inform you when an update is available.
+
+  %fieldset
+    %legend Email
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :email_author_in_body do
+            = f.check_box :email_author_in_body
+            Include author name in notification email body
+          .help-block
+            Some email servers do not support overriding the email sender name.
+            Enable this option to include the name of the author of the issue,
+            merge request or comment in the email body instead.
 
   .form-actions
     = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml
index 89d7a40..107fc25 100644
--- a/app/views/admin/background_jobs/_head.html.haml
+++ b/app/views/admin/background_jobs/_head.html.haml
@@ -1,22 +1,24 @@
-.nav-links.sub-nav
-  %ul{ class: (container_class) }
-    = nav_link(controller: :system_info) do
-      = link_to admin_system_info_path, title: 'System Info' do
-        %span
-          System Info
-    = nav_link(controller: :background_jobs) do
-      = link_to admin_background_jobs_path, title: 'Background Jobs' do
-        %span
-          Background Jobs
-    = nav_link(controller: :logs) do
-      = link_to admin_logs_path, title: 'Logs' do
-        %span
-          Logs
-    = nav_link(controller: :health_check) do
-      = link_to admin_health_check_path, title: 'Health Check' do
-        %span
-          Health Check
-    = nav_link(controller: :requests_profiles) do
-      = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
-        %span
-          Requests Profiles
+.scrolling-tabs-container.sub-nav-scroll
+  = render 'shared/nav_scroll'
+  .nav-links.sub-nav.scrolling-tabs
+    %ul{ class: (container_class) }
+      = nav_link(controller: :system_info) do
+        = link_to admin_system_info_path, title: 'System Info' do
+          %span
+            System Info
+      = nav_link(controller: :background_jobs) do
+        = link_to admin_background_jobs_path, title: 'Background Jobs' do
+          %span
+            Background Jobs
+      = nav_link(controller: :logs) do
+        = link_to admin_logs_path, title: 'Logs' do
+          %span
+            Logs
+      = nav_link(controller: :health_check) do
+        = link_to admin_health_check_path, title: 'Health Check' do
+          %span
+            Health Check
+      = nav_link(controller: :requests_profiles) do
+        = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
+          %span
+            Requests Profiles
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 4f680b5..05855db 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -28,14 +28,10 @@
               %th COMMAND
             %tbody
               - @sidekiq_processes.each do |process|
-                - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
-                - data = process.strip.split(' ')
                 %tr
                   %td= gitlab_config.user
-                  - 5.times do
-                    %td= data.shift
-                  %td= data.join(' ')
-
+                  - parse_sidekiq_ps(process).each do |value|
+                    %td= value
         .clearfix
           %p
             %i.fa.fa-exclamation-circle
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
deleted file mode 100644
index f29d9c9..0000000
--- a/app/views/admin/builds/_build.html.haml
+++ /dev/null
@@ -1,77 +0,0 @@
-- project = build.project
-%tr.build.commit
-  %td.status
-    = ci_status_with_icon(build.status)
-
-  %td
-    .branch-commit
-      - if can?(current_user, :read_build, build.project)
-        = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
-          %span.build-link ##{build.id}
-      - else
-        %span.build-link ##{build.id}
-
-      - if build.ref
-        .icon-container
-          = build.tag? ? icon('tag') : icon('code-fork')
-        = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
-      - else
-        .light none
-      .icon-container
-        = custom_icon("icon_commit")
-
-      = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id"
-      - if build.stuck?
-        %i.fa.fa-warning.text-warning
-
-      .label-container
-        - if build.tags.any?
-          - build.tags.each do |tag|
-            %span.label.label-primary
-              = tag
-        - if build.try(:trigger_request)
-          %span.label.label-info triggered
-        - if build.try(:allow_failure)
-          %span.label.label-danger allowed to fail
-
-  %td
-    - if project
-      = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project)
-
-  %td
-    - if build.try(:runner)
-      = runner_link(build.runner)
-    - else
-      .light none
-
-  %td
-    #{build.stage} / #{build.name}
-
-  %td
-    - if build.duration
-      %p.duration
-        = custom_icon("icon_timer")
-        = duration_in_numbers(build.duration)
-
-    - if build.finished_at
-      %p.finished-at
-        = icon("calendar")
-        %span #{time_ago_with_tooltip(build.finished_at)}
-
-  - if defined?(coverage) && coverage
-    %td.coverage
-      - if build.try(:coverage)
-        #{build.coverage}%
-
-  %td
-    .pull-right
-      - if can?(current_user, :read_build, project) && build.artifacts?
-        = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do
-          %i.fa.fa-download
-      - if can?(current_user, :update_build, build.project)
-        - if build.active?
-          = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
-            %i.fa.fa-remove.cred
-        - elsif defined?(allow_retry) && allow_retry && build.retryable?
-          = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
-            %i.fa.fa-refresh
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index 3d77634..26a8846 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/builds/index.html.haml
@@ -4,26 +4,8 @@
 %div{ class: container_class }
 
   .top-area
-    %ul.nav-links
-      %li{class: ('active' if @scope.nil?)}
-        = link_to admin_builds_path do
-          All
-          %span.badge.js-totalbuilds-count= @all_builds.count(:id)
-
-      %li{class: ('active' if @scope == 'pending')}
-        = link_to admin_builds_path(scope: :pending) do
-          Pending
-          %span.badge= number_with_delimiter(@all_builds.pending.count(:id))
-
-      %li{class: ('active' if @scope == 'running')}
-        = link_to admin_builds_path(scope: :running) do
-          Running
-          %span.badge= number_with_delimiter(@all_builds.running.count(:id))
-
-      %li{class: ('active' if @scope == 'finished')}
-        = link_to admin_builds_path(scope: :finished) do
-          Finished
-          %span.badge= number_with_delimiter(@all_builds.finished.count(:id))
+    - build_path_proc = ->(scope) { admin_builds_path(scope: scope) }
+    = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
 
     .nav-controls
       - if @all_builds.running_or_pending.any?
@@ -33,23 +15,4 @@
     #{(@scope || 'all').capitalize} builds
 
   %ul.content-list.builds-content-list
-    - if @builds.blank?
-      %li
-        .nothing-here-block No builds to show
-    - else
-      .table-holder
-        %table.table.builds
-          %thead
-            %tr
-              %th Status
-              %th Commit
-              %th Project
-              %th Runner
-              %th Name
-              %th
-              %th
-
-          - @builds.each do |build|
-            = render "admin/builds/build", build: build
-
-      = paginate @builds, theme: 'gitlab'
+    = render "projects/builds/table", builds: @builds, admin: true
diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml
index b74da64..c91ab4c 100644
--- a/app/views/admin/dashboard/_head.html.haml
+++ b/app/views/admin/dashboard/_head.html.haml
@@ -1,26 +1,28 @@
-.nav-links.sub-nav
-  %ul{ class: (container_class) }
-    = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
-      = link_to admin_root_path, title: 'Overview' do
-        %span
-          Overview
-    = nav_link(controller: [:admin, :projects]) do
-      = link_to admin_namespaces_projects_path, title: 'Projects' do
-        %span
-          Projects
-    = nav_link(controller: :users) do
-      = link_to admin_users_path, title: 'Users' do
-        %span
-          Users
-    = nav_link(controller: :groups) do
-      = link_to admin_groups_path, title: 'Groups' do
-        %span
-          Groups
-    = nav_link path: 'builds#index' do
-      = link_to admin_builds_path, title: 'Builds' do
-        %span
-          Builds
-    = nav_link path: ['runners#index', 'runners#show'] do
-      = link_to admin_runners_path, title: 'Runners' do
-        %span
-          Runners
+.scrolling-tabs-container.sub-nav-scroll
+  = render 'shared/nav_scroll'
+  .nav-links.sub-nav.scrolling-tabs
+    %ul{ class: (container_class) }
+      = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
+        = link_to admin_root_path, title: 'Overview' do
+          %span
+            Overview
+      = nav_link(controller: [:admin, :projects]) do
+        = link_to admin_namespaces_projects_path, title: 'Projects' do
+          %span
+            Projects
+      = nav_link(controller: :users) do
+        = link_to admin_users_path, title: 'Users' do
+          %span
+            Users
+      = nav_link(controller: :groups) do
+        = link_to admin_groups_path, title: 'Groups' do
+          %span
+            Groups
+      = nav_link path: 'builds#index' do
+        = link_to admin_builds_path, title: 'Builds' do
+          %span
+            Builds
+      = nav_link path: ['runners#index', 'runners#show'] do
+        = link_to admin_runners_path, title: 'Runners' do
+          %span
+            Runners
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 5f7fdfd..817910f 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -13,6 +13,8 @@
     .col-sm-offset-2.col-sm-10
       = render 'shared/allow_request_access', form: f
 
+  = render 'groups/group_lfs_settings', f: f
+
   - if @group.new_record?
     .form-group
       .col-sm-offset-2.col-sm-10
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index bb37469..0188ed4 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -37,6 +37,12 @@
           %strong
             = @group.created_at.to_s(:medium)
 
+        %li
+          %span.light Group Git LFS status:
+          %strong
+            = group_lfs_status(@group)
+            = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+
     .panel.panel-default
       .panel-heading
         %h3.panel-title
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index b2c6073..6c7c3c4 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -73,6 +73,12 @@
             %span.light last commit:
             %strong
               = last_commit(@project)
+
+          %li
+            %span.light Git LFS status:
+            %strong
+              = project_lfs_status(@project)
+              = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
         - else
           %li
             %span.light repository:
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 02efcec..fbe3ab9 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -1,5 +1,5 @@
 - grouped_emojis = awardable.grouped_awards(with_thumbs: inline)
-.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) } }
+.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
   - awards_sort(grouped_emojis).each do |emoji, awards|
     %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", class: (award_active_class(awards, current_user)), data: { placement: "bottom", title: award_user_list(awards, current_user) } }
       = emoji_icon(emoji, sprite: false)
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index 0044d77..889086c 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -1,3 +1,6 @@
+- page_title "CI Lint"
+- page_description "Validate your GitLab CI configuration file"
+
 %h2 Check your .gitlab-ci.yml
 %hr
 
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 98f302d..b40395c 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -1,6 +1,7 @@
 %li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} }
+  = author_avatar(todo, size: 40)
+
   .todo-item.todo-block
-    = image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
     .todo-title.title
       - unless todo.build_failed?
         = todo_target_state_pill(todo)
@@ -19,13 +20,13 @@
 
       · #{time_ago_with_tooltip(todo.created_at)}
 
-    - if todo.pending?
-      .todo-actions.pull-right
-        = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading done-todo' do
-          Done
-          = icon('spinner spin')
-
     .todo-body
       .todo-note
         .md
           = event_note(todo.body, project: todo.project)
+
+  - if todo.pending?
+    .todo-actions
+      = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading done-todo' do
+        Done
+        = icon('spinner spin')
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index d320d3b..9d31f31 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -28,21 +28,25 @@
   .row-content-block.second-block
     = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
       .filter-item.inline
-        = select_tag('project_id', todo_projects_options,
-          class: 'select2 trigger-submit', include_blank: true,
-          data: {placeholder: 'Project'})
+        - if params[:project_id].present?
+          = hidden_field_tag(:project_id, params[:project_id])
+        = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
+          placeholder: 'Search projects', data: { data: todo_projects_options } })
       .filter-item.inline
-        = users_select_tag(:author_id, selected: params[:author_id],
-          placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
+        - if params[:author_id].present?
+          = hidden_field_tag(:author_id, params[:author_id])
+        = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
+          placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } })
       .filter-item.inline
-        = select_tag('type', todo_types_options,
-          class: 'select2 trigger-submit', include_blank: true,
-          data: {placeholder: 'Type'})
+        - if params[:type].present?
+          = hidden_field_tag(:type, params[:type])
+        = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit',
+          data: { data: todo_types_options } })
       .filter-item.inline.actions-filter
-        = select_tag('action_id', todo_actions_options,
-          class: 'select2 trigger-submit', include_blank: true,
-          data: {placeholder: 'Action'})
-
+        - if params[:action_id].present?
+          = hidden_field_tag(:action_id, params[:action_id])
+        = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit',
+          data: { data: todo_actions_options }})
       .pull-right
         .dropdown.inline.prepend-left-10
           %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
@@ -66,7 +70,7 @@
   - if @todos.any?
     .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
     - @todos.group_by(&:project).each do |group|
-      .panel.panel-default.panel-small.js-todos-list
+      .panel.panel-default.panel-small
         - project = group[0]
         .panel-heading
           = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
@@ -76,11 +80,3 @@
     = paginate @todos, theme: "gitlab"
   - else
     .nothing-here-block You're all done!
-
-:javascript
-  new UsersSelect();
-
-  $('form.filter-form').on('submit', function (event) {
-    event.preventDefault();
-    Turbolinks.visit(this.action + '&' + $(this).serialize());
-  });
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 4debd3d..e623f7c 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -18,6 +18,5 @@
             = f.submit "Verify code", class: "btn btn-save"
 
       - if @user.two_factor_u2f_enabled?
-
         %hr
-        = render "u2f/authenticate"
+        = render "u2f/authenticate", locals: { params: params, resource: resource, resource_name: resource_name }
diff --git a/app/views/discussions/_jump_to_next.html.haml b/app/views/discussions/_jump_to_next.html.haml
index 69bd416..7ed09dd 100644
--- a/app/views/discussions/_jump_to_next.html.haml
+++ b/app/views/discussions/_jump_to_next.html.haml
@@ -5,5 +5,5 @@
       %button.btn.btn-default.discussion-next-btn.has-tooltip{ "@click" => "jumpToNextUnresolvedDiscussion",
         title: "Jump to next unresolved discussion",
         "aria-label" => "Jump to next unresolved discussion",
-        data: { container: "body" } }
+        data: { container: "body" }}
         = custom_icon("next_discussion")
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
index fbe470b..dfdbdf1 100644
--- a/app/views/discussions/_notes.html.haml
+++ b/app/views/discussions/_notes.html.haml
@@ -10,6 +10,7 @@
         .btn-group{ role: "group" }
           = link_to_reply_discussion(discussion, line_type)
         = render "discussions/resolve_all", discussion: discussion
-        = render "discussions/jump_to_next", discussion: discussion
+        - if discussion.for_merge_request?
+          = render "discussions/jump_to_next", discussion: discussion
     - else
       = link_to_reply_discussion(discussion)
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 5c318cd..31fdcc5 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -1,7 +1,7 @@
 - if event.visible_to_user?(current_user)
   .event-item{ class: event_row_class(event) }
     .event-item-timestamp
-      #{time_ago_with_tooltip(event.created_at)}
+      #{time_ago_with_tooltip(event.created_at, skip_js: true)}
 
     = cache [event, current_application_settings, "v2.2"] do
       = author_avatar(event, size: 40)
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 57f6e7e..b8248a8 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -24,7 +24,7 @@
         - else
           = sort_title_recently_created
         %b.caret
-      %ul.dropdown-menu
+      %ul.dropdown-menu.dropdown-menu-align-right
         %li
           = link_to explore_groups_path(sort: sort_value_recently_created) do
             = sort_title_recently_created
diff --git a/app/views/groups/_group_lfs_settings.html.haml b/app/views/groups/_group_lfs_settings.html.haml
new file mode 100644
index 0000000..af57065
--- /dev/null
+++ b/app/views/groups/_group_lfs_settings.html.haml
@@ -0,0 +1,11 @@
+- if current_user.admin?
+  .form-group
+    .col-sm-offset-2.col-sm-10
+      .checkbox
+        = f.label :lfs_enabled do
+          = f.check_box :lfs_enabled, checked: @group.lfs_enabled?
+          %strong
+            Allow projects within this group to use Git LFS
+            = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+          %br/
+          %span.descr This setting can be overridden in each project.
\ No newline at end of file
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index decb89b..c766370 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -25,6 +25,8 @@
         .col-sm-offset-2.col-sm-10
           = render 'shared/allow_request_access', form: f
 
+      = render 'group_lfs_settings', f: f
+
       .form-group
         %hr
         = f.label :share_with_group_lock, class: 'control-label' do
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 90f362c..f789796 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -17,7 +17,7 @@
     .panel-heading
       %strong #{@group.name}
       group members
-      %span.badge= @members.size
+      %span.badge= @members.total_count
       .controls
         = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form'  do
           .form-group
diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml
index 742f9d7..3be7ed8 100644
--- a/app/views/groups/group_members/update.js.haml
+++ b/app/views/groups/group_members/update.js.haml
@@ -1,3 +1,3 @@
 :plain
   $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
-  new MemberExpirationDate();
+  new gl.MemberExpirationDate();
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 53ed4fa..31db6ee 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -23,7 +23,7 @@
         .cover-desc.description
           = markdown(@group.description, pipeline: :description)
 
-%div{ class: container_class }
+%div.groups-header{ class: container_class }
   .top-area
     %ul.nav-links
       %li.active
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index ce4536e..65842a0 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -7,277 +7,284 @@
           Keyboard Shortcuts
           %small
             = link_to '(Show all)', '#', class: 'js-more-help-button'
-      .modal-body.shortcuts-cheatsheet
-        .col-lg-4
-          %table.shortcut-mappings
-            %tbody
-              %tr
-                %th
-                %th Global Shortcuts
-              %tr
-                %td.shortcut
-                  .key s
-                %td Focus Search
-              %tr
-                %td.shortcut
-                  .key f
-                %td Focus Filter
-              %tr
-                %td.shortcut
-                  .key ?
-                %td Show/hide this dialog
-              %tr
-                %td.shortcut
-                  - if browser.platform.mac?
-                    .key ⌘ shift p
-                  - else
-                    .key ctrl shift p
-                %td Toggle Markdown preview
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-up
-                %td Edit last comment (when focused on an empty textarea)
-            %tbody
-              %tr
-                %th
-                %th Project Files browsing
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-up
-                %td Move selection up
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-down
-                %td Move selection down
-              %tr
-                %td.shortcut
-                  .key enter
-                %td Open Selection
-            %tbody
-              %tr
-                %th
-                %th Finding Project File
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-up
-                %td Move selection up
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-down
-                %td Move selection down
-              %tr
-                %td.shortcut
-                  .key enter
-                %td Open Selection
-              %tr
-                %td.shortcut
-                  .key esc
-                %td Go back
+      .modal-body
+        .row
+          .col-lg-4
+            %table.shortcut-mappings
+              %tbody
+                %tr
+                  %th
+                  %th Global Shortcuts
+                %tr
+                  %td.shortcut
+                    .key s
+                  %td Focus Search
+                %tr
+                  %td.shortcut
+                    .key f
+                  %td Focus Filter
+                %tr
+                  %td.shortcut
+                    .key ?
+                  %td Show/hide this dialog
+                %tr
+                  %td.shortcut
+                    - if browser.platform.mac?
+                      .key ⌘ shift p
+                    - else
+                      .key ctrl shift p
+                  %td Toggle Markdown preview
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-up
+                  %td Edit last comment (when focused on an empty textarea)
+              %tbody
+                %tr
+                  %th
+                  %th Project Files browsing
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-up
+                  %td Move selection up
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-down
+                  %td Move selection down
+                %tr
+                  %td.shortcut
+                    .key enter
+                  %td Open Selection
+              %tbody
+                %tr
+                  %th
+                  %th Finding Project File
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-up
+                  %td Move selection up
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-down
+                  %td Move selection down
+                %tr
+                  %td.shortcut
+                    .key enter
+                  %td Open Selection
+                %tr
+                  %td.shortcut
+                    .key esc
+                  %td Go back
 
-        .col-lg-4
-          %table.shortcut-mappings
-            %tbody{ class: 'hidden-shortcut project', style: 'display:none' }
-              %tr
-                %th
-                %th Global Dashboard
-              %tr
-                %td.shortcut
-                  .key g
-                  .key a
-                %td
-                  Go to the activity feed
-              %tr
-                %td.shortcut
-                  .key g
-                  .key p
-                %td
-                  Go to projects
-              %tr
-                %td.shortcut
-                  .key g
-                  .key i
-                %td
-                  Go to issues
-              %tr
-                %td.shortcut
-                  .key g
-                  .key m
-                %td
-                  Go to merge requests
-            %tbody
-              %tr
-                %th
-                %th Project
-              %tr
-                %td.shortcut
-                  .key g
-                  .key p
-                %td
-                  Go to the project's home page
-              %tr
-                %td.shortcut
-                  .key g
-                  .key e
-                %td
-                  Go to the project's activity feed
-              %tr
-                %td.shortcut
-                  .key g
-                  .key f
-                %td
-                  Go to files
-              %tr
-                %td.shortcut
-                  .key g
-                  .key c
-                %td
-                  Go to commits
-              %tr
-                %td.shortcut
-                  .key g
-                  .key b
-                %td
-                  Go to builds
-              %tr
-                %td.shortcut
-                  .key g
-                  .key n
-                %td
-                  Go to network graph
-              %tr
-                %td.shortcut
-                  .key g
-                  .key g
-                %td
-                  Go to graphs
-              %tr
-                %td.shortcut
-                  .key g
-                  .key i
-                %td
-                  Go to issues
-              %tr
-                %td.shortcut
-                  .key g
-                  .key m
-                %td
-                  Go to merge requests
-              %tr
-                %td.shortcut
-                  .key g
-                  .key s
-                %td
-                  Go to snippets
-              %tr
-                %td.shortcut
-                  .key t
-                %td Go to finding file
-              %tr
-                %td.shortcut
-                  .key i
-                %td New issue
-        .col-lg-4
-          %table.shortcut-mappings
-            %tbody{ class: 'hidden-shortcut network', style: 'display:none' }
-              %tr
-                %th
-                %th Network Graph
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-left
-                  \/
-                  .key h
-                %td Scroll left
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-right
-                  \/
-                  .key l
-                %td Scroll right
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-up
-                  \/
-                  .key k
-                %td Scroll up
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-down
-                  \/
-                  .key j
-                %td Scroll down
-              %tr
-                %td.shortcut
-                  .key
-                    shift
-                    %i.fa.fa-arrow-up
-                  \/
-                  .key
-                    shift k
-                %td Scroll to top
-              %tr
-                %td.shortcut
-                  .key
-                    shift
-                    %i.fa.fa-arrow-down
-                  \/
-                  .key
-                    shift j
-                %td Scroll to bottom
-            %tbody{ class: 'hidden-shortcut issues', style: 'display:none' }
-              %tr
-                %th
-                %th Issues
-              %tr
-                %td.shortcut
-                  .key a
-                %td Change assignee
-              %tr
-                %td.shortcut
-                  .key m
-                %td Change milestone
-              %tr
-                %td.shortcut
-                  .key r
-                %td Reply (quoting selected text)
-              %tr
-                %td.shortcut
-                  .key e
-                %td Edit issue
-              %tr
-                %td.shortcut
-                  .key l
-                %td Change Label
-            %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
-              %tr
-                %th
-                %th Merge Requests
-              %tr
-                %td.shortcut
-                  .key a
-                %td Change assignee
-              %tr
-                %td.shortcut
-                  .key m
-                %td Change milestone
-              %tr
-                %td.shortcut
-                  .key r
-                %td Reply (quoting selected text)
-              %tr
-                %td.shortcut
-                  .key e
-                %td Edit merge request
-              %tr
-                %td.shortcut
-                  .key l
-                %td Change Label
+          .col-lg-4
+            %table.shortcut-mappings
+              %tbody{ class: 'hidden-shortcut project', style: 'display:none' }
+                %tr
+                  %th
+                  %th Global Dashboard
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key a
+                  %td
+                    Go to the activity feed
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key p
+                  %td
+                    Go to projects
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key i
+                  %td
+                    Go to issues
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key m
+                  %td
+                    Go to merge requests
+              %tbody
+                %tr
+                  %th
+                  %th Project
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key p
+                  %td
+                    Go to the project's home page
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key e
+                  %td
+                    Go to the project's activity feed
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key f
+                  %td
+                    Go to files
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key c
+                  %td
+                    Go to commits
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key b
+                  %td
+                    Go to builds
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key n
+                  %td
+                    Go to network graph
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key g
+                  %td
+                    Go to graphs
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key i
+                  %td
+                    Go to issues
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key l
+                  %td
+                    Go to issue boards
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key m
+                  %td
+                    Go to merge requests
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key s
+                  %td
+                    Go to snippets
+                %tr
+                  %td.shortcut
+                    .key t
+                  %td Go to finding file
+                %tr
+                  %td.shortcut
+                    .key i
+                  %td New issue
+          .col-lg-4
+            %table.shortcut-mappings
+              %tbody{ class: 'hidden-shortcut network', style: 'display:none' }
+                %tr
+                  %th
+                  %th Network Graph
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-left
+                    \/
+                    .key h
+                  %td Scroll left
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-right
+                    \/
+                    .key l
+                  %td Scroll right
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-up
+                    \/
+                    .key k
+                  %td Scroll up
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-down
+                    \/
+                    .key j
+                  %td Scroll down
+                %tr
+                  %td.shortcut
+                    .key
+                      shift
+                      %i.fa.fa-arrow-up
+                    \/
+                    .key
+                      shift k
+                  %td Scroll to top
+                %tr
+                  %td.shortcut
+                    .key
+                      shift
+                      %i.fa.fa-arrow-down
+                    \/
+                    .key
+                      shift j
+                  %td Scroll to bottom
+              %tbody{ class: 'hidden-shortcut issues', style: 'display:none' }
+                %tr
+                  %th
+                  %th Issues
+                %tr
+                  %td.shortcut
+                    .key a
+                  %td Change assignee
+                %tr
+                  %td.shortcut
+                    .key m
+                  %td Change milestone
+                %tr
+                  %td.shortcut
+                    .key r
+                  %td Reply (quoting selected text)
+                %tr
+                  %td.shortcut
+                    .key e
+                  %td Edit issue
+                %tr
+                  %td.shortcut
+                    .key l
+                  %td Change Label
+              %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
+                %tr
+                  %th
+                  %th Merge Requests
+                %tr
+                  %td.shortcut
+                    .key a
+                  %td Change assignee
+                %tr
+                  %td.shortcut
+                    .key m
+                  %td Change milestone
+                %tr
+                  %td.shortcut
+                    .key r
+                  %td Reply (quoting selected text)
+                %tr
+                  %td.shortcut
+                    .key e
+                  %td Edit merge request
+                %tr
+                  %td.shortcut
+                    .key l
+                  %td Change Label
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 85e188d..d16bd61 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -549,4 +549,4 @@
     %li wiki page
     %li help page
 
-  You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown/markdown")}.
+  You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("user/markdown")}.
diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml
index 804ad88..8e92953 100644
--- a/app/views/import/base/create.js.haml
+++ b/app/views/import/base/create.js.haml
@@ -1,23 +1,4 @@
-- if @already_been_taken
-  :plain
-    tr = $("tr#repo_#{@repo_id}")
-    target_field = tr.find(".import-target")
-    import_button = tr.find(".btn-import")
-    origin_target = target_field.text()
-    project_name = "#{@project_name}"
-    origin_namespace = "#{@target_namespace}"
-    target_field.empty()
-    target_field.append("<p class='alert alert-danger'>This namespace already been taken! Please choose another one</p>")
-    target_field.append("<input type='text' name='target_namespace' />")
-    target_field.append("/" + project_name)
-    target_field.data("project_name", project_name)
-    target_field.find('input').prop("value", origin_namespace)
-    import_button.enable().removeClass('is-loading')
-- elsif @access_denied
-  :plain
-    job = $("tr#repo_#{@repo_id}")
-    job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>")
-- elsif @project.persisted?
+- if @project.persisted?
   :plain
     job = $("tr#repo_#{@repo_id}")
     job.attr("id", "project_#{@project.id}")
diff --git a/app/views/import/base/unauthorized.js.haml b/app/views/import/base/unauthorized.js.haml
new file mode 100644
index 0000000..36f8069
--- /dev/null
+++ b/app/views/import/base/unauthorized.js.haml
@@ -0,0 +1,14 @@
+:plain
+  tr = $("tr#repo_#{@repo_id}")
+  target_field = tr.find(".import-target")
+  import_button = tr.find(".btn-import")
+  origin_target = target_field.text()
+  project_name = "#{@project_name}"
+  origin_namespace = "#{@target_namespace.path}"
+  target_field.empty()
+  target_field.append("<p class='alert alert-danger'>This namespace has already been taken! Please choose another one.</p>")
+  target_field.append("<input type='text' name='target_namespace' />")
+  target_field.append("/" + project_name)
+  target_field.data("project_name", project_name)
+  target_field.find('input').prop("value", origin_namespace)
+  import_button.enable().removeClass('is-loading')
diff --git a/app/views/import/bitbucket/deploy_key.js.haml b/app/views/import/bitbucket/deploy_key.js.haml
new file mode 100644
index 0000000..81b34ab
--- /dev/null
+++ b/app/views/import/bitbucket/deploy_key.js.haml
@@ -0,0 +1,3 @@
+:plain
+  job = $("tr#repo_#{@repo_id}")
+  job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>")
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 15dd980..f8b4b10 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -51,7 +51,7 @@
           %td
             = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
           %td.import-target
-            = "#{repo["owner"]}/#{repo["slug"]}"
+            = import_project_target(repo['owner'], repo['slug'])
           %td.import-actions.job-status
             = button_tag class: "btn btn-import js-add-to-import" do
               Import
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 54ff1d2..4c721d4 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -45,7 +45,17 @@
           %td
             = github_project_link(repo.full_name)
           %td.import-target
-            = repo.full_name
+            %fieldset.row
+            .input-group
+              .project-path.input-group-btn
+                - if current_user.can_select_namespace?
+                  - selected = params[:namespace_id] || :current_user
+                  - opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner.login, path: repo.owner.login) } : {}
+                  = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
+                - else
+                  = text_field_tag :path, current_user.namespace_path, class: "input-large form-control", tabindex: 1, disabled: true
+              %span.input-group-addon /
+              = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
           %td.import-actions.job-status
             = button_tag class: "btn btn-import js-add-to-import" do
               Import
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index fcfc6fd..d31fc2e 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -45,7 +45,7 @@
           %td
             = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
           %td.import-target
-            = repo["path_with_namespace"]
+            = import_project_target(repo['namespace']['path'], repo['name'])
           %td.import-actions.job-status
             = button_tag class: "btn btn-import js-add-to-import" do
               Import
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
deleted file mode 100644
index ed3afb0..0000000
--- a/app/views/import/gitorious/status.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-- page_title "Gitorious import"
-- header_title "Projects", root_path
-%h3.page-title
-  %i.icon-gitorious.icon-gitorious-big
-  Import projects from Gitorious.org
-
-%p.light
-  Select projects you want to import.
-%hr
-%p
-  = button_tag class: "btn btn-import btn-success js-import-all" do
-    Import all projects
-    = icon("spinner spin", class: "loading-icon")
-
-.table-responsive
-  %table.table.import-jobs
-    %colgroup.import-jobs-from-col
-    %colgroup.import-jobs-to-col
-    %colgroup.import-jobs-status-col
-    %thead
-      %tr
-        %th From Gitorious.org
-        %th To GitLab
-        %th Status
-    %tbody
-      - @already_added_projects.each do |project|
-        %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
-          %td
-            = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
-          %td
-            = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
-          %td.job-status
-            - if project.import_status == 'finished'
-              %span
-                %i.fa.fa-check
-                done
-            - elsif project.import_status == 'started'
-              %i.fa.fa-spinner.fa-spin
-              started
-            - else
-              = project.human_import_status_name
-
-      - @repos.each do |repo|
-        %tr{id: "repo_#{repo.id}"}
-          %td
-            = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank"
-          %td.import-target
-            = repo.full_name
-          %td.import-actions.job-status
-            = button_tag class: "btn btn-import js-add-to-import" do
-              Import
-              = icon("spinner spin", class: "loading-icon")
-
-.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitorious_path}", import_path: "#{import_gitorious_path}" } }
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index bf50633..4f7839a 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,5 +1,5 @@
 .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
-  .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
+  .sidebar-wrapper.nicescroll
     .sidebar-action-buttons
       = link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do
         %span.sr-only Toggle navigation
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index d7d36c8..27ac176 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,5 +1,5 @@
+= render 'layouts/nav/group_settings'
 .scrolling-tabs-container{ class: nav_control_class }
-  = render 'layouts/nav/group_settings'
   .fade-left
     = icon('angle-left')
   .fade-right
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index bf9a7ec..75275af 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,22 +1,26 @@
 - if current_user
+  - can_admin_group = can?(current_user, :admin_group, @group)
   - can_edit = can?(current_user, :admin_group, @group)
   - member = @group.members.find_by(user_id: current_user.id)
   - can_leave = member && can?(current_user, :destroy_group_member, member)
 
-  .controls
-    .dropdown.group-settings-dropdown
-      %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
-        = icon('cog')
-        = icon('caret-down')
-      %ul.dropdown-menu.dropdown-menu-align-right
-        = nav_link(path: 'groups#projects') do
-          = link_to 'Projects', projects_group_path(@group), title: 'Projects'
-        %li.divider
-        - if can_edit
-          %li
-            = link_to 'Edit Group', edit_group_path(@group)
-        - if can_leave
-          %li
-            = link_to polymorphic_path([:leave, @group, :members]),
-              data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do
-              Leave Group
+  - if can_admin_group || can_edit || can_leave
+    .controls
+      .dropdown.group-settings-dropdown
+        %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
+          = icon('cog')
+          = icon('caret-down')
+        %ul.dropdown-menu.dropdown-menu-align-right
+          - if can_admin_group
+            = nav_link(path: 'groups#projects') do
+              = link_to 'Projects', projects_group_path(@group), title: 'Projects'
+          - if can_edit || can_leave
+            %li.divider
+          - if can_edit
+            %li
+              = link_to 'Edit Group', edit_group_path(@group)
+          - if can_leave
+            %li
+              = link_to polymorphic_path([:leave, @group, :members]),
+                data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do
+                Leave Group
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index f701259..e44a2bf 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -47,7 +47,7 @@
             Repository
 
     - if project_nav_tab? :pipelines
-      = nav_link(controller: [:pipelines, :builds, :environments]) do
+      = nav_link(controller: [:pipelines, :builds, :environments, :cycle_analytics]) do
         = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
           %span
             Pipelines
@@ -113,3 +113,7 @@
       %li.hidden
         = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
           Commits
+
+    -# Shortcut to issue boards
+    %li.hidden
+      = link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 52a5bdc..613b8b7 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -26,7 +26,7 @@
       %span
         Protected Branches
 
-  - if @project.builds_enabled?
+  - if @project.feature_available?(:builds, current_user)
     = nav_link(controller: :runners) do
       = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
         %span
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index dde2e28..1ec4c3f 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -25,8 +25,8 @@
           - if @labels_url
             adjust your #{link_to 'label subscriptions', @labels_url}.
           - else
-            - if @sent_notification && @sent_notification.unsubscribable?
-              = link_to "unsubscribe", unsubscribe_sent_notification_url(@sent_notification)
+            - if @sent_notification_url
+              = link_to "unsubscribe", @sent_notification_url
               from this thread or
             adjust your notification settings.
 
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 9fe9429..277eb71 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -14,9 +14,6 @@
       window.project_uploads_path = "#{namespace_project_uploads_path project.namespace,project}";
       window.preview_markdown_path = "#{preview_markdown_path}";
 
-- content_for :scripts_body do
-  = render "layouts/init_auto_complete" if current_user
-
 - content_for :header_content do
   .js-dropdown-menu-projects
     .dropdown-menu.dropdown-select.dropdown-menu-projects
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index a42b3b8..9318787 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,4 +1,5 @@
 - page_title "SSH Keys"
+= render 'profiles/head'
 
 .row.prepend-top-default
   .col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml
index 249680b..de1337a 100644
--- a/app/views/profiles/update_username.js.haml
+++ b/app/views/profiles/update_username.js.haml
@@ -1,6 +1,6 @@
 - if @user.valid?
   :plain
-    new Flash("Username sucessfully changed", "notice")
+    new Flash("Username successfully changed", "notice")
 - else
   :plain
     new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert")
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index 19b4249..80053dd 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -1,11 +1,14 @@
-%fieldset.builds-feature
-  %h5.prepend-top-0
-    Merge Requests
-  .form-group
-    .checkbox
-      = f.label :only_allow_merge_if_build_succeeds do
-        = f.check_box :only_allow_merge_if_build_succeeds
-        %strong Only allow merge requests to be merged if the build succeeds
-      .help-block
-        Builds need to be configured to enable this feature.
-        = link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
+.merge-requests-feature
+  %fieldset.builds-feature
+    %hr
+    %h5.prepend-top-0
+      Merge Requests
+    .form-group
+      .checkbox
+        = f.label :only_allow_merge_if_build_succeeds do
+          = f.check_box :only_allow_merge_if_build_succeeds
+          %strong Only allow merge requests to be merged if the build succeeds
+          %br
+          %span.descr
+            Builds need to be configured to enable this feature.
+            = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index 3978fa6..cb97181 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -7,3 +7,6 @@
     = text_area_tag attr, nil, class: classes, placeholder: placeholder
   %a.zen-control.zen-control-leave.js-zen-leave{ href: "#" }
     = icon('compress')
+
+- content_for :scripts_body do
+  = render "layouts/init_auto_complete" if current_user && (@target_project || @project)
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 377665b..5a98e25 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -11,7 +11,7 @@
       %small= number_to_human_size @blob.size
       .file-actions
         = render "projects/blob/actions"
-    .file-content.blame.code.js-syntax-highlight
+    .table-responsive.file-content.blame.code.js-syntax-highlight
       %table
         - current_line = 1
         - @blame_groups.each do |blame_group|
@@ -19,6 +19,7 @@
             %td.blame-commit
               .commit
                 - commit = blame_group[:commit]
+                = author_avatar(commit, size: 36)
                 .commit-row-title
                   %strong
                     = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index ff379ba..0237e15 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -24,7 +24,7 @@
       .encoding-selector
         = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
 
-  .file-content.code
+  .file-editor.code
     %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
     - if local_assigns[:path]
       .js-edit-mode-pane#preview.hide
diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml
index de53a29..7306615 100644
--- a/app/views/projects/boards/components/_board.html.haml
+++ b/app/views/projects/boards/components/_board.html.haml
@@ -13,19 +13,13 @@
         %h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
           {{ list.title }}
           %span.pull-right{ "v-if" => "list.type !== 'blank'" }
-            {{ list.issues.length }}
+            {{ list.issuesSize }}
           - if can?(current_user, :admin_list, @project)
             %board-delete{ "inline-template" => true,
               ":list" => "list",
               "v-if" => "!list.preset && list.id" }
               %button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
                 = icon("trash")
-          = icon("spinner spin", class: "board-header-loading-spinner pull-right", "v-show" => "list.loadingMore")
-      .board-inner-container.board-search-container{ "v-if" => "list.canSearch()" }
-        %input.form-control{ type: "text", placeholder: "Search issues", "v-model" => "query", "debounce" => "250" }
-        = icon("search", class: "board-search-icon", "v-show" => "!query")
-        %button.board-search-clear-btn{ type: "button", role: "button", "aria-label" => "Clear search", "@click" => "query = ''", "v-show" => "query" }
-          = icon("times", class: "board-search-clear")
       %board-list{ "inline-template" => true,
         "v-if" => "list.type !== 'blank'",
         ":list" => "list",
@@ -39,5 +33,11 @@
           "v-show" => "!loading",
           ":data-board" => "list.id" }
           = render "projects/boards/components/card"
+          %li.board-list-count.text-center{ "v-if" => "showCount" }
+            = icon("spinner spin", "v-show" => "list.loadingMore" )
+            %span{ "v-if" => "list.issues.length === list.issuesSize" }
+              Showing all issues
+            %span{ "v-else" => true }
+              Showing {{ list.issues.length }} of {{ list.issuesSize }} issues
       - if can?(current_user, :admin_list, @project)
         = render "projects/boards/components/blank_state"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 4bd8506..5217b8b 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -3,10 +3,11 @@
 - diverging_commit_counts = @repository.diverging_commit_counts(branch)
 - number_commits_behind = diverging_commit_counts[:behind]
 - number_commits_ahead = diverging_commit_counts[:ahead]
+- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
 %li(class="js-branch-#{branch.name}")
   %div
-    = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
-      %span.item-title.str-truncated= branch.name
+    = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated' do
+      = branch.name
      
     - if branch.name == @repository.root_ref
       %span.label.label-primary default
@@ -19,14 +20,16 @@
         %i.fa.fa-lock
         protected
     .controls.hidden-xs
-      - if create_mr_button?(@repository.root_ref, branch.name)
+      - if merge_project && create_mr_button?(@repository.root_ref, branch.name)
         = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do
           Merge Request
 
       - if branch.name != @repository.root_ref
-        = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do
+        = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
           Compare
 
+      = render 'projects/buttons/download', project: @project, ref: branch.name
+
       - if can_remove_branch?(@project, branch.name)
         = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
           = icon("trash-o")
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 5b0b58e..0aa3092 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -1,3 +1,6 @@
+- builds = @build.pipeline.builds.latest.to_a
+- statuses = ["failed", "pending", "running", "canceled", "success", "skipped"]
+
 %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
   .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
     Build
@@ -11,40 +14,6 @@
       %p.build-detail-row
         #{@build.coverage}%
 
-  - builds = @build.pipeline.builds.latest.to_a
-  - statuses = ["failed", "pending", "running", "canceled", "success", "skipped"]
-  - if builds.size > 1
-    .dropdown.build-dropdown
-      .build-light-text Stage
-      %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
-        %span.stage-selection More
-        = icon('caret-down')
-      %ul.dropdown-menu
-        - builds.map(&:stage).uniq.each do |stage|
-          %li
-            %a.stage-item= stage
-
-    .builds-container
-      - statuses.each do |build_status|
-        - builds.select{|build| build.status == build_status}.each do |build|
-          .build-job{class: ('active' if build == @build), data: {stage: build.stage}}
-            = link_to namespace_project_build_path(@project.namespace, @project, build) do
-              = icon('check')
-              = ci_icon_for_status(build.status)
-              %span
-                - if build.name
-                  = build.name
-                - else
-                  = build.id
-
-        - if @build.retried?
-          %li.active
-            %a
-              Build ##{@build.id}
-              ·
-              %i.fa.fa-warning
-              This build was retried.
-
   .blocks-container
     - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
       .block{ class: ("block-first" if !@build.coverage) }
@@ -76,7 +45,7 @@
       .title
         Build details
         - if can?(current_user, :update_build, @build) && @build.retryable?
-          = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
+          = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
       - if @build.merge_request
         %p.build-detail-row
           %span.build-light-text Merge Request:
@@ -100,7 +69,7 @@
         - elsif @build.runner
           \##{@build.runner.id}
       .btn-group.btn-group-justified{ role: :group }
-        - if @build.has_trace?
+        - if @build.has_trace_file?
           = link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default'
         - if @build.active?
           = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
@@ -121,12 +90,13 @@
 
         - if @build.trigger_request.variables
           %p
-            %span.build-light-text Variables:
+            %button.btn.group.btn-group-justified.reveal-variables Reveal Variables
 
 
           - @build.trigger_request.variables.each do |key, value|
-            %code
-              #{key}=#{value}
+            .hide.js-build
+              .js-build-variable= key 
+              .js-build-value= value
 
     .block
       .title
@@ -141,3 +111,35 @@
         - @build.tag_list.each do |tag|
           %span.label.label-primary
             = tag
+
+    - if @build.pipeline.stages.many?
+      .dropdown.build-dropdown
+        .title Stage
+        %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
+          %span.stage-selection More
+          = icon('caret-down')
+        %ul.dropdown-menu
+          - @build.pipeline.stages.each do |stage|
+            %li
+              %a.stage-item= stage
+
+  .builds-container
+    - statuses.each do |build_status|
+      - builds.select{|build| build.status == build_status}.each do |build|
+        .build-job{class: ('active' if build == @build), data: {stage: build.stage}}
+          = link_to namespace_project_build_path(@project.namespace, @project, build) do
+            = icon('check')
+            = ci_icon_for_status(build.status)
+            %span
+              - if build.name
+                = build.name
+              - else
+                = build.id
+
+      - if @build.retried?
+        %li.active
+          %a
+            Build ##{@build.id}
+            ·
+            %i.fa.fa-warning
+            This build was retried.
diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml
new file mode 100644
index 0000000..61eff73
--- /dev/null
+++ b/app/views/projects/builds/_table.html.haml
@@ -0,0 +1,24 @@
+- admin = local_assigns.fetch(:admin, false)
+
+- if builds.blank?
+  %li
+    .nothing-here-block No builds to show
+- else
+  .table-holder
+    %table.table.builds
+      %thead
+        %tr
+          %th Status
+          %th Commit
+          - if admin
+            %th Project
+            %th Runner
+          %th Stage
+          %th Name
+          %th
+          %th Coverage
+          %th
+
+      = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: admin || project.build_coverage_enabled?, admin: admin }
+
+  = paginate builds, theme: 'gitlab'
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 2af625f..5c60b7a 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -4,30 +4,8 @@
 
 %div{ class: container_class }
   .top-area
-    %ul.nav-links
-      %li{class: ('active' if @scope.nil?)}
-        = link_to project_builds_path(@project) do
-          All
-          %span.badge.js-totalbuilds-count
-            = number_with_delimiter(@all_builds.count(:id))
-
-      %li{class: ('active' if @scope == 'pending')}
-        = link_to project_builds_path(@project, scope: :pending) do
-          Pending
-          %span.badge
-            = number_with_delimiter(@all_builds.pending.count(:id))
-
-      %li{class: ('active' if @scope == 'running')}
-        = link_to project_builds_path(@project, scope: :running) do
-          Running
-          %span.badge
-            = number_with_delimiter(@all_builds.running.count(:id))
-
-      %li{class: ('active' if @scope == 'finished')}
-        = link_to project_builds_path(@project, scope: :finished) do
-          Finished
-          %span.badge
-            = number_with_delimiter(@all_builds.finished.count(:id))
+    - build_path_proc = ->(scope) { project_builds_path(@project, scope: scope) }
+    = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
 
     .nav-controls
       - if can?(current_user, :update_build, @project)
@@ -42,23 +20,4 @@
           %span CI Lint
 
   %ul.content-list.builds-content-list
-    - if @builds.blank?
-      %li
-        .nothing-here-block No builds to show
-    - else
-      .table-holder
-        %table.table.builds
-          %thead
-            %tr
-              %th Status
-              %th Commit
-              %th Stage
-              %th Name
-              %th
-              - if @project.build_coverage_enabled?
-                %th Coverage
-              %th
-
-          = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
-
-      = paginate @builds, theme: 'gitlab'
+    = render "table", builds: @builds, project: @project
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 58f43ec..24de020 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,4 +1,42 @@
-- unless @project.empty_repo?
-  - if can? current_user, :download_code, @project
-    = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
-      = icon('download')
+- if !project.empty_repo? && can?(current_user, :download_code, project)
+  %span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'}
+    .dropdown.inline
+      %button.btn{ 'data-toggle' => 'dropdown' }
+        = icon('download')
+        %span.caret
+        %span.sr-only
+          Select Archive Format
+      %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
+        %li.dropdown-header Source code
+        %li
+          = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
+            %i.fa.fa-download
+            %span Download zip
+        %li
+          = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
+            %i.fa.fa-download
+            %span Download tar.gz
+        %li
+          = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
+            %i.fa.fa-download
+            %span Download tar.bz2
+        %li
+          = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
+            %i.fa.fa-download
+            %span Download tar
+
+        - pipeline = project.pipelines.latest_successful_for(ref)
+        - if pipeline
+          - artifacts = pipeline.builds.latest.with_artifacts
+          - if artifacts.any?
+            %li.dropdown-header Artifacts
+            - unless pipeline.latest?
+              - latest_pipeline = project.pipeline_for(ref)
+              %li
+                .unclickable= ci_status_for_statuseable(latest_pipeline)
+              %li.dropdown-header Previous Artifacts
+            - artifacts.each do |job|
+              %li
+                = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do
+                  %i.fa.fa-download
+                  %span Download '#{job.name}'
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index d78888e..22db334 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -3,11 +3,11 @@
     - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
       = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
         = custom_icon('icon_fork')
-        Fork
+        %span Fork
     - else
       = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
         = custom_icon('icon_fork')
-        Fork
+        %span Fork
     %div.count-with-arrow
       %span.arrow
       = link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 71cf558..3115830 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,10 +1,10 @@
 - if current_user
   = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: current_user.starred?(@project) ? 'Unstar project' : 'Star project' } do
     - if current_user.starred?(@project)
-      = icon('star fw')
+      = icon('star')
       %span.starred Unstar
     - else
-      = icon('star-o fw')
+      = icon('star-o')
       %span Star
   %div.count-with-arrow
     %span.arrow
@@ -13,7 +13,7 @@
 
 - else
   = link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: 'You must sign in to star a project' do
-    = icon('star fw')
+    = icon('star')
     Star
   %div.count-with-arrow
     %span.arrow
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 1fdf324..75192c4 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -1,3 +1,11 @@
+- admin = local_assigns.fetch(:admin, false)
+- ref = local_assigns.fetch(:ref, nil)
+- commit_sha = local_assigns.fetch(:commit_sha, nil)
+- retried = local_assigns.fetch(:retried, false)
+- stage = local_assigns.fetch(:stage, false)
+- coverage = local_assigns.fetch(:coverage, false)
+- allow_retry = local_assigns.fetch(:allow_retry, false)
+
 %tr.build.commit
   %td.status
     - if can?(current_user, :read_build, build)
@@ -9,11 +17,11 @@
     .branch-commit
       - if can?(current_user, :read_build, build)
         = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
-          %span ##{build.id}
+          %span.build-link ##{build.id}
       - else
-        %span ##{build.id}
+        %span.build-link ##{build.id}
 
-      - if defined?(ref) && ref
+      - if ref
         - if build.ref
           .icon-container
             = build.tag? ? icon('tag') : icon('code-fork')
@@ -23,12 +31,12 @@
         .icon-container
           = custom_icon("icon_commit")
 
-      - if defined?(commit_sha) && commit_sha
+      - if commit_sha
         = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
 
       - if build.stuck?
         = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
-      - if defined?(retried) && retried
+      - if retried
         = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
 
       .label-container
@@ -40,19 +48,24 @@
           %span.label.label-info triggered
         - if build.try(:allow_failure)
           %span.label.label-danger allowed to fail
-        - if defined?(retried) && retried
+        - if retried
           %span.label.label-warning retried
         - if build.manual?
           %span.label.label-info manual
 
-  - if defined?(runner) && runner
+  - if admin
+    %td
+      - if build.project
+        = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project)
+
+  - if admin
     %td
       - if build.try(:runner)
         = runner_link(build.runner)
       - else
         .light none
 
-  - if defined?(stage) && stage
+  - if stage
     %td
       = build.stage
 
@@ -64,13 +77,14 @@
       %p.duration
         = custom_icon("icon_timer")
         = duration_in_numbers(build.duration)
+
     - if build.finished_at
       %p.finished-at
         = icon("calendar")
         %span #{time_ago_with_tooltip(build.finished_at)}
 
-  - if defined?(coverage) && coverage
-    %td.coverage
+  %td.coverage
+    - if coverage
       - if build.try(:coverage)
         #{build.coverage}%
 
@@ -83,10 +97,10 @@
         - if build.active?
           = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
             = icon('remove', class: 'cred')
-        - elsif defined?(allow_retry) && allow_retry
+        - elsif allow_retry
           - if build.retryable?
             = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
               = icon('repeat')
-          - elsif build.playable?
+          - elsif build.playable? && !admin
             = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
-              = icon('play')
+              = custom_icon('icon_play')
diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml
index 04cbd0c..547bc0c 100644
--- a/app/views/projects/ci/builds/_build_pipeline.html.haml
+++ b/app/views/projects/ci/builds/_build_pipeline.html.haml
@@ -1,14 +1,12 @@
 - is_playable = subject.playable? && can?(current_user, :update_build, @project)
-%li.build{class: ("playable" if is_playable)}
-  .build-content
-    - if is_playable
-      = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
-        = render_status_with_link('build', 'play')
-        = subject.name
-    - elsif can?(current_user, :read_build, @project)
-      = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
-        = render_status_with_link('build', subject.status)
-        = subject.name
-    - else
-      = render_status_with_link('build', subject.status)
-      = ci_icon_for_status(subject.status)
+- if is_playable
+  = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
+    = render_status_with_link('build', 'play')
+    .ci-status-text= subject.name
+- elsif can?(current_user, :read_build, @project)
+  = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
+    = render_status_with_link('build', subject.status)
+    .ci-status-text= subject.name
+- else
+  = render_status_with_link('build', subject.status)
+  = ci_icon_for_status(subject.status)
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b119f6e..6391c67 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -36,16 +36,14 @@
 
 
     - stages_status = pipeline.statuses.relevant.latest.stages_status
-    - stages.each do |stage|
-      %td.stage-cell
+    %td.stage-cell
+      - stages.each do |stage|
         - status = stages_status[stage]
         - tooltip = "#{stage.titleize}: #{status || 'not found'}"
         - if status
-          = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
-            = ci_icon_for_status(status)
-        - else
-          .light.has-tooltip{ title: tooltip }
-            \-
+          .stage-container
+            = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
+              = ci_icon_for_status(status)
 
   %td
     - if pipeline.duration
@@ -66,13 +64,13 @@
           - if actions.any?
             .btn-group
               %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
-                = icon("play")
+                = custom_icon('icon_play')
                 %b.caret
               %ul.dropdown-menu.dropdown-menu-align-right
                 - actions.each do |build|
                   %li
                     = link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do
-                      = icon("play")
+                      = custom_icon('icon_play')
                       %span= build.name.humanize
           - if artifacts.present?
             .btn-group
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index d9b800a..e4cd55b 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -17,7 +17,9 @@
           .form-group.branch
             = label_tag 'target_branch', target_label, class: 'control-label'
             .col-sm-10
-              = select_tag "target_branch", project_branches, class: "select2 select2-sm js-target-branch"
+              = hidden_field_tag :target_branch, @project.default_branch, id: 'target_branch'
+              = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown js-target-branch dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "target_branch", selected: @project.default_branch, target_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false }})
+
               - if can?(current_user, :push_code, @project)
                 .js-create-merge-request-container
                   .checkbox
diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml
index 9d925ca..6bb900e 100644
--- a/app/views/projects/commit/_ci_stage.html.haml
+++ b/app/views/projects/commit/_ci_stage.html.haml
@@ -8,8 +8,8 @@
       - if stage
          
         = stage.titleize
-  = render statuses.latest.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true
-  = render statuses.retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true
+  = render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true
+  = render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true
   %tr
     %td{colspan: 10}
        
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 20a8514..9258f4b 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -39,8 +39,7 @@
               = stage.titleize
           .builds-container
             %ul
-              - statuses.each do |status|
-                = render "projects/#{status.to_partial_path}_pipeline", subject: status
+              = render "projects/commit/pipeline_stage", statuses: statuses
 
 
 - if pipeline.yaml_errors.present?
diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml
new file mode 100644
index 0000000..23c5c51
--- /dev/null
+++ b/app/views/projects/commit/_pipeline_stage.html.haml
@@ -0,0 +1,14 @@
+- status_groups = statuses.group_by(&:group_name)
+- status_groups.each do |group_name, grouped_statuses|
+  - if grouped_statuses.one?
+    - status = grouped_statuses.first
+    - is_playable = status.playable? && can?(current_user, :update_build, @project)
+    %li.build{ class: ("playable" if is_playable) }
+      .curve
+      .build-content
+        = render "projects/#{status.to_partial_path}_pipeline", subject: status
+  - else
+    %li.build
+      .curve
+      .build-content
+        = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml
new file mode 100644
index 0000000..4e7a6f1
--- /dev/null
+++ b/app/views/projects/commit/_pipeline_status_group.html.haml
@@ -0,0 +1,11 @@
+- group_status = CommitStatus.where(id: subject).status
+= render_status_with_link('build', group_status)
+.dropdown.inline
+  %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
+    %span.ci-status-text
+      = name
+    %span.badge= subject.size
+  %ul.dropdown-menu.grouped-pipeline-dropdown
+    .arrow
+    - subject.each do |status|
+      = render "projects/#{status.to_partial_path}_pipeline", subject: status
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index 29f4ef8..f41a11a 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -10,7 +10,10 @@
           %th Commit
           - pipelines.stages.each do |stage|
             %th.stage
-              %span.has-tooltip{ title: "#{stage.titleize}" }
+              - if stage.titleize.length > 12
+                %span.has-tooltip{ title: "#{stage.titleize}" }
+                  = stage.titleize
+              - else
                 = stage.titleize
           %th
           %th
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index fd888f4..389477d 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -7,7 +7,7 @@
 - cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
 - cache_key.push(commit.status) if commit.status
 
-= cache(cache_key) do
+= cache(cache_key, expires_in: 1.day) do
   %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
     = author_avatar(commit, size: 36)
 
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 6115264..4d1ee1c 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,8 +1,5 @@
 .scrolling-tabs-container.sub-nav-scroll
-  .fade-left
-    = icon('angle-left')
-  .fade-right
-    = icon('angle-right')
+  = render 'shared/nav_scroll'
   .nav-links.sub-nav.scrolling-tabs
     %ul{ class: (container_class) }
       = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
new file mode 100644
index 0000000..7f346df
--- /dev/null
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -0,0 +1,59 @@
+- @no_container = true
+- page_title "Cycle Analytics"
+= render "projects/pipelines/head"
+
+#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
+
+  .bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"}
+    = icon('times', class: 'dismiss-icon', "@click": "dismissLanding()")
+    .row
+      .col-sm-3.col-xs-12.svg-container
+        = custom_icon('icon_cycle_analytics_splash')
+      .col-sm-8.col-xs-12.inner-content
+        %h4
+          Introducing Cycle Analytics
+        %p
+          Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
+
+        = link_to "Read more",  help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
+
+  = icon("spinner spin", "v-show" => "isLoading")
+
+  .wrapper{"v-show" => "!isLoading && !hasError"}
+    .panel.panel-default
+      .panel-heading
+        Pipeline Health
+
+      .content-block
+        .container-fluid
+          .row
+            .col-sm-3.col-xs-12.column{"v-for" => "item in analytics.summary"}
+              %h3.header {{item.value}}
+              %p.text {{item.title}}
+
+            .col-sm-3.col-xs-12.column
+              .dropdown.inline.js-ca-dropdown
+                %button.dropdown-menu-toggle{"data-toggle" => "dropdown", :type => "button"}
+                  %span.dropdown-label Last 30 days
+                  %i.fa.fa-chevron-down
+                %ul.dropdown-menu.dropdown-menu-align-right
+                  %li
+                    %a{'href' => "#", 'data-value' => '30'}
+                      Last 30 days
+                  %li
+                    %a{'href' => "#", 'data-value' => '90'}
+                      Last 90 days
+
+    .bordered-box
+      %ul.content-list
+        %li{"v-for" => "item in analytics.stats"}
+          .container-fluid
+            .row
+              .col-xs-8.title-col
+                %p.title
+                  {{item.title}}
+                %p.text
+                  {{item.description}}
+              .col-xs-4.value-col
+                %span
+                  {{item.value}}
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index f7bf3b8..16d134e 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -5,13 +5,13 @@
       .inline
         .dropdown
           %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
-            = icon("play")
+            = custom_icon('icon_play')
             %b.caret
           %ul.dropdown-menu.dropdown-menu-align-right
             - actions.each do |action|
               %li
                 = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
-                  = icon("play")
+                  = custom_icon('icon_play')
                   %span= action.name.humanize
 
     - if local_assigns.fetch(:allow_rollback, false)
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index ad2eb3e..1a51ccd 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -5,7 +5,7 @@
     - unless diff_file.submodule?
       .file-actions.hidden-xs
         - if blob_text_viewable?(blob)
-          = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do
+          = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this files", disabled: @diff_notes_disabled do
             = icon('comment')
           \
 
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index b282aa5..a04d53e 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -44,42 +44,55 @@
         %hr
         %fieldset.features.append-bottom-0
           %h5.prepend-top-0
-            Features
-          .form-group
-            .checkbox
-              = f.label :issues_enabled do
-                = f.check_box :issues_enabled
-                %strong Issues
-                %br
-                %span.descr Lightweight issue tracking system for this project
-          .form-group
-            .checkbox
-              = f.label :merge_requests_enabled do
-                = f.check_box :merge_requests_enabled
-                %strong Merge Requests
-                %br
-                %span.descr Submit changes to be merged upstream
-          .form-group
-            .checkbox
-              = f.label :builds_enabled do
-                = f.check_box :builds_enabled
-                %strong Builds
-                %br
-                %span.descr Test and deploy your changes before merge
-          .form-group
-            .checkbox
-              = f.label :wiki_enabled do
-                = f.check_box :wiki_enabled
-                %strong Wiki
-                %br
-                %span.descr Pages for project documentation
-          .form-group
-            .checkbox
-              = f.label :snippets_enabled do
-                = f.check_box :snippets_enabled
-                %strong Snippets
-                %br
-                %span.descr Share code pastes with others out of git repository
+            Feature Visibility
+
+            = f.fields_for :project_feature do |feature_fields|
+              .form_group.prepend-top-20
+                .row
+                  .col-md-9
+                    = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
+                    %span.help-block Lightweight issue tracking system for this project
+                  .col-md-3
+                    = project_feature_access_select(:issues_access_level)
+
+                .row
+                  .col-md-9
+                    = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
+                    %span.help-block Submit changes to be merged upstream
+                  .col-md-3
+                    = project_feature_access_select(:merge_requests_access_level)
+
+                .row
+                  .col-md-9
+                    = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
+                    %span.help-block Submit Test and deploy your changes before merge
+                  .col-md-3
+                    = project_feature_access_select(:builds_access_level)
+
+                .row
+                  .col-md-9
+                    = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
+                    %span.help-block Pages for project documentation
+                  .col-md-3
+                    = project_feature_access_select(:wiki_access_level)
+
+                .row
+                  .col-md-9
+                    = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
+                    %span.help-block Share code pastes with others out of Git repository
+                  .col-md-3
+                    = project_feature_access_select(:snippets_access_level)
+
+            - if Gitlab.config.lfs.enabled && current_user.admin?
+              .row
+                .col-md-9
+                  = f.label :lfs_enabled, 'LFS', class: 'label-light'
+                  %span.help-block
+                    Git Large File Storage
+                    = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+                .col-md-3
+                  = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control'
+
           - if Gitlab.config.registry.enabled
             .form-group
               .checkbox
@@ -88,7 +101,7 @@
                   %strong Container Registry
                   %br
                   %span.descr Enable Container Registry for this repository
-        %hr
+
         = render 'merge_request_settings', f: f
         %hr
         %fieldset.features.append-bottom-default
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index a1d79bd..bacc570 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -32,11 +32,11 @@
       - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
         = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
           = custom_icon('icon_fork')
-          Fork
+          %span Fork
       - else
         = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
           = custom_icon('icon_fork')
-          Fork
+          %span Fork
 
 
 = render 'projects', projects: @forks
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
index 584c0fa..409f470 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
@@ -1,9 +1,7 @@
-%li.build
-  .build-content
-    - if subject.target_url
-      - link_to subject.target_url do
-        = render_status_with_link('commit status', subject.status)
-        = subject.name
-    - else
-      = render_status_with_link('commit status', subject.status)
-      = subject.name
+- if subject.target_url
+  = link_to subject.target_url do
+    = render_status_with_link('commit status', subject.status)
+    %span.ci-status-text= subject.name
+- else
+  = render_status_with_link('commit status', subject.status)
+  %span.ci-status-text= subject.name
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 45e5138..082e2cb 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -1,16 +1,18 @@
-.nav-links.sub-nav
-  %ul{ class: (container_class) }
+.scrolling-tabs-container.sub-nav-scroll
+  = render 'shared/nav_scroll'
+  .nav-links.sub-nav.scrolling-tabs
+    %ul{ class: (container_class) }
 
-    - content_for :page_specific_javascripts do
-      = page_specific_javascript_tag('lib/chart.js')
-      = page_specific_javascript_tag('graphs/graphs_bundle.js')
-    = nav_link(action: :show) do
-      = link_to 'Contributors', namespace_project_graph_path
-    = nav_link(action: :commits) do
-      = link_to 'Commits', commits_namespace_project_graph_path
-    = nav_link(action: :languages) do
-      = link_to 'Languages', languages_namespace_project_graph_path
-    - if @project.builds_enabled?
-      = nav_link(action: :ci) do
-        = link_to ci_namespace_project_graph_path do
-          Continuous Integration
+      - content_for :page_specific_javascripts do
+        = page_specific_javascript_tag('lib/chart.js')
+        = page_specific_javascript_tag('graphs/graphs_bundle.js')
+      = nav_link(action: :show) do
+        = link_to 'Contributors', namespace_project_graph_path
+      = nav_link(action: :commits) do
+        = link_to 'Commits', commits_namespace_project_graph_path
+      = nav_link(action: :languages) do
+        = link_to 'Languages', languages_namespace_project_graph_path
+      - if @project.feature_available?(:builds, current_user)
+        = nav_link(action: :ci) do
+          = link_to ci_namespace_project_graph_path do
+            Continuous Integration
diff --git a/app/views/projects/hooks/_project_hook.html.haml b/app/views/projects/hooks/_project_hook.html.haml
index 3fcf169..ceabe2e 100644
--- a/app/views/projects/hooks/_project_hook.html.haml
+++ b/app/views/projects/hooks/_project_hook.html.haml
@@ -3,7 +3,7 @@
     .col-md-8.col-lg-7
       %strong.light-header= hook.url
       %div
-        - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events pipeline_events wiki_page_events).each do |trigger|
+        - %w(push_events tag_push_events issues_events confidential_issues_events note_events merge_requests_events build_events pipeline_events wiki_page_events).each do |trigger|
           - if hook.send(trigger)
             %span.label.label-gray.deploy-project-label= trigger.titleize
     .col-md-4.col-lg-5.text-right-lg.prepend-top-5
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index b6cb559..f88b330 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -1,30 +1,32 @@
-.nav-links.sub-nav
-  %ul{ class: (container_class) }
-    - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
-      = nav_link(controller: :issues) do
-        = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
-          %span
-            Issues
+.scrolling-tabs-container.sub-nav-scroll
+  = render 'shared/nav_scroll'
+  .nav-links.sub-nav.scrolling-tabs
+    %ul{ class: (container_class) }
+      - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
+        = nav_link(controller: :issues) do
+          = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
+            %span
+              Issues
 
-      = nav_link(controller: :boards) do
-        = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do
-          %span
-            Board
+        = nav_link(controller: :boards) do
+          = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do
+            %span
+              Board
 
-    - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
-      = nav_link(controller: :merge_requests) do
-        = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
-          %span
-            Merge Requests
+      - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
+        = nav_link(controller: :merge_requests) do
+          = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+            %span
+              Merge Requests
 
-    - if project_nav_tab? :labels
-      = nav_link(controller: :labels) do
-        = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
-          %span
-            Labels
+      - if project_nav_tab? :labels
+        = nav_link(controller: :labels) do
+          = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+            %span
+              Labels
 
-    - if project_nav_tab? :milestones
-      = nav_link(controller: :milestones) do
-        = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
-          %span
-            Milestones
+      - if project_nav_tab? :milestones
+        = nav_link(controller: :milestones) do
+          = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+            %span
+              Milestones
\ No newline at end of file
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 79b1481..8b1a8a8 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,7 +1,7 @@
 %li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
-  - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
+  - if @bulk_edit
     .issue-check
-      = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
+      = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
 
   .issue-title.title
     %span.issue-title-text
@@ -29,7 +29,7 @@
 
       - note_count = issue.notes.user.count
       %li
-        = link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do
+        = link_to issue_path(issue, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
           = icon('comments')
           = note_count
 
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index f34f3c0..a2c31c0 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,4 +1,4 @@
-%ul.content-list.issues-list
+%ul.content-list.issues-list.issuable-list
   = render @issues
   - if @issues.blank?
     %li
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index d807537..31d3ec2 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -1,7 +1,7 @@
 - if @merge_requests.any?
   %h2.merge-requests-title
     = pluralize(@merge_requests.count, 'Related Merge Request')
-  %ul.unstyled-list
+  %ul.unstyled-list.related-merge-requests
     - has_any_ci = @merge_requests.any?(&:pipeline)
     - @merge_requests.each do |merge_request|
       %li
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 2474969..c56b6cc 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,13 +1,12 @@
 - if can?(current_user, :push_code, @project)
   .pull-right
-    #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
+    #new-branch.new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
+      = link_to '#', class: 'checking btn btn-grouped', disabled: 'disabled' do
+        = icon('spinner spin')
+        Checking branches
       = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid),
-        method: :post, class: 'btn btn-new btn-inverted has-tooltip', title: @issue.to_branch_name, disabled: 'disabled' do
-        .checking
-          = icon('spinner spin')
-          Checking branches
-        .available.hide
-          New branch
-        .unavailable.hide
-          = icon('exclamation-triangle')
-          New branch unavailable
+        method: :post, class: 'btn btn-new btn-inverted btn-grouped has-tooltip available hide', title: @issue.to_branch_name do
+        New branch
+      = link_to '#', class: 'unavailable btn btn-grouped hide', disabled: 'disabled' do
+        = icon('exclamation-triangle')
+        New branch unavailable
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index 6ea9f61..44683c8 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -1,11 +1,11 @@
 - if @related_branches.any?
   %h2.related-branches-title
     = pluralize(@related_branches.count, 'Related Branch')
-  %ul.unstyled-list
+  %ul.unstyled-list.related-merge-requests
     - @related_branches.each do |branch|
       %li
         - target = @project.repository.find_branch(branch).target
-        - pipeline = @project.pipeline(target.sha, branch) if target
+        - pipeline = @project.pipeline_for(branch, target.sha) if target
         - if pipeline
           %span.related-branch-ci-status
             = render_pipeline_status(pipeline)
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 1a87045..8da9f21 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,4 +1,6 @@
 - @no_container = true
+- @bulk_edit = can?(current_user, :admin_issue, @project)
+
 - page_title "Issues"
 - new_issue_email = @project.new_issue_address(current_user)
 = render "projects/issues/head"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 9f1a046..3fb4191 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -22,7 +22,7 @@
   - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
     .issuable-actions
       .clearfix.issue-btn-group.dropdown
-        %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+        %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
           %span.caret
           Options
         .dropdown-menu.dropdown-menu-align-right.hidden-lg
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index d070979..3900b4f 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -2,7 +2,7 @@
   - if can?(current_user, :update_merge_request, @merge_request)
     - if @merge_request.open?
       = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"}
-    - if @merge_request.closed?
+    - if @merge_request.reopenable?
       = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
   %comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" }
     %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } }
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 5029b36..68fb7d5 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,4 +1,8 @@
 %li{ class: mr_css_classes(merge_request) }
+  - if @bulk_edit
+    .issue-check
+      = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
+
   .merge-request-title.title
     %span.merge-request-title-text
       = link_to merge_request.title, merge_request_path(merge_request)
@@ -37,7 +41,7 @@
 
       - note_count = merge_request.mr_and_commit_notes.user.count
       %li
-        = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do
+        = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
           = icon('comments')
           = note_count
 
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index 4468877..fe82f75 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -1,4 +1,4 @@
-%ul.content-list.mr-list
+%ul.content-list.mr-list.issuable-list
   = render @merge_requests
   - if @merge_requests.blank?
     %li
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 9d8b4cc..d03ff9e 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -29,17 +29,19 @@
             %ul.dropdown-menu.dropdown-menu-align-right
               %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
               %li= link_to "Plain Diff",    merge_request_path(@merge_request, format: :diff)
-      .normal
-        %span Request to merge
-        %span.label-branch= source_branch_with_namespace(@merge_request)
-        %span into
-        %span.label-branch
-          = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
-        - if @merge_request.open? && @merge_request.diverged_from_target_branch?
-          %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
+      - unless @merge_request.closed_without_fork?
+        .normal
+          %span Request to merge
+          %span.label-branch= source_branch_with_namespace(@merge_request)
+          %span into
+          %span.label-branch
+            = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
+          - if @merge_request.open? && @merge_request.diverged_from_target_branch?
+            %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
 
-    = render "projects/merge_requests/show/how_to_merge"
-    = render "projects/merge_requests/widget/show.html.haml"
+    - unless @merge_request.closed_without_source_project?
+      = render "projects/merge_requests/show/how_to_merge"
+      = render "projects/merge_requests/widget/show.html.haml"
 
     - if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user)
       .light.prepend-top-default.append-bottom-default
@@ -53,10 +55,11 @@
           = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
             Discussion
             %span.badge= @merge_request.mr_and_commit_notes.user.count
-        %li.commits-tab
-          = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
-            Commits
-            %span.badge= @commits_count
+        - unless @merge_request.closed_without_source_project?
+          %li.commits-tab
+            = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
+              Commits
+              %span.badge= @commits_count
         - if @pipeline
           %li.pipelines-tab
             = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
@@ -83,7 +86,7 @@
 
       .tab-content#diff-notes-app
         #notes.notes.tab-pane.voting_notes
-          .content-block.content-block-small.oneline-block
+          .content-block.content-block-small
             = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
 
           .row
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index ace275c..144b3a9 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,4 +1,6 @@
 - @no_container = true
+- @bulk_edit = can?(current_user, :admin_merge_request, @project)
+
 - page_title "Merge Requests"
 = render "projects/issues/head"
 = render 'projects/last_push'
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index 013b056..99c71e1 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,4 +1,5 @@
 - if @merge_request_diff.collected?
+  = render 'projects/merge_requests/show/versions'
   = render "projects/diffs/diffs", diffs: @diffs
 - elsif @merge_request_diff.empty?
   .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
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 b727efa..f1d5441 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
@@ -12,7 +12,7 @@
         %pre.dark#merge-info-1
           - if @merge_request.for_fork?
             :preserve
-              git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch}
+              git fetch #{h default_url_to_repo(@merge_request.source_project)} #{h @merge_request.source_branch}
               git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD
           - else
             :preserve
@@ -47,8 +47,9 @@
             Note that pushing to GitLab requires write access to this repository.
         %p
           %strong Tip:
-          You can also checkout merge requests locally by
-          %a{href: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/workflow/merge_requests.md#checkout-merge-requests-locally', target: '_blank'} following these guidelines
+          = succeed '.' do
+            You can also checkout merge requests locally by
+            = link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank'
 
 :javascript
   $(function(){
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 b24bdf2..e35291d 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,3 +1,7 @@
+- if @merge_request.closed_without_fork?
+  .alert.alert-danger
+    %p The source project of this merge request has been removed.
+
 .clearfix.detail-page-header
   .issuable-header
     .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
@@ -14,7 +18,7 @@
   - if can?(current_user, :update_merge_request, @merge_request)
     .issuable-actions
       .clearfix.issue-btn-group.dropdown
-        %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+        %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
           %span.caret
           Options
         .dropdown-menu.dropdown-menu-align-right.hidden-lg
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
new file mode 100644
index 0000000..4981951
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -0,0 +1,74 @@
+- if @merge_request_diffs.size > 1
+  .mr-version-controls
+    %div.mr-version-menus-container.content-block
+      Changes between
+      %span.dropdown.inline.mr-version-dropdown
+        %a.dropdown-toggle.btn.btn-default{ data: {toggle: :dropdown} }
+          %span
+            - if @merge_request_diff.latest?
+              latest version
+            - else
+              version #{version_index(@merge_request_diff)}
+          %span.caret
+        .dropdown-menu.dropdown-select.dropdown-menu-selectable
+          .dropdown-title
+            %span Version:
+            %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+              = icon('times', class: 'dropdown-menu-close-icon')
+          .dropdown-content
+            %ul
+              - @merge_request_diffs.each do |merge_request_diff|
+                %li
+                  = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do
+                    %strong
+                      - if merge_request_diff.latest?
+                        latest version
+                      - else
+                        version #{version_index(merge_request_diff)}
+                    .monospace #{short_sha(merge_request_diff.head_commit_sha)}
+                    %small
+                      #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)},
+                      = time_ago_with_tooltip(merge_request_diff.created_at)
+
+      - if @merge_request_diff.base_commit_sha
+        and
+        %span.dropdown.inline.mr-version-compare-dropdown
+          %a.btn.btn-default.dropdown-toggle{ data: {toggle: :dropdown} }
+            %span
+              - if @start_sha
+                version #{version_index(@start_version)}
+              - else
+                #{@merge_request.target_branch}
+            %span.caret
+          .dropdown-menu.dropdown-select.dropdown-menu-selectable
+            .dropdown-title
+              %span Compared with:
+              %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
+                = icon('times', class: 'dropdown-menu-close-icon')
+            .dropdown-content
+              %ul
+                - @comparable_diffs.each do |merge_request_diff|
+                  %li
+                    = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do
+                      %strong
+                        - if merge_request_diff.latest?
+                          latest version
+                        - else
+                          version #{version_index(merge_request_diff)}
+                      .monospace #{short_sha(merge_request_diff.head_commit_sha)}
+                      %small
+                        = time_ago_with_tooltip(merge_request_diff.created_at)
+                  %li
+                    = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
+                      %strong
+                        #{@merge_request.target_branch} (base)
+                      .monospace #{short_sha(@merge_request_diff.base_commit_sha)}
+
+    - unless @merge_request_diff.latest? && !@start_sha
+      .comments-disabled-notif.content-block
+        = icon('info-circle')
+        - if @start_sha
+          Comments are disabled because you're comparing two versions of this merge request.
+        - else
+          Comments are disabled because you're viewing an old version of this merge request.
+        = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm'
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 494695a..44e645a 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -43,15 +43,16 @@
       = icon("times-circle")
       Could not connect to the CI server. Please check your settings and try again.
 
-- @merge_request.environments.each do |environment|
-  .mr-widget-heading
-    .ci_widget.ci-success
-      = ci_icon_for_status("success")
-      %span.hidden-sm
-        Deployed to
-        = succeed '.' do
-          = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: 'environment'
-        - external_url = environment.external_url
-        - if external_url
-          = link_to external_url, target: '_blank' do
-            = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true)
+- @merge_request.environments.sort_by(&:name).each do |environment|
+  - if can?(current_user, :read_environment, environment)
+    .mr-widget-heading
+      .ci_widget.ci-success
+        = ci_icon_for_status("success")
+        %span.hidden-sm
+          Deployed to
+          = succeed '.' do
+            = link_to environment.name, environment_path(environment), class: 'environment'
+          - external_url = environment.external_url
+          - if external_url
+            = link_to external_url, target: '_blank' do
+              = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true)
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index ea4898f..fda0592 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -55,16 +55,11 @@
                       = render 'bitbucket_import_modal'
                 %div
                   - if gitlab_import_enabled?
-                    = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless bitbucket_import_configured?}" do
+                    = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
                       = icon('gitlab', text: 'GitLab.com')
                     - unless gitlab_import_configured?
                       = render 'gitlab_import_modal'
                 %div
-                  - if gitorious_import_enabled?
-                    = link_to new_import_gitorious_path, class: 'btn import_gitorious' do
-                      %i.icon-gitorious.icon-gitorious-small
-                      Gitorious.org
-                %div
                   - if google_code_import_enabled?
                     = link_to new_import_google_code_path, class: 'btn import_google_code' do
                       = icon('google', text: 'Google Code')
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 402f5b5..46b4025 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,3 +1,5 @@
+- supports_slash_commands = note_supports_slash_commands?(@note)
+
 = form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form", "data-noteable-iid" => @note.noteable.try(:iid), }, authenticity_token: true do |f|
   = hidden_field_tag :view, diff_view
   = hidden_field_tag :line_type
@@ -14,8 +16,8 @@
       attr: :note,
       classes: 'note-textarea js-note-text',
       placeholder: "Write a comment or drag your files here...",
-      supports_slash_commands: true
-    = render 'projects/notes/hints', supports_slash_commands: true
+      supports_slash_commands: supports_slash_commands
+    = render 'projects/notes/hints', supports_slash_commands: supports_slash_commands
     .error-alert
 
   .note-form-actions.clearfix
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index d2ac1ce..9ec17cf 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,6 +1,5 @@
 - return unless note.author
 - return if note.cross_reference_not_visible_for?(current_user)
-- can_resolve = can?(current_user, :resolve_note, note)
 
 - note_editable = note_editable?(note)
 %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
@@ -24,6 +23,8 @@
               %span.note-role.hidden-xs= access
 
             - if note.resolvable?
+              - can_resolve = can?(current_user, :resolve_note, note)
+
               %resolve-btn{ ":namespace-path" => "'#{note.project.namespace.path}'",
                   ":project-path" => "'#{note.project.path}'",
                   ":discussion-id" => "'#{note.discussion_id}'",
@@ -52,11 +53,11 @@
               - if note.emoji_awardable?
                 = link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do
                   = icon('spinner spin')
-                  = icon('smile-o')
+                  = icon('smile-o', class: 'link-highlight')
 
               - if note_editable
                 = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
-                  = icon('pencil')
+                  = icon('pencil', class: 'link-highlight')
                 = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do
                   = icon('trash-o')
       .note-body{class: note_editable ? 'js-task-list-container' : ''}
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index d65faf8..5f57149 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -1,19 +1,27 @@
-.nav-links.sub-nav
-  %ul{ class: (container_class) }
-    - if project_nav_tab? :pipelines
-      = nav_link(controller: :pipelines) do
-        = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
-          %span
-            Pipelines
+.scrolling-tabs-container.sub-nav-scroll
+  = render 'shared/nav_scroll'
+  .nav-links.sub-nav.scrolling-tabs
+    %ul{ class: (container_class) }
+      - if project_nav_tab? :pipelines
+        = nav_link(controller: :pipelines) do
+          = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+            %span
+              Pipelines
 
-    - if project_nav_tab? :builds
-      = nav_link(controller: %w(builds)) do
-        = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
-          %span
-            Builds
+      - if project_nav_tab? :builds
+        = nav_link(controller: %w(builds)) do
+          = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
+            %span
+              Builds
 
-    - if project_nav_tab? :environments
-      = nav_link(controller: %w(environments)) do
-        = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
-          %span
-            Environments
+      - if project_nav_tab? :environments
+        = nav_link(controller: %w(environments)) do
+          = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
+            %span
+              Environments
+
+      - if can?(current_user, :read_cycle_analytics, @project)
+        = nav_link(controller: %w(cycle_analytics)) do
+          = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do
+            %span
+              Cycle Analytics
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 063e83a..5800ef7 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -10,6 +10,8 @@
   - if @pipeline.duration
     in
     = time_interval_in_words(@pipeline.duration)
+  - if @pipeline.queued_duration
+    = "(queued for #{time_interval_in_words(@pipeline.queued_duration)})"
 
   .pull-right
     = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 5f466bd..faf28db 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -47,10 +47,7 @@
           %tbody
             %th Status
             %th Commit
-            - stages.each do |stage|
-              %th.stage
-                %span.has-tooltip{ title: "#{stage.titleize}" }
-                  = stage.titleize
+            %th Stages
             %th
             %th
           = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml
index 833954b..37e55dc 100644
--- a/app/views/projects/project_members/update.js.haml
+++ b/app/views/projects/project_members/update.js.haml
@@ -1,3 +1,3 @@
 :plain
   $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}');
-  new MemberExpirationDate();
+  new gl.MemberExpirationDate();
diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml
index 8ee2aef..1141168 100644
--- a/app/views/projects/refs/logs_tree.js.haml
+++ b/app/views/projects/refs/logs_tree.js.haml
@@ -5,8 +5,8 @@
 
   :plain
     var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}");
-    row.find("td.tree_time_ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}');
-    row.find("td.tree_commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}');
+    row.find("td.tree-time-ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}');
+    row.find("td.tree-commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}');
 
 - if @more_log_url
   :plain
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
deleted file mode 100644
index 2465831..0000000
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ /dev/null
@@ -1,37 +0,0 @@
-- ref = ref || nil
-- btn_class = btn_class || ''
-- split_button = split_button || false
-- if split_button == true
-  %span.btn-group{class: btn_class}
-    = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
-      %i.fa.fa-download
-      %span Download zip
-    %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
-      %span.caret
-      %span.sr-only
-        Select Archive Format
-    %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
-      %li
-        = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do
-          %i.fa.fa-download
-          %span Download zip
-      %li
-        = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
-          %i.fa.fa-download
-          %span Download tar.gz
-      %li
-        = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
-          %i.fa.fa-download
-          %span Download tar.bz2
-      %li
-        = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do
-          %i.fa.fa-download
-          %span Download tar
-- else
-  %span.btn-group{class: btn_class}
-    = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
-      %i.fa.fa-download
-      %span zip
-    = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
-      %i.fa.fa-download
-      %span tar.gz
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 340e159..9adce77 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -72,7 +72,7 @@
           = render "projects/buttons/koding"
 
         .btn-group.project-repo-btn-group
-          = render "projects/buttons/download"
+          = render 'projects/buttons/download', project: @project, ref: @ref
           = render 'projects/buttons/dropdown'
 
         = render 'shared/notifications/button', notification_setting: @notification_setting
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index bdbf3e5..4aa4ab4 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -3,11 +3,11 @@
     = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
       New Snippet
   - if can?(current_user, :update_project_snippet, @snippet)
+    = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
+      Delete
+  - if can?(current_user, :update_project_snippet, @snippet)
     = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
       Edit
-  - if can?(current_user, :update_project_snippet, @snippet)
-    = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
-      Delete
 - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
   .visible-xs-block.dropdown
     %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -21,9 +21,9 @@
               New Snippet
         - if can?(current_user, :update_project_snippet, @snippet)
           %li
-            = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
-              Edit
-        - if can?(current_user, :update_project_snippet, @snippet)
-          %li
             = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
               Delete
+        - if can?(current_user, :update_project_snippet, @snippet)
+          %li
+            = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
+              Edit
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index bae4d8f..9503dbd 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,15 +1,17 @@
 - page_title @snippet.title, "Snippets"
 
-.snippet-holder
-  = render 'shared/snippets/header'
+= render 'shared/snippets/header'
 
-  %article.file-holder.file-holder-no-border.snippet-file-content
-    .file-title.file-title-clear
+.project-snippets
+  %article.file-holder.snippet-file-content
+    .file-title
       = blob_icon 0, @snippet.file_name
       = @snippet.file_name
-      .file-actions.hidden-xs
+      .file-actions
         = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
         = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
     = render 'shared/snippets/blob'
 
+  = render 'award_emoji/awards_block', awardable: @snippet, inline: true
+
   %div#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml
deleted file mode 100644
index 8a11dbf..0000000
--- a/app/views/projects/tags/_download.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-%span.btn-group
-  = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do
-    %span Source code
-  %a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' }
-    %span.caret
-    %span.sr-only
-      Select Archive Format
-  %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
-    %li
-      = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
-        %span Download zip
-    %li
-      = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
-        %span Download tar.gz
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 2c11c0e..a156d98 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -11,8 +11,7 @@
       = strip_gpg_signature(tag.message)
 
     .controls
-      - if can?(current_user, :download_code, @project)
-        = render 'projects/tags/download', ref: tag.name, project: @project
+      = render 'projects/buttons/download', project: @project, ref: tag.name
 
       - if can?(current_user, :push_code, @project)
         = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 368231e..6adbe93 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -8,21 +8,24 @@
       Tags give the ability to mark specific points in history as being important
 
     .nav-controls
-      - if can? current_user, :push_code, @project
-        = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
-          New tag
+      = form_tag(filter_tags_path, method: :get) do
+        = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false }
       .dropdown.inline
         %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} }
-          %span.light= @sort.humanize
+          %span.light
+            = @sort.humanize
           %b.caret
         %ul.dropdown-menu.dropdown-menu-align-right
           %li
-            = link_to namespace_project_tags_path(sort: nil) do
+            = link_to filter_tags_path(sort: nil) do
               Name
-            = link_to namespace_project_tags_path(sort: sort_value_recently_updated) do
+            = link_to filter_tags_path(sort: sort_value_recently_updated) do
               = sort_title_recently_updated
-            = link_to namespace_project_tags_path(sort: sort_value_oldest_updated) do
+            = link_to filter_tags_path(sort: sort_value_oldest_updated) do
               = sort_title_oldest_updated
+      - if can?(current_user, :push_code, @project)
+        = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
+          New tag
 
   .tags
     - if @tags.any?
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 395d7af..4dd7439 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -12,8 +12,7 @@
         = icon('files-o')
       = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
         = icon('history')
-      - if can? current_user, :download_code, @project
-        = render 'projects/tags/download', ref: @tag.name, project: @project
+      = render 'projects/buttons/download', project: @project, ref: @tag.name
       - if can?(current_user, :admin_project, @project)
         .pull-right
           = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index a3a4dba..ee417b5 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -4,6 +4,6 @@
     - file_name = blob_item.name
     = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
       %span.str-truncated= file_name
-  %td.tree_time_ago.cgray
-    = render 'projects/tree/spinner'
-  %td.hidden-xs.tree_commit
+  %td.hidden-xs.tree-commit
+  %td.tree-time-ago.cgray.text-right
+    = render 'projects/tree/spinner'
\ No newline at end of file
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index baaa2ca..a1f4e3e 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,7 +1,7 @@
 %article.file-holder.readme-holder
   .file-title
     = blob_icon readme.mode, readme.name
-    = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, @path, readme.name)) do
+    = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, @path, readme.name)) do
       %strong
         = readme.name
   .file-content.wiki
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 558e614..0f7d629 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -4,7 +4,6 @@
       %thead
         %tr
           %th Name
-          %th Last Update
           %th.hidden-xs
             .pull-left Last Commit
             .last-commit.hidden-sm.pull-left
@@ -14,9 +13,11 @@
               %small.light
                 = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
                 –
-                = truncate(@commit.title, length: 50)
-            = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
-
+                = time_ago_with_tooltip(@commit.committed_date)
+                = @commit.full_title
+            %small.commit-history-link-spacer |
+            = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'commit-history-link'
+          %th.text-right Last Update
       - if @path.present?
         %tr.tree-item
           %td.tree-item-file-name
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index 9577696..1ccef6d 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -4,6 +4,6 @@
     - path = flatten_tree(tree_item)
     = link_to namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)), title: path do
       %span.str-truncated= path
-  %td.tree_time_ago.cgray
-    = render 'projects/tree/spinner'
-  %td.hidden-xs.tree_commit
+  %td.hidden-xs.tree-commit
+  %td.tree-time-ago.text-right
+    = render 'projects/tree/spinner'
\ No newline at end of file
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index bf5360b..37d3412 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -10,8 +10,7 @@
 %div{ class: container_class }
   .tree-controls
     = render 'projects/find_file_link'
-    - if can? current_user, :download_code, @project
-      = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
+    = render 'projects/buttons/download', project: @project, ref: @ref
 
   #tree-holder.tree-holder.clearfix
     .nav-block
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index 7f3de47..f6e0b0a 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -4,65 +4,89 @@
   .col-lg-3
     %h4.prepend-top-0
       = page_title
-    %p
-      Triggers can force a specific branch or tag to rebuild with an API call.
+    %p.prepend-top-20
+      Triggers can force a specific branch or tag to get rebuilt with an API call.
+    %p.append-bottom-0
+      = succeed '.' do
+        Learn more in the
+        = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank'
   .col-lg-9
-    %h5.prepend-top-0
-      Your triggers
-    - if @triggers.any?
-      .table-responsive
-        %table.table
-          %thead
-            %th Token
-            %th Last used
-            %th
-          = render partial: 'trigger', collection: @triggers, as: :trigger
-    - else
-      %p.settings-message.text-center.append-bottom-default
-        No triggers have been created yet. Add one using the button below.
+    .panel.panel-default
+      .panel-heading
+        %h4.panel-title
+          Manage your project's triggers
+      .panel-body
+        - if @triggers.any?
+          .table-responsive
+            %table.table
+              %thead
+                %th
+                  %strong Token
+                %th
+                  %strong Last used
+                %th
+              = render partial: 'trigger', collection: @triggers, as: :trigger
+        - else
+          %p.settings-message.text-center.append-bottom-default
+            No triggers have been created yet. Add one using the button below.
 
-    = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f|
-      = f.submit "Add Trigger", class: 'btn btn-success'
+        = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f|
+          = f.submit "Add trigger", class: 'btn btn-success'
 
-    %h5.prepend-top-default
-      Use CURL
+      .panel-footer
 
-    %p.light
-      Copy the token above, set your branch or tag name, and that reference will be rebuilt.
+        %p
+          In the following examples, you can see the exact API call you need to
+          make in order to rebuild a specific
+          %code ref
+          (branch or tag) with a trigger token.
+        %p
+          All you need to do is replace the
+          %code TOKEN
+          and
+          %code REF_NAME
+          with the trigger token and the branch or tag name respectively.
 
-    %pre
-      :plain
-        curl -X POST \
-             -F token=TOKEN \
-             -F ref=REF_NAME \
-             #{builds_trigger_url(@project.id)}
-    %h5.prepend-top-default
-      Use .gitlab-ci.yml
+        %h5.prepend-top-default
+          Use cURL
 
-    %p.light
-      In the
-      %code .gitlab-ci.yml
-      of the dependent project, include the following snippet.
-      The project will rebuild at the end of the build.
+        %p.light
+          Copy one of the tokens above, set your branch or tag name, and that
+          reference will be rebuilt.
 
-    %pre
-      :plain
-        trigger:
-          type: deploy
-          script:
-            - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
-    %h5.prepend-top-default
-      Pass build variables
+        %pre
+          :plain
+            curl -X POST \
+                 -F token=TOKEN \
+                 -F ref=REF_NAME \
+                 #{builds_trigger_url(@project.id)}
+        %h5.prepend-top-default
+          Use .gitlab-ci.yml
 
-    %p.light
-      Add
-      %code variables[VARIABLE]=VALUE
-      to an API request. Variable values can be used to distinguish between triggered builds and normal builds.
+        %p.light
+          In the
+          %code .gitlab-ci.yml
+          of another project, include the following snippet.
+          The project will be rebuilt at the end of the build.
 
-    %pre.append-bottom-0
-      :plain
-        curl -X POST \
-             -F token=TOKEN \
-             -F "ref=REF_NAME" \
-             -F "variables[RUN_NIGHTLY_BUILD]=true" \
-             #{builds_trigger_url(@project.id)}
+        %pre
+          :plain
+            trigger_build:
+              stage: deploy
+              script:
+                - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
+        %h5.prepend-top-default
+          Pass build variables
+
+        %p.light
+          Add
+          %code variables[VARIABLE]=VALUE
+          to an API request. Variable values can be used to distinguish between triggered builds and normal builds.
+
+        %pre.append-bottom-0
+          :plain
+            curl -X POST \
+                 -F token=TOKEN \
+                 -F "ref=REF_NAME" \
+                 -F "variables[RUN_NIGHTLY_BUILD]=true" \
+                 #{builds_trigger_url(@project.id)}
diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml
index 6c43f82..07cee86 100644
--- a/app/views/projects/variables/_table.html.haml
+++ b/app/views/projects/variables/_table.html.haml
@@ -9,7 +9,7 @@
       %th Value
       %th
     %tbody
-      - @project.variables.each do |variable|
+      - @project.variables.order_key_asc.each do |variable|
         - if variable.id?
           %tr
             %td= variable.key
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index f8ea479..551a20c 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,13 +1,15 @@
-.nav-links.sub-nav
-  %ul{ class: (container_class) }
-    = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
-      = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
+.scrolling-tabs-container.sub-nav-scroll
+  = render 'shared/nav_scroll'
+  .nav-links.sub-nav.scrolling-tabs
+    %ul{ class: (container_class) }
+      = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
+        = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
 
-    = nav_link(path: 'wikis#pages') do
-      = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
+      = nav_link(path: 'wikis#pages') do
+        = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
 
-    = nav_link(path: 'wikis#git_access') do
-      = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
-        Git Access
+      = nav_link(path: 'wikis#git_access') do
+        = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
+          Git Access
 
-  = render 'projects/wikis/new'
+    = render 'projects/wikis/new'
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 252c375..7fe2bce 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -10,12 +10,16 @@
         in group #{link_to @group.name, @group}
 
   .results.prepend-top-10
-    .search-results
-      - if @scope == 'projects'
-        .term
-          = render 'shared/projects/list', projects: @search_objects
-      - else
-        = render partial: "search/results/#{@scope.singularize}", collection: @search_objects
+    - if @scope == 'commits'
+      %ul.list-unstyled
+        = render partial: "search/results/commit", collection: @search_objects
+    - else
+      .search-results
+        - if @scope == 'projects'
+          .term
+            = render 'shared/projects/list', projects: @search_objects
+        - else
+          = render partial: "search/results/#{@scope.singularize}", collection: @search_objects
 
     - if @scope != 'projects'
       = paginate(@search_objects, theme: 'gitlab')
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 290743f..6f0a0ea 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,4 +1,4 @@
-- blob = @project.repository.parse_search_result(blob)
+- blob = parse_search_result(blob)
 .blob-result
   .file-holder
     .file-title
diff --git a/app/views/search/results/_commit.html.haml b/app/views/search/results/_commit.html.haml
index 4e6c396..5b2d83d 100644
--- a/app/views/search/results/_commit.html.haml
+++ b/app/views/search/results/_commit.html.haml
@@ -1,2 +1 @@
-.search-result-row
-  = render 'projects/commits/commit', project: @project, commit: commit
+= render 'projects/commits/commit', project: @project, commit: commit
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index 235106c..648d0bd 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,4 +1,4 @@
-- wiki_blob = @project.repository.parse_search_result(wiki_blob)
+- wiki_blob = parse_search_result(wiki_blob)
 .blob-result
   .file-holder
     .file-title
diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml
new file mode 100644
index 0000000..9ce6a1a
--- /dev/null
+++ b/app/views/sent_notifications/unsubscribe.html.haml
@@ -0,0 +1,19 @@
+- noteable = @sent_notification.noteable
+- noteable_type = @sent_notification.noteable_type.humanize(capitalize: false)
+- noteable_text = %(#{noteable.title} (#{noteable.to_reference}))
+
+- page_title "Unsubscribe", noteable_text, @sent_notification.noteable_type.humanize.pluralize, @sent_notification.project.name_with_namespace
+
+
+%h3.page-title
+  Unsubscribe from #{noteable_type} #{noteable_text}
+
+%p
+  = succeed '?' do
+    Are you sure you want to unsubscribe from #{noteable_type}
+    = link_to noteable_text, url_for([@sent_notification.project.namespace.becomes(Namespace), @sent_notification.project, noteable])
+
+%p
+  = link_to 'Unsubscribe', unsubscribe_sent_notification_path(@sent_notification, force: true),
+            class: 'btn btn-primary append-right-10'
+  = link_to 'Cancel', new_user_session_path, class: 'btn append-right-10'
diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg
index b07f1c5..9b67422 100644
--- a/app/views/shared/_logo.svg
+++ b/app/views/shared/_logo.svg
@@ -1,9 +1,9 @@
-<svg width="36" height="36" id="tanuki-logo">
-  <path id="tanuki-right-ear" class="tanuki-shape" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
-  <path id="tanuki-left-ear" class="tanuki-shape" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
-  <path id="tanuki-nose" class="tanuki-shape" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
-  <path id="tanuki-right-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/>
-  <path id="tanuki-left-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/>
-  <path id="tanuki-right-cheek" class="tanuki-shape" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/>
-  <path id="tanuki-left-cheek" class="tanuki-shape" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/>
+<svg width="36" height="36" class="tanuki-logo">
+  <path class="tanuki-shape tanuki-left-ear" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
+  <path class="tanuki-shape tanuki-right-ear" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
+  <path class="tanuki-shape tanuki-nose" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
+  <path class="tanuki-shape tanuki-left-eye" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/>
+  <path class="tanuki-shape tanuki-right-eye" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/>
+  <path class="tanuki-shape tanuki-left-cheek" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/>
+  <path class="tanuki-shape tanuki-right-cheek" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/>
 </svg>
diff --git a/app/views/shared/_nav_scroll.html.haml b/app/views/shared/_nav_scroll.html.haml
new file mode 100644
index 0000000..4e3b1b3
--- /dev/null
+++ b/app/views/shared/_nav_scroll.html.haml
@@ -0,0 +1,4 @@
+.fade-left
+  = icon('angle-left')
+.fade-right
+  = icon('angle-right')
\ No newline at end of file
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index ea7162d..9a8252a 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -6,7 +6,7 @@
   - @options && @options.each do |key, value|
     = hidden_field_tag key, value, id: nil
   .dropdown
-    = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project) }, { toggle_class: "js-project-refs-dropdown" }
+    = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
     .dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
       = dropdown_title "Switch branch/tag"
       = dropdown_filter "Search branches and tags"
diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index 107ad19..add4536 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -1,7 +1,7 @@
 .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")
+    = link_to icon('question-circle'), help_page_path("public_access/public_access")
   .col-sm-10
     - if can_change_visibility_level
       = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml
new file mode 100644
index 0000000..60353ae
--- /dev/null
+++ b/app/views/shared/builds/_tabs.html.haml
@@ -0,0 +1,24 @@
+%ul.nav-links
+  %li{ class: ('active' if scope.nil?) }
+    = link_to build_path_proc.call(nil) do
+      All
+      %span.badge.js-totalbuilds-count
+        = number_with_delimiter(all_builds.count(:id))
+
+  %li{ class: ('active' if scope == 'pending') }
+    = link_to build_path_proc.call('pending') do
+      Pending
+      %span.badge
+        = number_with_delimiter(all_builds.pending.count(:id))
+
+  %li{ class: ('active' if scope == 'running') }
+    = link_to build_path_proc.call('running') do
+      Running
+      %span.badge
+        = number_with_delimiter(all_builds.running.count(:id))
+
+  %li{ class: ('active' if scope == 'finished') }
+    = link_to build_path_proc.call('finished') do
+      Finished
+      %span.badge
+        = number_with_delimiter(all_builds.finished.count(:id))
diff --git a/app/views/shared/icons/_icon_cycle_analytics_splash.svg b/app/views/shared/icons/_icon_cycle_analytics_splash.svg
new file mode 100644
index 0000000..eb5a962
--- /dev/null
+++ b/app/views/shared/icons/_icon_cycle_analytics_splash.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 99 102" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="0" d="m35.12 56.988c4.083-4.385 5.968-12.155 5.968-24.04 0-20.2-15.874-32.16-15.874-32.16-1.114-.954-2.929-.979-4.04 0 0 0-15.874 11.957-15.874 32.16 0 11.882 1.884 19.652 5.968 24.04h23.848"/><mask id="1" width="35.783" height="56.924" x="0" y="0" fill="#fff"><use xlink:href="#0"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(0-4)"><g transform= [...]
diff --git a/app/views/shared/icons/_icon_fork.svg b/app/views/shared/icons/_icon_fork.svg
index a21f8f3..fc970e4 100644
--- a/app/views/shared/icons/_icon_fork.svg
+++ b/app/views/shared/icons/_icon_fork.svg
@@ -1,3 +1,3 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
+<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="5 0 30 40">
   <path fill="#7E7E7E" fill-rule="evenodd" d="M22,29.5351288 L22,22.7193602 C26.1888699,21.5098039 29.3985457,16.802989 29.3985457,16.802989 C29.740988,16.3567547 30,15.5559546 30,15.0081969 L30,10.4648712 C31.1956027,9.77325238 32,8.48056471 32,7 C32,4.790861 30.209139,3 28,3 C25.790861,3 24,4.790861 24,7 C24,8.48056471 24.8043973,9.77325238 26,10.4648712 L26,14.7083871 C26,14.8784435 25.9055559,15.0987329 25.7890533,15.2104147 C25.7890533,15.2104147 24.5373893,16.4126202 23.9488702,16. [...]
 </svg>
diff --git a/app/views/shared/icons/_icon_play.svg b/app/views/shared/icons/_icon_play.svg
index 80a6d41..e965afa 100644
--- a/app/views/shared/icons/_icon_play.svg
+++ b/app/views/shared/icons/_icon_play.svg
@@ -1 +1,3 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11"><path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11" class="icon-play">
+  <path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/>
+  </svg>
\ No newline at end of file
diff --git a/app/views/shared/icons/_icon_status_created.svg b/app/views/shared/icons/_icon_status_created.svg
new file mode 100644
index 0000000..1f5c3b5
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_created.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" enable-background="new 0 0 14 14"><path d="M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z" /><circle cx="7" cy="7" r="3.25"/></svg>
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 4f8ea7e..cf26197 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,9 +1,11 @@
+- boards_page = controller.controller_name == 'boards'
+
 .issues-filters
   .issues-details-filters.row-content-block.second-block
-    = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do
-      - if params[:issue_search].present?
-        = hidden_field_tag :issue_search, params[:issue_search]
-      - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
+    = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
+      - if params[:search].present?
+        = hidden_field_tag :search, params[:search]
+      - if @bulk_edit
         .check-all-holder
           = check_box_tag "check_all_issues", nil, false,
             class: "check_all_issues left"
@@ -26,22 +28,28 @@
         .filter-item.inline.labels-filter
           = render "shared/issuable/label_dropdown"
 
+        .filter-item.inline.reset-filters
+          %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters
+
         .pull-right
-          - if controller.controller_name == 'boards' && can?(current_user, :admin_list, @project)
-            .dropdown
-              %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } }
-                Create new list
-              .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
-                = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" }
-                - if can?(current_user, :admin_label, @project)
-                  = render partial: "shared/issuable/label_page_create"
-                = dropdown_loading
+          - if boards_page
+            #js-boards-seach.issue-boards-search
+              %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" }
+              - if can?(current_user, :admin_list, @project)
+                .dropdown.pull-right
+                  %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } }
+                    Create new list
+                  .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
+                    = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" }
+                    - if can?(current_user, :admin_label, @project)
+                      = render partial: "shared/issuable/label_page_create"
+                    = dropdown_loading
           - else
             = render 'shared/sort_dropdown'
 
-    - if controller.controller_name == 'issues'
+    - if @bulk_edit
       .issues_bulk_update.hide
-        = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post, class: 'bulk-update'  do
+        = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update'  do
           .filter-item.inline
             = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
               %ul
@@ -64,10 +72,10 @@
                 %li
                   %a{href: "#", data: {id: "unsubscribe"}} Unsubscribe
 
-          = hidden_field_tag 'update[issues_ids]', []
+          = hidden_field_tag 'update[issuable_ids]', []
           = hidden_field_tag :state_event, params[:state_event]
           .filter-item.inline
-            = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+            = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
 
   - if !@labels.nil?
     .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 5ea0204..0437368 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -1,5 +1,12 @@
 = form_errors(issuable)
 
+- if @conflict
+  .alert.alert-danger
+    Someone edited the #{issuable.class.model_name.human.downcase} the same time you did.
+    Please check out
+    = link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank"
+    and make sure your changes will not unintentionally remove theirs
+
 .form-group
   = f.label :title, class: 'control-label'
 
@@ -12,7 +19,7 @@
 
         = dropdown_tag(title, options: { toggle_class: 'js-issuable-selector',
           title: title, filter: true, placeholder: 'Filter', footer_content: true,
-          data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: @project.path, namespace_path: @project.namespace.path } } ) do
+          data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do
           %ul.dropdown-footer-list
             %li
               %a.reset-template
@@ -121,13 +128,13 @@
     = label_tag :move_to_project_id, 'Move', class: 'control-label'
     .col-sm-10
       .issuable-form-select-holder
-        = hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id) }
+        = hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id), page_size: MoveToProjectFinder::PAGE_SIZE }
        
       %span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
       title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
         = icon('question-circle')
 
-- if issuable.is_a?(MergeRequest)
+- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork?
   %hr
   - if @merge_request.new_record?
     .form-group
@@ -168,7 +175,9 @@
     = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
   - else
     .pull-right
-      - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
+      - if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
         = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
                                                                                                   method: :delete, class: 'btn btn-danger btn-grouped'
       = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
+
+= f.hidden_field :lock_version
diff --git a/app/views/shared/issuable/_search_form.html.haml b/app/views/shared/issuable/_search_form.html.haml
index 186963b..2c89217 100644
--- a/app/views/shared/issuable/_search_form.html.haml
+++ b/app/views/shared/issuable/_search_form.html.haml
@@ -1,2 +1,2 @@
-= form_tag(path, method: :get, id: "issue_search_form", class: 'issue-search-form') do
-  = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input input-short', spellcheck: false }
+= form_tag(path, method: :get, id: "issuable_search_form", class: 'issuable-search-form') do
+  = search_field_tag :search, params[:search], { id: 'issuable_search', placeholder: 'Filter by name ...', class: 'form-control issuable_search search-text-input input-short', spellcheck: false }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index c1b50e6..b13daaf 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -118,7 +118,7 @@
             = icon('spinner spin', class: 'block-loading')
             - if can_edit_issuable
               = link_to 'Edit', '#', class: 'edit-link pull-right'
-          .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
+          .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
             - if issuable.labels_array.any?
               - issuable.labels_array.each do |label|
                 = link_to_label(label, type: issuable.to_ability_name)
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 281ec72..66c3096 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -20,11 +20,11 @@
       - if forks
         %span
           = icon('code-fork')
-          = project.forks_count
+          = number_with_delimiter(project.forks_count)
       - if stars
         %span
           = icon('star')
-          = project.star_count
+          = number_with_delimiter(project.star_count)
       %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project)}
         = visibility_level_icon(project.visibility_level, fw: true)
 
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index af75349..7ae4211 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -6,12 +6,13 @@
   %strong.item-title
     Snippet #{@snippet.to_reference}
   %span.creator
-    created by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title")}
+    authored
     = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
     - if @snippet.updated_at != @snippet.created_at
       %span
         = icon('edit', title: 'edited')
         = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
+    by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")}
 
   .snippet-actions
     - if @snippet.project_id?
@@ -19,6 +20,5 @@
     - else
       = render "snippets/actions"
 
-.content-block.second-block
-  %h2.snippet-title.prepend-top-0.append-bottom-0
-    = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author
+%h2.snippet-title.prepend-top-0.append-bottom-0
+  = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index c96dfef..ea17bec 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -3,19 +3,30 @@
 
   .title
     = link_to reliable_snippet_path(snippet) do
-      = truncate(snippet.title, length: 60)
+      = snippet.title
       - if snippet.private?
-        %span.label.label-gray
+        %span.label.label-gray.hidden-xs
           = icon('lock')
           private
-    %span.monospace.pull-right
+    %span.monospace.pull-right.hidden-xs
       = snippet.file_name
 
-  %small.pull-right.cgray
+    %ul.controls.visible-xs
+      %li
+        - note_count = snippet.notes.user.count
+        = link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
+          = icon('comments')
+          = note_count
+      %li
+        %span.sr-only
+          = visibility_level_label(snippet.visibility_level)
+        = visibility_level_icon(snippet.visibility_level, fw: false)
+
+  %small.pull-right.cgray.hidden-xs
     - if snippet.project_id?
       = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
 
-  .snippet-info
+  .snippet-info.hidden-xs
     = link_to user_snippets_path(snippet.author) do
       = snippet.author_name
     authored #{time_ago_with_tooltip(snippet.created_at)}
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index d2ec6c3..5d659eb 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -52,6 +52,13 @@
               %p.light
                 This URL will be triggered when an issue is created/updated/merged
           %li
+            = f.check_box :confidential_issues_events, class: 'pull-left'
+            .prepend-left-20
+              = f.label :confidential_issues_events, class: 'list-label' do
+                %strong Confidential Issues events
+              %p.light
+                This URL will be triggered when a confidential issue is created/updated/merged
+          %li
             = f.check_box :merge_requests_events, class: 'pull-left'
             .prepend-left-20
               = f.label :merge_requests_events, class: 'list-label' do
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 160c6cd..fdaca19 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -2,12 +2,12 @@
   - if current_user
     = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
       New Snippet
-  - if can?(current_user, :update_personal_snippet, @snippet)
-    = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
-      Edit
   - if can?(current_user, :admin_personal_snippet, @snippet)
     = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
       Delete
+  - if can?(current_user, :update_personal_snippet, @snippet)
+    = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
+      Edit
 - if current_user
   .visible-xs-block.dropdown
     %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -18,11 +18,11 @@
         %li
           = link_to new_snippet_path, title: "New Snippet" do
             New Snippet
-        - if can?(current_user, :update_personal_snippet, @snippet)
-          %li
-            = link_to edit_snippet_path(@snippet) do
-              Edit
         - if can?(current_user, :admin_personal_snippet, @snippet)
           %li
             = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
               Delete
+        - if can?(current_user, :update_personal_snippet, @snippet)
+          %li
+            = link_to edit_snippet_path(@snippet) do
+              Edit
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index 80a3e73..7be4a47 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -1,7 +1,11 @@
-%ul.content-list
-  = render partial: 'shared/snippets/snippet', collection: @snippets
-  - if @snippets.empty?
-    %li
-      .nothing-here-block Nothing here.
+.snippets-list-holder
+  %ul.content-list
+    = render partial: 'shared/snippets/snippet', collection: @snippets
+    - if @snippets.empty?
+      %li
+        .nothing-here-block Nothing here.
 
-= paginate @snippets, theme: 'gitlab'
+  = paginate @snippets, theme: 'gitlab', remote: true
+
+:javascript
+  gl.SnippetsList();
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index ed39926..cd89155 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,13 +1,14 @@
 - page_title @snippet.title, "Snippets"
 
-.snippet-holder
-  = render 'shared/snippets/header'
+= render 'shared/snippets/header'
 
-  %article.file-holder.file-holder-no-border.snippet-file-content
-    .file-title.file-title-clear
-      = blob_icon 0, @snippet.file_name
-      = @snippet.file_name
-      .file-actions.hidden-xs
-        = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
-        = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
-    = render 'shared/snippets/blob'
+%article.file-holder.snippet-file-content
+  .file-title
+    = blob_icon 0, @snippet.file_name
+    = @snippet.file_name
+    .file-actions
+      = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
+      = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
+  = render 'shared/snippets/blob'
+
+= render 'award_emoji/awards_block', awardable: @snippet, inline: true
\ No newline at end of file
diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml
index 75fb0e3..9657101 100644
--- a/app/views/u2f/_authenticate.html.haml
+++ b/app/views/u2f/_authenticate.html.haml
@@ -20,6 +20,8 @@
   %div
     %p We heard back from your U2F device. Click this button to authenticate with the GitLab server.
     = form_tag(new_user_session_path, method: :post) do |f|
+      - resource_params = params[resource_name].presence || params
+      = hidden_field_tag 'user[remember_me]', resource_params.fetch(:remember_me, 0)
       = hidden_field_tag 'user[device_response]', nil, class: 'form-control', required: true, id: "js-device-response"
       = submit_tag "Authenticate via U2F Device", class: "btn btn-success"
 
diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml
index 77f2dde..09ff8a7 100644
--- a/app/views/users/calendar.html.haml
+++ b/app/views/users/calendar.html.haml
@@ -4,6 +4,6 @@
     Summary of issues, merge requests, and push events
 :javascript
   new Calendar(
-    #{@timestamps.to_json},
+    #{@activity_dates.to_json},
     '#{user_calendar_activities_path}'
-  );
+  );
\ No newline at end of file
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index c7f3986..9a052ab 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -123,6 +123,6 @@
 :javascript
   var userProfile;
 
-  userProfile = new User({
+  userProfile = new gl.User({
     action: "#{controller.action_name}"
   });
diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb
new file mode 100644
index 0000000..5883caf
--- /dev/null
+++ b/app/workers/prune_old_events_worker.rb
@@ -0,0 +1,17 @@
+class PruneOldEventsWorker
+  include Sidekiq::Worker
+
+  def perform
+    # Contribution calendar shows maximum 12 months of events.
+    # Double nested query is used because MySQL doesn't allow DELETE subqueries
+    # on the same table.
+    Event.unscoped.where(
+      '(id IN (SELECT id FROM (?) ids_to_remove))',
+      Event.unscoped.where(
+        'created_at < ?',
+        (12.months + 1.day).ago).
+      select(:id).
+      limit(10_000)).
+    delete_all
+  end
+end
diff --git a/changelogs/archive.md b/changelogs/archive.md
new file mode 100644
index 0000000..c68ab69
--- /dev/null
+++ b/changelogs/archive.md
@@ -0,0 +1,1810 @@
+## 7.14.3
+
+- No changes
+
+## 7.14.2
+
+- Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with ISO-encoded files (Stan Hu)
+- Allow configuration of LDAP attributes GitLab will use for the new user account.
+
+## 7.14.1
+
+- Improve abuse reports management from admin area
+- Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
+- Disabled DNS lookups for SSH in docker image (Rowan Wookey)
+- Only include base URL in OmniAuth full_host parameter (Stan Hu)
+- Fix Error 500 in API when accessing a group that has an avatar (Stan Hu)
+- Ability to enable SSL verification for Webhooks
+
+## 7.14.0
+
+- Fix bug where non-project members of the target project could set labels on new merge requests.
+- Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller)
+- Fix redirection after sign in when using auto_sign_in_with_provider
+- Upgrade gitlab_git to 7.2.14 to ignore CRLFs in .gitmodules (Stan Hu)
+- Clear cache to prevent listing deleted branches after MR removes source branch (Stan Hu)
+- Provide more feedback what went wrong if HipChat service failed test (Stan Hu)
+- Fix bug where backslashes in inline diffs could be dropped (Stan Hu)
+- Disable turbolinks when linking to Bitbucket import status (Stan Hu)
+- Fix broken code import and display error messages if something went wrong with creating project (Stan Hu)
+- Fix corrupted binary files when using API files endpoint (Stan Hu)
+- Bump Haml to 4.0.7 to speed up textarea rendering (Stan Hu)
+- Show incompatible projects in Bitbucket import status (Stan Hu)
+- Fix coloring of diffs on MR Discussion-tab (Gert Goet)
+- Fix "Network" and "Graphs" pages for branches with encoded slashes (Stan Hu)
+- Fix errors deleting and creating branches with encoded slashes (Stan Hu)
+- Always add current user to autocomplete controller to support filter by "Me" (Stan Hu)
+- Fix multi-line syntax highlighting (Stan Hu)
+- Fix network graph when branch name has single quotes (Stan Hu)
+- Add "Confirm user" button in user admin page (Stan Hu)
+- Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu)
+- Add support for Unicode filenames in relative links (Hiroyuki Sato)
+- Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki)
+- Fix commit data retrieval when branch name has single quotes (Stan Hu)
+- Check that project was actually created rather than just validated in import:repos task (Stan Hu)
+- Fix full screen mode for snippet comments (Daniel Gerhardt)
+- Fix 404 error in files view after deleting the last file in a repository (Stan Hu)
+- Fix the "Reload with full diff" URL button (Stan Hu)
+- Fix label read access for unauthenticated users (Daniel Gerhardt)
+- Fix access to disabled features for unauthenticated users (Daniel Gerhardt)
+- Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu)
+- Fix file upload dialog for comment editing (Daniel Gerhardt)
+- Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu)
+- Return comments in created order in merge request API (Stan Hu)
+- Disable internal issue tracker controller if external tracker is used (Stan Hu)
+- Expire Rails cache entries after two weeks to prevent endless Redis growth
+- Add support for destroying project milestones (Stan Hu)
+- Allow custom backup archive permissions
+- Add project star and fork count, group avatar URL and user/group web URL attributes to API
+- Show who last edited a comment if it wasn't the original author
+- Send notification to all participants when MR is merged.
+- Add ability to manage user email addresses via the API.
+- Show buttons to add license, changelog and contribution guide if they're missing.
+- Tweak project page buttons.
+- Disabled autocapitalize and autocorrect on login field (Daryl Chan)
+- Mention group and project name in creation, update and deletion notices (Achilleas Pipinellis)
+- Update gravatar link on profile page to link to configured gravatar host (Ben Bodenmiller)
+- Remove redis-store TTL monkey patch
+- Add support for CI skipped status
+- Fetch code from forks to refs/merge-requests/:id/head when merge request created
+- Remove comments and email addresses when publicly exposing ssh keys (Zeger-Jan van de Weg)
+- Add "Check out branch" button to the MR page.
+- Improve MR merge widget text and UI consistency.
+- Improve text in MR "How To Merge" modal.
+- Cache all events
+- Order commits by date when comparing branches
+- Fix bug causing error when the target branch of a symbolic ref was deleted
+- Include branch/tag name in archive file and directory name
+- Add dropzone upload progress
+- Add a label for merged branches on branches page (Florent Baldino)
+- Detect .mkd and .mkdn files as markdown (Ben Boeckel)
+- Fix: User search feature in admin area does not respect filters
+- Set max-width for README, issue and merge request description for easier read on big screens
+- Update Flowdock integration to support new Flowdock API (Boyan Tabakov)
+- Remove author from files view (Sven Strickroth)
+- Fix infinite loop when SAML was incorrectly configured.
+
+## 7.13.5
+
+- Satellites reverted
+
+## 7.13.4
+
+- Allow users to send abuse reports
+
+## 7.13.3
+
+- Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
+- Allow users to send abuse reports
+- Remove satellites
+- Link username to profile on Group Members page (Tom Webster)
+
+## 7.13.2
+
+- Fix randomly failed spec
+- Create project services on Project creation
+- Add admin_merge_request ability to Developer level and up
+- Fix Error 500 when browsing projects with no HEAD (Stan Hu)
+- Fix labels / assignee / milestone for the merge requests when issues are disabled
+- Show the first tab automatically on MergeRequests#new
+- Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt)
+- Fix Gmail Actions
+
+## 7.13.1
+
+- Fix: Label modifications are not reflected in existing notes and in the issue list
+- Fix: Label not shown in the Issue list, although it's set through web interface
+- Fix: Group/project references are linked incorrectly
+- Improve documentation
+- Fix of migration: Check if session_expire_delay column exists before adding the column
+- Fix: ActionView::Template::Error
+- Fix: "Create Merge Request" isn't always shown in event for newly pushed branch
+- Fix bug causing "Remove source-branch" option not to work for merge requests from the same project.
+- Render Note field hints consistently for "new" and "edit" forms
+
+## 7.13.0
+
+- Remove repository graph log to fix slow cache updates after push event (Stan Hu)
+- Only enable HSTS header for HTTPS and port 443 (Stan Hu)
+- Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
+- Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
+- Add branch switching support for graphs (Daniel Gerhardt)
+- Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
+- Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
+- Add support for unlocking users in admin settings (Stan Hu)
+- Add Irker service configuration options (Stan Hu)
+- Fix order of issues imported from GitHub (Hiroyuki Sato)
+- Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart)
+- Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI
+- Add `two_factor_enabled` field to admin user API (Stan Hu)
+- Fix invalid timestamps in RSS feeds (Rowan Wookey)
+- Fix downloading of patches on public merge requests when user logged out (Stan Hu)
+- Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu)
+- Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu)
+- Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu)
+- Support commenting on diffs in side-by-side mode (Stan Hu)
+- Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu)
+- Return 40x error codes if branch could not be deleted in UI (Stan Hu)
+- Remove project visibility icons from dashboard projects list
+- Rename "Design" profile settings page to "Preferences".
+- Allow users to customize their default Dashboard page.
+- Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8
+- Admin can edit and remove user identities
+- Convert CRLF newlines to LF when committing using the web editor.
+- API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged.
+- Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled.
+- Show a user's Two-factor Authentication status in the administration area.
+- Explicit error when commit not found in the CI
+- Improve performance for issue and merge request pages
+- Users with guest access level can not set assignee, labels or milestones for issue and merge request
+- Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels
+- Better performance for pages with events list, issues list and commits list
+- Faster automerge check and merge itself when source and target branches are in same repository
+- Correctly show anonymous authorized applications under Profile > Applications.
+- Query Optimization in MySQL.
+- Allow users to be blocked and unblocked via the API
+- Use native Postgres database cleaning during backup restore
+- Redesign project page. Show README as default instead of activity. Move project activity to separate page
+- Make left menu more hierarchical and less contextual by adding back item at top
+- A fork can’t have a visibility level that is greater than the original project.
+- Faster code search in repository and wiki. Fixes search page timeout for big repositories
+- Allow administrators to disable 2FA for a specific user
+- Add error message for SSH key linebreaks
+- Store commits count in database (will populate with valid values only after first push)
+- Rebuild cache after push to repository in background job
+- Fix transferring of project to another group using the API.
+
+## 7.12.2
+
+- Correctly show anonymous authorized applications under Profile > Applications.
+- Faster automerge check and merge itself when source and target branches are in same repository
+- Audit log for user authentication
+- Allow custom label to be set for authentication providers.
+
+## 7.12.1
+
+- Fix error when deleting a user who has projects (Stan Hu)
+- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
+- Add SAML to list of social_provider (Matt Firtion)
+- Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets)
+- Fix closed merge request scope at milestone page (Dmitriy Zaporozhets)
+- Revert merge request states renaming
+- Fix hooks for web based events with external issue references (Daniel Gerhardt)
+- Improve performance for issue and merge request pages
+- Compress database dumps to reduce backup size
+
+## 7.12.0
+
+- Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu)
+- Disable changing of target branch in new merge request page when a branch has already been specified (Stan Hu)
+- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
+- Update oauth button logos for Twitter and Google to recommended assets
+- Update browser gem to version 0.8.0 for IE11 support (Stan Hu)
+- Fix timeout when rendering file with thousands of lines.
+- Add "Remember me" checkbox to LDAP signin form.
+- Add session expiration delay configuration through UI application settings
+- Don't notify users mentioned in code blocks or blockquotes.
+- Omit link to generate labels if user does not have access to create them (Stan Hu)
+- Show warning when a comment will add 10 or more people to the discussion.
+- Disable changing of the source branch in merge request update API (Stan Hu)
+- Shorten merge request WIP text.
+- Add option to disallow users from registering any application to use GitLab as an OAuth provider
+- Support editing target branch of merge request (Stan Hu)
+- Refactor permission checks with issues and merge requests project settings (Stan Hu)
+- Fix Markdown preview not working in Edit Milestone page (Stan Hu)
+- Fix Zen Mode not closing with ESC key (Stan Hu)
+- Allow HipChat API version to be blank and default to v2 (Stan Hu)
+- Add file attachment support in Milestone description (Stan Hu)
+- Fix milestone "Browse Issues" button.
+- Set milestone on new issue when creating issue from index with milestone filter active.
+- Make namespace API available to all users (Stan Hu)
+- Add webhook support for note events (Stan Hu)
+- Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu)
+- Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu)
+- Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu)
+- Fix git blame syntax highlighting when different commits break up lines (Stan Hu)
+- Add "Resend confirmation e-mail" link in profile settings (Stan Hu)
+- Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka)
+- Disabled expansion of top/bottom blobs for new file diffs
+- Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka)
+- Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka)
+- Use the user list from the target project in a merge request (Stan Hu)
+- Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen)
+- Add validation to wiki page creation (only [a-zA-Z0-9/_-] are allowed) (Jeroen van Baarsen)
+- Fix new/empty milestones showing 100% completion value (Jonah Bishop)
+- Add a note when an Issue or Merge Request's title changes
+- Consistently refer to MRs as either Merged or Closed.
+- Add Merged tab to MR lists.
+- Prefix EmailsOnPush email subject with `[Git]`.
+- Group project contributions by both name and email.
+- Clarify navigation labels for Project Settings and Group Settings.
+- Move user avatar and logout button to sidebar
+- You can not remove user if he/she is an only owner of group
+- User should be able to leave group. If not - show him proper message
+- User has ability to leave project
+- Add SAML support as an omniauth provider
+- Allow to configure a URL to show after sign out
+- Add an option to automatically sign-in with an Omniauth provider
+- GitLab CI service sends .gitlab-ci.yml in each push call
+- When remove project - move repository and schedule it removal
+- Improve group removing logic
+- Trigger create-hooks on backup restore task
+- Add option to automatically link omniauth and LDAP identities
+- Allow special character in users bio. I.e.: I <3 GitLab
+
+## 7.11.4
+
+- Fix missing bullets when creating lists
+- Set rel="nofollow" on external links
+
+## 7.11.3
+
+- no changes
+- Fix upgrader script (Martins Polakovs)
+
+## 7.11.2
+
+- no changes
+
+## 7.11.1
+
+- no changes
+
+## 7.11.0
+
+- Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger)
+- Get editing comments to work in Chrome 43 again.
+- Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu)
+- Don't show duplicate deploy keys
+- Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger)
+- Make the first branch pushed to an empty repository the default HEAD (Stan Hu)
+- Fix broken view when using a tag to display a tree that contains git submodules (Stan Hu)
+- Make Reply-To config apply to change e-mail confirmation and other Devise notifications (Stan Hu)
+- Add application setting to restrict user signups to e-mail domains (Stan Hu)
+- Don't allow a merge request to be merged when its title starts with "WIP".
+- Add a page title to every page.
+- Allow primary email to be set to an email that you've already added.
+- Fix clone URL field and X11 Primary selection (Dmitry Medvinsky)
+- Ignore invalid lines in .gitmodules
+- Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu)
+- Redirect to sign in page after signing out.
+- Fix "Hello @username." references not working by no longer allowing usernames to end in period.
+- Fix "Revspec not found" errors when viewing diffs in a forked project with submodules (Stan Hu)
+- Improve project page UI
+- Fix broken file browsing with relative submodule in personal projects (Stan Hu)
+- Add "Reply quoting selected text" shortcut key (`r`)
+- Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention.
+- Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention.
+- When use change branches link at MR form - save source branch selection instead of target one
+- Improve handling of large diffs
+- Added GitLab Event header for project hooks
+- Add Two-factor authentication (2FA) for GitLab logins
+- Show Atom feed buttons everywhere where applicable.
+- Add project activity atom feed.
+- Don't crash when an MR from a fork has a cross-reference comment from the target project on one of its commits.
+- Explain how to get a new password reset token in welcome emails
+- Include commit comments in MR from a forked project.
+- Group milestones by title in the dashboard and all other issue views.
+- Query issues, merge requests and milestones with their IID through API (Julien Bianchi)
+- Add default project and snippet visibility settings to the admin web UI.
+- Show incompatible projects in Google Code import status (Stan Hu)
+- Fix bug where commit data would not appear in some subdirectories (Stan Hu)
+- Task lists are now usable in comments, and will show up in Markdown previews.
+- Fix bug where avatar filenames were not actually deleted from the database during removal (Stan Hu)
+- Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu)
+- Protect OmniAuth request phase against CSRF.
+- Don't send notifications to mentioned users that don't have access to the project in question.
+- Add search issues/MR by number
+- Change plots to bar graphs in commit statistics screen
+- Move snippets UI to fluid layout
+- Improve UI for sidebar. Increase separation between navigation and content
+- Improve new project command options (Ben Bodenmiller)
+- Add common method to force UTF-8 and use it to properly handle non-ascii OAuth user properties (Onur Küçük)
+- Prevent sending empty messages to HipChat (Chulki Lee)
+- Improve UI for mobile phones on dashboard and project pages
+- Add room notification and message color option for HipChat
+- Allow to use non-ASCII letters and dashes in project and namespace name. (Jakub Jirutka)
+- Add footnotes support to Markdown (Guillaume Delbergue)
+- Add current_sign_in_at to UserFull REST api.
+- Make Sidekiq MemoryKiller shutdown signal configurable
+- Add "Create Merge Request" buttons to commits and branches pages and push event.
+- Show user roles by comments.
+- Fix automatic blocking of auto-created users from Active Directory.
+- Call merge request webhook for each new commits (Arthur Gautier)
+- Use SIGKILL by default in Sidekiq::MemoryKiller
+- Fix mentioning of private groups.
+- Add style for <kbd> element in markdown
+- Spin spinner icon next to "Checking for CI status..." on MR page.
+- Fix reference links in dashboard activity and ATOM feeds.
+- Ensure that the first added admin performs repository imports
+
+## 7.10.4
+
+- Fix migrations broken in 7.10.2
+- Make tags for GitLab installations running on MySQL case sensitive
+- Get Gitorious importer to work again.
+- Fix adding new group members from admin area
+- Fix DB error when trying to tag a repository (Stan Hu)
+- Fix Error 500 when searching Wiki pages (Stan Hu)
+- Unescape branch names in compare commit (Stan Hu)
+- Order commit comments chronologically in API.
+
+## 7.10.2
+
+- Fix CI links on MR page
+
+## 7.10.0
+
+- Ignore submodules that are defined in .gitmodules but are checked in as directories.
+- Allow projects to be imported from Google Code.
+- Remove access control for uploaded images to fix broken images in emails (Hannes Rosenögger)
+- Allow users to be invited by email to join a group or project.
+- Don't crash when project repository doesn't exist.
+- Add config var to block auto-created LDAP users.
+- Don't use HTML ellipsis in EmailsOnPush subject truncated commit message.
+- Set EmailsOnPush reply-to address to committer email when enabled.
+- Fix broken file browsing with a submodule that contains a relative link (Stan Hu)
+- Fix persistent XSS vulnerability around profile website URLs.
+- Fix project import URL regex to prevent arbitary local repos from being imported.
+- Fix directory traversal vulnerability around uploads routes.
+- Fix directory traversal vulnerability around help pages.
+- Don't leak existence of project via search autocomplete.
+- Don't leak existence of group or project via search.
+- Fix bug where Wiki pages that included a '/' were no longer accessible (Stan Hu)
+- Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu)
+- Add a rake task to check repository integrity with `git fsck`
+- Add ability to configure Reply-To address in gitlab.yml (Stan Hu)
+- Move current user to the top of the list in assignee/author filters (Stan Hu)
+- Fix broken side-by-side diff view on merge request page (Stan Hu)
+- Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu)
+- Allow HTML tags in Markdown input
+- Fix code unfold not working on Compare commits page (Stan Hu)
+- Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik)
+- Fix "Import projects from" button to show the correct instructions (Stan Hu)
+- Fix dots in Wiki slugs causing errors (Stan Hu)
+- Make maximum attachment size configurable via Application Settings (Stan Hu)
+- Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg)
+- Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu)
+- Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu)
+- Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu)
+- enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger)
+- Fix a link in the patch update guide
+- Add a service to support external wikis (Hannes Rosenögger)
+- Omit the "email patches" link and fix plain diff view for merge commits
+- List new commits for newly pushed branch in activity view.
+- Add sidetiq gem dependency to match EE
+- Add changelog, license and contribution guide links to project tab bar.
+- Improve diff UI
+- Fix alignment of navbar toggle button (Cody Mize)
+- Fix checkbox rendering for nested task lists
+- Identical look of selectboxes in UI
+- Upgrade the gitlab_git gem to version 7.1.3
+- Move "Import existing repository by URL" option to button.
+- Improve error message when save profile has error.
+- Passing the name of pushed ref to CI service (requires GitLab CI 7.9+)
+- Add location field to user profile
+- Fix print view for markdown files and wiki pages
+- Fix errors when deleting old backups
+- Improve GitLab performance when working with git repositories
+- Add tag message and last commit to tag hook (Kamil Trzciński)
+- Restrict permissions on backup files
+- Improve oauth accounts UI in profile page
+- Add ability to unlink connected accounts
+- Replace commits calendar with faster contribution calendar that includes issues and merge requests
+- Add inifinite scroll to user page activity
+- Don't include system notes in issue/MR comment count.
+- Don't mark merge request as updated when merge status relative to target branch changes.
+- Link note avatar to user.
+- Make Git-over-SSH errors more descriptive.
+- Fix EmailsOnPush.
+- Refactor issue filtering
+- AJAX selectbox for issue assignee and author filters
+- Fix issue with missing options in issue filtering dropdown if selected one
+- Prevent holding Control-Enter or Command-Enter from posting comment multiple times.
+- Prevent note form from being cleared when submitting failed.
+- Improve file icons rendering on tree (Sullivan Sénéchal)
+- API: Add pagination to project events
+- Get issue links in notification mail to work again.
+- Don't show commit comment button when user is not signed in.
+- Fix admin user projects lists.
+- Don't leak private group existence by redirecting from namespace controller to group controller.
+- Ability to skip some items from backup (database, respositories or uploads)
+- Archive repositories in background worker.
+- Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace.
+- Project labels are now available over the API under the "tag_list" field (Cristian Medina)
+- Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz)
+- Fix and improve help rendering (Sullivan Sénéchal)
+- Fix final line in EmailsOnPush email diff being rendered as error.
+- Prevent duplicate Buildkite service creation.
+- Fix git over ssh errors 'fatal: protocol error: bad line length character'
+- Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled
+- Bust group page project list cache when namespace name or path changes.
+- Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded
+- Allow user to choose a public email to show on public profile
+- Remove truncation from issue titles on milestone page (Jason Blanchard)
+- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
+- Fix merge request comments on files with multiple commits
+- Fix Resource Owner Password Authentication Flow
+- Add icons to Add dropdown items.
+- Allow admin to create public deploy keys that are accessible to any project.
+- Warn when gitlab-shell version doesn't match requirement.
+- Skip email confirmation when set by admin or via LDAP.
+- Only allow users to reference groups, projects, issues, MRs, commits they have access to.
+
+## 7.9.4
+
+- Security: Fix project import URL regex to prevent arbitary local repos from being imported
+- Fixed issue where only 25 commits would load in file listings
+- Fix LDAP identities  after config update
+
+## 7.9.3
+
+- Contains no changes
+
+## 7.9.2
+
+- Contains no changes
+
+## 7.9.1
+
+- Include missing events and fix save functionality in admin service template settings form (Stan Hu)
+- Fix "Import projects from" button to show the correct instructions (Stan Hu)
+- Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu)
+- Fix for LDAP with commas in DN
+- Fix missing events and in admin Slack service template settings form (Stan Hu)
+- Don't show commit comment button when user is not signed in.
+- Downgrade gemnasium-gitlab-service gem
+
+## 7.9.0
+
+- Add HipChat integration documentation (Stan Hu)
+- Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu)
+- Fix broken email images (Hannes Rosenögger)
+- Automatically config git if user forgot, where possible (Zeger-Jan van de Weg)
+- Fix mass SQL statements on initial push (Hannes Rosenögger)
+- Add tag push notifications and normalize HipChat and Slack messages to be consistent (Stan Hu)
+- Add comment notification events to HipChat and Slack services (Stan Hu)
+- Add issue and merge request events to HipChat and Slack services (Stan Hu)
+- Fix merge request URL passed to Webhooks. (Stan Hu)
+- Fix bug that caused a server error when editing a comment to "+1" or "-1" (Stan Hu)
+- Fix code preview theme setting for comments, issues, merge requests, and snippets (Stan Hu)
+- Move labels/milestones tabs to sidebar
+- Upgrade Rails gem to version 4.1.9.
+- Improve error messages for file edit failures
+- Improve UI for commits, issues and merge request lists
+- Fix commit comments on first line of diff not rendering in Merge Request Discussion view.
+- Allow admins to override restricted project visibility settings.
+- Move restricted visibility settings from gitlab.yml into the web UI.
+- Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev)
+- Save web edit in new branch
+- Fix ordering of imported but unchanged projects (Marco Wessel)
+- Mobile UI improvements: make aside content expandable
+- Expose avatar_url in projects API
+- Fix checkbox alignment on the application settings page.
+- Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger)
+- Fix mass-unassignment of issues (Robert Speicher)
+- Fix hidden diff comments in merge request discussion view
+- Allow user confirmation to be skipped for new users via API
+- Add a service to send updates to an Irker gateway (Romain Coltel)
+- Add brakeman (security scanner for Ruby on Rails)
+- Slack username and channel options
+- Add grouped milestones from all projects to dashboard.
+- Webhook sends pusher email as well as commiter
+- Add Bitbucket omniauth provider.
+- Add Bitbucket importer.
+- Support referencing issues to a project whose name starts with a digit
+- Condense commits already in target branch when updating merge request source branch.
+- Send notifications and leave system comments when bulk updating issues.
+- Automatically link commit ranges to compare page: sha1...sha4 or sha1..sha4 (includes sha1 in comparison)
+- Move groups page from profile to dashboard
+- Starred projects page at dashboard
+- Blocking user does not remove him/her from project/groups but show blocked label
+- Change subject of EmailsOnPush emails to include namespace, project and branch.
+- Change subject of EmailsOnPush emails to include first commit message when multiple were pushed.
+- Remove confusing footer from EmailsOnPush mail body.
+- Add list of changed files to EmailsOnPush emails.
+- Add option to send EmailsOnPush emails from committer email if domain matches.
+- Add option to disable code diffs in EmailOnPush emails.
+- Wrap commit message in EmailsOnPush email.
+- Send EmailsOnPush emails when deleting commits using force push.
+- Fix EmailsOnPush email comparison link to include first commit.
+- Fix highliht of selected lines in file
+- Reject access to group/project avatar if the user doesn't have access.
+- Add database migration to clean group duplicates with same path and name (Make sure you have a backup before update)
+- Add GitLab active users count to rake gitlab:check
+- Starred projects page at dashboard
+- Make email display name configurable
+- Improve json validation in hook data
+- Use Emoji One
+- Updated emoji help documentation to properly reference EmojiOne.
+- Fix missing GitHub organisation repositories on import page.
+- Added blue theme
+- Remove annoying notice messages when create/update merge request
+- Allow smb:// links in Markdown text.
+- Filter merge request by title or description at Merge Requests page
+- Block user if he/she was blocked in Active Directory
+- Fix import pages not working after first load.
+- Use custom LDAP label in LDAP signin form.
+- Execute hooks and services when branch or tag is created or deleted through web interface.
+- Block and unblock user if he/she was blocked/unblocked in Active Directory
+- Raise recommended number of unicorn workers from 2 to 3
+- Use same layout and interactivity for project members as group members.
+- Prevent gitlab-shell character encoding issues by receiving its changes as raw data.
+- Ability to unsubscribe/subscribe to issue or merge request
+- Delete deploy key when last connection to a project is destroyed.
+- Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther)
+- Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup)
+- Add canceled status for CI
+- Send EmailsOnPush email when branch or tag is created or deleted.
+- Faster merge request processing for large repository
+- Prevent doubling AJAX request with each commit visit via Turbolink
+- Prevent unnecessary doubling of js events on import pages and user calendar
+
+## 7.8.4
+
+- Fix issue_tracker_id substitution in custom issue trackers
+- Fix path and name duplication in namespaces
+
+## 7.8.3
+
+- Bump version of gitlab_git fixing annotated tags without message
+
+## 7.8.2
+
+- Fix service migration issue when upgrading from versions prior to 7.3
+- Fix setting of the default use project limit via admin UI
+- Fix showing of already imported projects for GitLab and Gitorious importers
+- Fix response of push to repository to return "Not found" if user doesn't have access
+- Fix check if user is allowed to view the file attachment
+- Fix import check for case sensetive namespaces
+- Increase timeout for Git-over-HTTP requests to 1 hour since large pulls/pushes can take a long time.
+- Properly handle autosave local storage exceptions.
+- Escape wildcards when searching LDAP by username.
+
+## 7.8.1
+
+- Fix run of custom post receive hooks
+- Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3
+- Fix the warning for LDAP users about need to set password
+- Fix avatars which were not shown for non logged in users
+- Fix urls for the issues when relative url was enabled
+
+## 7.8.0
+
+- Fix access control and protection against XSS for note attachments and other uploads.
+- Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
+- Make project search case insensitive (Hannes Rosenögger)
+- Include issue/mr participants in list of recipients for reassign/close/reopen emails
+- Expose description in groups API
+- Better UI for project services page
+- Cleaner UI for web editor
+- Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
+- Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen)
+- View note image attachments in new tab when clicked instead of downloading them
+- Improve sorting logic in UI and API. Explicitly define what sorting method is used by default
+- Fix overflow at sidebar when have several items
+- Add notes for label changes in issue and merge requests
+- Show tags in commit view (Hannes Rosenögger)
+- Only count a user's vote once on a merge request or issue (Michael Clarke)
+- Increase font size when browse source files and diffs
+- Service Templates now let you set default values for all services
+- Create new file in empty repository using GitLab UI
+- Ability to clone project using oauth2 token
+- Upgrade Sidekiq gem to version 3.3.0
+- Stop git zombie creation during force push check
+- Show success/error messages for test setting button in services
+- Added Rubocop for code style checks
+- Fix commits pagination
+- Async load a branch information at the commit page
+- Disable blacklist validation for project names
+- Allow configuring protection of the default branch upon first push (Marco Wessel)
+- Add gitlab.com importer
+- Add an ability to login with gitlab.com
+- Add a commit calendar to the user profile (Hannes Rosenögger)
+- Submit comment on command-enter
+- Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`.
+- Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" (Julien Bianchi and Hannes Rosenögger)
+- Fix long broadcast message cut-off on left sidebar (Visay Keo)
+- Add Project Avatars (Steven Thonus and Hannes Rosenögger)
+- Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
+- Edit group members via API
+- Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks)
+- Add action property to merge request hook (Julien Bianchi)
+- Remove duplicates from group milestone participants list.
+- Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger)
+- API: Access groups with their path (Julien Bianchi)
+- Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard)
+- Allow notification email to be set separately from primary email.
+- API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
+- Don't have Markdown preview fail for long comments/wiki pages.
+- When test webhook - show error message instead of 500 error page if connection to hook url was reset
+- Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
+- Added persistent collapse button for left side nav bar (Jason Blanchard)
+- Prevent losing unsaved comments by automatically restoring them when comment page is loaded again.
+- Don't allow page to be scaled on mobile.
+- Clean the username acquired from OAuth/LDAP so it doesn't fail username validation and block signing up.
+- Show assignees in merge request index page (Kelvin Mutuma)
+- Link head panel titles to relevant root page.
+- Allow users that signed up via OAuth to set their password in order to use Git over HTTP(S).
+- Show users button to share their newly created public or internal projects on twitter
+- Add quick help links to the GitLab pricing and feature comparison pages.
+- Fix duplicate authorized applications in user profile and incorrect application client count in admin area.
+- Make sure Markdown previews always use the same styling as the eventual destination.
+- Remove deprecated Group#owner_id from API
+- Show projects user contributed to on user page. Show stars near project on user page.
+- Improve database performance for GitLab
+- Add Asana service (Jeremy Benoist)
+- Improve project webhooks with extra data
+
+## 7.7.2
+
+- Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
+- Fix issue when LDAP user can't login with existing GitLab account
+
+## 7.7.1
+
+- Improve mention autocomplete performance
+- Show setup instructions for GitHub import if disabled
+- Allow use http for OAuth applications
+
+## 7.7.0
+
+- Import from GitHub.com feature
+- Add Jetbrains Teamcity CI service (Jason Lippert)
+- Mention notification level
+- Markdown preview in wiki (Yuriy Glukhov)
+- Raise group avatar filesize limit to 200kb
+- OAuth applications feature
+- Show user SSH keys in admin area
+- Developer can push to protected branches option
+- Set project path instead of project name in create form
+- Block Git HTTP access after 10 failed authentication attempts
+- Updates to the messages returned by API (sponsored by O'Reilly Media)
+- New UI layout with side navigation
+- Add alert message in case of outdated browser (IE < 10)
+- Added API support for sorting projects
+- Update gitlab_git to version 7.0.0.rc14
+- Add API project search filter option for authorized projects
+- Fix File blame not respecting branch selection
+- Change some of application settings on fly in admin area UI
+- Redesign signin/signup pages
+- Close standard input in Gitlab::Popen.popen
+- Trigger GitLab CI when push tags
+- When accept merge request - do merge using sidaekiq job
+- Enable web signups by default
+- Fixes for diff comments: drag-n-drop images, selecting images
+- Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update
+- Remove password strength indicator
+
+## 7.6.0
+
+- Fork repository to groups
+- New rugged version
+- Add CRON=1 backup setting for quiet backups
+- Fix failing wiki restore
+- Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
+- Monokai highlighting style now more faithful to original design (Mark Riedesel)
+- Create project with repository in synchrony
+- Added ability to create empty repo or import existing one if project does not have repository
+- Reactivate highlight.js language autodetection
+- Mobile UI improvements
+- Change maximum avatar file size from 100KB to 200KB
+- Strict validation for snippet file names
+- Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada)
+- In the docker directory is a container template based on the Omnibus packages.
+- Update Sidekiq to version 2.17.8
+- Add author filter to project issues and merge requests pages
+- Atom feed for user activity
+- Support multiple omniauth providers for the same user
+- Rendering cross reference in issue title and tooltip for merge request
+- Show username in comments
+- Possibility to create Milestones or Labels when Issues are disabled
+- Fix bug with showing gpg signature in tag
+
+## 7.5.3
+
+- Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
+
+## 7.5.2
+
+- Don't log Sidekiq arguments by default
+- Fix restore of wiki repositories from backups
+
+## 7.5.1
+
+- Add missing timestamps to 'members' table
+
+## 7.5.0
+
+- API: Add support for Hipchat (Kevin Houdebert)
+- Add time zone configuration in gitlab.yml (Sullivan Senechal)
+- Fix LDAP authentication for Git HTTP access
+- Run 'GC.start' after every EmailsOnPushWorker job
+- Fix LDAP config lookup for provider 'ldap'
+- Drop all sequences during Postgres database restore
+- 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
+- Performance improvements
+- Fix post-receive issue for projects with deleted forks
+- New gitlab-shell version with custom hooks support
+- Improve code
+- GitLab CI 5.2+ support (does not support older versions)
+- Fixed bug when you can not push commits starting with 000000 to protected branches
+- Added a password strength indicator
+- Change project name and path in one form
+- Display renamed files in diff views (Vinnie Okada)
+- Fix raw view for public snippets
+- Use secret token with GitLab internal API.
+- Add missing timestamps to 'members' table
+
+## 7.4.5
+
+- Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
+
+## 7.4.4
+
+- No changes
+
+## 7.4.3
+
+- Fix raw snippets view
+- Fix security issue for member api
+- Fix buildbox integration
+
+## 7.4.2
+
+- Fix internal snippet exposing for unauthenticated users
+
+## 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
+
+## 7.4.0
+
+- Refactored membership logic
+- 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
+- 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
+- Internal ForkService: Prepare support for fork to a given namespace
+- 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
+
+## 7.3.2
+
+- Fix creating new file via web editor
+- Use gitlab-shell v2.0.1
+
+## 7.3.1
+
+- Fix ref parsing in Gitlab::GitAccess
+- Fix error 500 when viewing diff on a file with changed permissions
+- Fix adding comments to MR when source branch is master
+- Fix error 500 when searching description contains relative link
+
+## 7.3.0
+
+- Always set the 'origin' remote in satellite actions
+- Write authorized_keys in tmp/ during tests
+- Use sockets to connect to Redis
+- Add dormant New Relic gem (can be enabled via environment variables)
+- Expire Rack sessions after 1 week
+- Cleaner signin/signup pages
+- Improved comments UI
+- Better search with filtering, pagination etc
+- Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov)
+- Prevent project stars duplication when fork project
+- Use the default Unicorn socket backlog value of 1024
+- Support Unix domain sockets for Redis
+- Store session Redis keys in 'session:gitlab:' namespace
+- Deprecate LDAP account takeover based on partial LDAP email / GitLab username match
+- Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy)
+- Keyboard shortcuts for productivity (Robert Schilling)
+- API: filter issues by state (Julien Bianchi)
+- API: filter issues by labels (Julien Bianchi)
+- Add system hook for ssh key changes
+- Add blob permalink link (Ciro Santilli)
+- Create annotated tags through UI and API (Sean Edge)
+- Snippets search (Charles Bushong)
+- Comment new push to existing MR
+- Add 'ci' to the blacklist of forbidden names
+- Improve text filtering on issues page
+- Comment & Close button
+- Process git push --all much faster
+- Don't allow edit of system notes
+- Project wiki search (Ralf Seidler)
+- Enabled Shibboleth authentication support (Matus Banas)
+- Zen mode (fullscreen) for issues/MR/notes (Robert Schilling)
+- Add ability to configure webhook timeout via gitlab.yml (Wes Gurney)
+- Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media)
+- Add Redis socket support to 'rake gitlab:shell:install'
+
+## 7.2.1
+
+- Delete orphaned labels during label migration (James Brooks)
+- Security: prevent XSS with stricter MIME types for raw repo files
+
+## 7.2.0
+
+- Explore page
+- Add project stars (Ciro Santilli)
+- Log Sidekiq arguments
+- Better labels: colors, ability to rename and remove
+- Improve the way merge request collects diffs
+- Improve compare page for large diffs
+- Expose the full commit message via API
+- Fix 500 error on repository rename
+- Fix bug when MR download patch return invalid diff
+- Test gitlab-shell integration
+- Repository import timeout increased from 2 to 4 minutes allowing larger repos to be imported
+- API for labels (Robert Schilling)
+- API: ability to set an import url when creating project for specific user
+
+## 7.1.1
+
+- Fix cpu usage issue in Firefox
+- Fix redirect loop when changing password by new user
+- Fix 500 error on new merge request page
+
+## 7.1.0
+
+- Remove observers
+- Improve MR discussions
+- Filter by description on Issues#index page
+- Fix bug with namespace select when create new project page
+- Show README link after description for non-master members
+- Add @all mention for comments
+- Dont show reply button if user is not signed in
+- Expose more information for issues with webhook
+- Add a mention of the merge request into the default merge request commit message
+- Improve code highlight, introduce support for more languages like Go, Clojure, Erlang etc
+- Fix concurrency issue in repository download
+- Dont allow repository name start with ?
+- Improve email threading (Pierre de La Morinerie)
+- Cleaner help page
+- Group milestones
+- Improved email notifications
+- Contributors API (sponsored by Mobbr)
+- Fix LDAP TLS authentication (Boris HUISGEN)
+- Show VERSION information on project sidebar
+- Improve branch removal logic when accept MR
+- Fix bug where comment form is spawned inside the Reply button
+- Remove Dir.chdir from Satellite#lock for thread-safety
+- Increased default git max_size value from 5MB to 20MB in gitlab.yml. Please update your configs!
+- Show error message in case of timeout in satellite when create MR
+- Show first 100 files for huge diff instead of hiding all
+- Change default admin email from admin at local.host to admin at example.com
+
+## 7.0.0
+
+- The CPU no longer overheats when you hold down the spacebar
+- Improve edit file UI
+- Add ability to upload group avatar when create
+- Protected branch cannot be removed
+- Developers can remove normal branches with UI
+- Remove branch via API (sponsored by O'Reilly Media)
+- Move protected branches page to Project settings area
+- Redirect to Files view when create new branch via UI
+- Drag and drop upload of image in every markdown-area (Earle Randolph Bunao and Neil Francis Calabroso)
+- Refactor the markdown relative links processing
+- Make it easier to implement other CI services for GitLab
+- Group masters can create projects in group
+- Deprecate ruby 1.9.3 support
+- Only masters can rewrite/remove git tags
+- Add X-Frame-Options SAMEORIGIN to Nginx config so Sidekiq admin is visible
+- UI improvements
+- Case-insensetive search for issues
+- Update to rails 4.1
+- Improve performance of application for projects and groups with a lot of members
+- Formally support Ruby 2.1
+- Include Nginx gitlab-ssl config
+- Add manual language detection for highlight.js
+- Added example.com/:username routing
+- Show notice if your profile is public
+- UI improvements for mobile devices
+- Improve diff rendering performance
+- Drag-n-drop for issues and merge requests between states at milestone page
+- Fix '0 commits' message for huge repositories on project home page
+- Prevent 500 error page when visit commit page from large repo
+- Add notice about huge push over http to unicorn config
+- File action in satellites uses default 30 seconds timeout instead of old 10 seconds one
+- Overall performance improvements
+- Skip init script check on omnibus-gitlab
+- Be more selective when killing stray Sidekiqs
+- Check LDAP user filter during sign-in
+- Remove wall feature (no data loss - you can take it from database)
+- Dont expose user emails via API unless you are admin
+- Detect issues closed by Merge Request description
+- Better email subject lines from email on push service (Alex Elman)
+- Enable identicon for gravatar be default
+
+## 6.9.2
+
+- Revert the commit that broke the LDAP user filter
+
+## 6.9.1
+
+- Fix scroll to highlighted line
+- Fix the pagination on load for commits page
+
+## 6.9.0
+
+- Store Rails cache data in the Redis `cache:gitlab` namespace
+- Adjust MySQL limits for existing installations
+- Add db index on project_id+iid column. This prevents duplicate on iid (During migration duplicates will be removed)
+- Markdown preview or diff during editing via web editor (Evgeniy Sokovikov)
+- Give the Rails cache its own Redis namespace
+- Add ability to set different ssh host, if different from http/https
+- Fix syntax highlighting for code comments blocks
+- Improve comments loading logic
+- Stop refreshing comments when the tab is hidden
+- Improve issue and merge request mobile UI (Drew Blessing)
+- Document how to convert a backup to PostgreSQL
+- Fix locale bug in backup manager
+- Fix can not automerge when MR description is too long
+- Fix wiki backup skip bug
+- Two Step MR creation process
+- Remove unwanted files from satellite working directory with git clean -fdx
+- Accept merge request via API (sponsored by O'Reilly Media)
+- Add more access checks during API calls
+- Block SSH access for 'disabled' Active Directory users
+- Labels for merge requests (Drew Blessing)
+- Threaded emails by setting a Message-ID (Philip Blatter)
+
+## 6.8.0
+
+- Ability to at mention users that are participating in issue and merge req. discussion
+- Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu)
+- Make user search case-insensitive (Christopher Arnold)
+- Remove omniauth-ldap nickname bug workaround
+- Drop all tables before restoring a Postgres backup
+- Make the repository downloads path configurable
+- Create branches via API (sponsored by O'Reilly Media)
+- Changed permission of gitlab-satellites directory not to be world accessible
+- Protected branch does not allow force push
+- Fix popen bug in `rake gitlab:satellites:create`
+- Disable connection reaping for MySQL
+- Allow oauth signup without email for twitter and github
+- Fix faulty namespace names that caused 500 on user creation
+- Option to disable standard login
+- Clean old created archives from repository downloads directory
+- Fix download link for huge MR diffs
+- Expose event and mergerequest timestamps in API
+- Fix emails on push service when only one commit is pushed
+
+## 6.7.3
+
+- Fix the merge notification email not being sent (Pierre de La Morinerie)
+- Drop all tables before restoring a Postgres backup
+- Remove yanked modernizr gem
+
+## 6.7.2
+
+- Fix upgrader script
+
+## 6.7.1
+
+- Fix GitLab CI integration
+
+## 6.7.0
+
+- Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations
+- Add support for Gemnasium as a Project Service (Olivier Gonzalez)
+- Add edit file button to MergeRequest diff
+- Public groups (Jason Hollingsworth)
+- Cleaner headers in Notification Emails (Pierre de La Morinerie)
+- Blob and tree gfm links to anchors work
+- Piwik Integration (Sebastian Winkler)
+- Show contribution guide link for new issue form (Jeroen van Baarsen)
+- Fix CI status for merge requests from fork
+- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
+- New page load indicator that includes a spinner that scrolls with the page
+- Converted all the help sections into markdown
+- LDAP user filters
+- Streamline the content of notification emails (Pierre de La Morinerie)
+- Fixes a bug with group member administration (Matt DeTullio)
+- Sort tag names using VersionSorter (Robert Speicher)
+- Add GFM autocompletion for MergeRequests (Robert Speicher)
+- Add webhook when a new tag is pushed (Jeroen van Baarsen)
+- Add button for toggling inline comments in diff view
+- Add retry feature for repository import
+- Reuse the GitLab LDAP connection within each request
+- Changed markdown new line behaviour to conform to markdown standards
+- Fix global search
+- Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5)
+- Create and Update MR calls now support the description parameter (Greg Messner)
+- Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository
+- Added Slack service integration (Federico Ravasio)
+- Better API responses for access_levels (sponsored by O'Reilly Media)
+- Requires at least 2 unicorn workers
+- Requires gitlab-shell v1.9+
+- Replaced gemoji(due to closed licencing problem) with Phantom Open Emoji library(combined SIL Open Font License, MIT License and the CC 3.0 License)
+- Fix `/:username.keys` response content type (Dmitry Medvinsky)
+
+## 6.6.5
+
+- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
+- Hide mr close button for comment form if merge request was closed or inline comment
+- Adds ability to reopen closed merge request
+
+## 6.6.4
+
+- Add missing html escape for highlighted code blocks in comments, issues
+
+## 6.6.3
+
+- Fix 500 error when edit yourself from admin area
+- Hide private groups for public profiles
+
+## 6.6.2
+
+- Fix 500 error on branch/tag create or remove via UI
+
+## 6.6.1
+
+- Fix 500 error on files tab if submodules presents
+
+## 6.6.0
+
+- Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys
+- Permissions: Developer now can manage issue tracker (modify any issue)
+- Improve Code Compare page performance
+- Group avatar
+- Pygments.rb replaced with highlight.js
+- Improve Merge request diff store logic
+- Improve render performnace for MR show page
+- Fixed Assembla hardcoded project name
+- Jira integration documentation
+- Refactored app/services
+- Remove snippet expiration
+- Mobile UI improvements (Drew Blessing)
+- Fix block/remove UI for admin::users#show page
+- Show users' group membership on users' activity page (Robert Djurasaj)
+- User pages are visible without login if user is authorized to a public project
+- Markdown rendered headers have id derived from their name and link to their id
+- Improve application to work faster with large groups (100+ members)
+- Multiple emails per user
+- Show last commit for file when view file source
+- Restyle Issue#show page and MR#show page
+- Ability to filter by multiple labels for Issues page
+- Rails version to 4.0.3
+- Fixed attachment identifier displaying underneath note text (Jason Blanchard)
+
+## 6.5.1
+
+- Fix branch selectbox when create merge request from fork
+
+## 6.5.0
+
+- Dropdown menus on issue#show page for assignee and milestone (Jason Blanchard)
+- Add color custimization and previewing to broadcast messages
+- Fixed notes anchors
+- Load new comments in issues dynamically
+- Added sort options to Public page
+- New filters (assigned/authored/all) for Dashboard#issues/merge_requests (sponsored by Say Media)
+- Add project visibility icons to dashboard
+- Enable secure cookies if https used
+- Protect users/confirmation with rack_attack
+- Default HTTP headers to protect against MIME-sniffing, force https if enabled
+- Bootstrap 3 with responsive UI
+- New repository download formats: tar.bz2, zip, tar (Jason Hollingsworth)
+- Restyled accept widgets for MR
+- SCSS refactored
+- Use jquery timeago plugin
+- Fix 500 error for rdoc files
+- Ability to customize merge commit message (sponsored by Say Media)
+- Search autocomplete via ajax
+- Add website url to user profile
+- Files API supports base64 encoded content (sponsored by O'Reilly Media)
+- Added support for Go's repository retrieval (Bruno Albuquerque)
+
+## 6.4.3
+
+- Don't use unicorn worker killer if PhusionPassenger is defined
+
+## 6.4.2
+
+- Fixed wrong behaviour of script/upgrade.rb
+
+## 6.4.1
+
+- Fixed bug with repository rename
+- Fixed bug with project transfer
+
+## 6.4.0
+
+- Added sorting to project issues page (Jason Blanchard)
+- Assembla integration (Carlos Paramio)
+- Fixed another 500 error with submodules
+- UI: More compact issues page
+- Minimal password length increased to 8 symbols
+- Side-by-side diff view (Steven Thonus)
+- Internal projects (Jason Hollingsworth)
+- Allow removal of avatar (Drew Blessing)
+- Project webhooks now support issues and merge request events
+- Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth)
+- Expire event cache on avatar creation/removal (Drew Blessing)
+- Archiving old projects (Steven Thonus)
+- Rails 4
+- Add time ago tooltips to show actual date/time
+- UI: Fixed UI for admin system hooks
+- Ruby script for easier GitLab upgrade
+- Do not remove Merge requests if fork project was removed
+- Improve sign-in/signup UX
+- Add resend confirmation link to sign-in page
+- Set noreply at HOSTNAME for reply_to field in all emails
+- Show GitLab API version on Admin#dashboard
+- API Cross-origin resource sharing
+- Show READMe link at project home page
+- Show repo size for projects in Admin area
+
+## 6.3.0
+
+- API for adding gitlab-ci service
+- Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey)
+- Restyle project home page
+- Grammar fixes
+- Show branches list (which branches contains commit) on commit page (Andrew Kumanyaev)
+- Security improvements
+- Added support for GitLab CI 4.0
+- Fixed issue with 500 error when group did not exist
+- Ability to leave project
+- You can create file in repo using UI
+- You can remove file from repo using UI
+- API: dropped default_branch attribute from project during creation
+- Project default_branch is not stored in db any more. It takes from repo now.
+- Admin broadcast messages
+- UI improvements
+- Dont show last push widget if user removed this branch
+- Fix 500 error for repos with newline in file name
+- Extended html titles
+- API: create/update/delete repo files
+- Admin can transfer project to any namespace
+- API: projects/all for admin users
+- Fix recent branches order
+
+## 6.2.4
+
+- Security: Cast API private_token to string (CVE-2013-4580)
+- Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
+- Fix for Git SSH access for LDAP users
+
+## 6.2.3
+
+- Security: More protection against CVE-2013-4489
+- Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
+- Fix sidekiq rake tasks
+
+## 6.2.2
+
+- Security: Update gitlab_git (CVE-2013-4489)
+
+## 6.2.1
+
+- Security: Fix issue with generated passwords for new users
+
+## 6.2.0
+
+- Public project pages are now visible to everyone (files, issues, wik, etc.)
+  THIS MEANS YOUR ISSUES AND WIKI FOR PUBLIC PROJECTS ARE PUBLICLY VISIBLE AFTER THE UPGRADE
+- Add group access to permissions page
+- Require current password to change one
+- Group owner or admin can remove other group owners
+- Remove group transfer since we have multiple owners
+- Respect authorization in Repository API
+- Improve UI for Project#files page
+- Add more security specs
+- Added search for projects by name to api (Izaak Alpert)
+- Make default user theme configurable (Izaak Alpert)
+- Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev)
+- Rake tasks for webhooks management (Jonhnny Weslley)
+- Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov)
+- API: Remove group
+- API: Remove project
+- Avatar upload on profile page with a maximum of 100KB (Steven Thonus)
+- Store the sessions in Redis instead of the cookie store
+- Fixed relative links in markdown
+- User must confirm their email if signup enabled
+- User must confirm changed email
+
+## 6.1.0
+
+- Project specific IDs for issues, mr, milestones
+  Above items will get a new id and for example all bookmarked issue urls will change.
+  Old issue urls are redirected to the new one if the issue id is too high for an internal id.
+- Description field added to Merge Request
+- API: Sudo api calls (Izaak Alpert)
+- API: Group membership api (Izaak Alpert)
+- Improved commit diff
+- Improved large commit handling (Boyan Tabakov)
+- Rewrite: Init script now less prone to errors and keeps better track of the service (Rovanion Luckey)
+- Link issues, merge requests, and commits when they reference each other with GFM (Ash Wilson)
+- Close issues automatically when pushing commits with a special message
+- Improve user removal from admin area
+- Invalidate events cache when project was moved
+- Remove deprecated classes and rake tasks
+- Add event filter for group and project show pages
+- Add links to create branch/tag from project home page
+- Add public-project? checkbox to new-project view
+- Improved compare page. Added link to proceed into Merge Request
+- Send an email to a user when they are added to group
+- New landing page when you have 0 projects
+
+## 6.0.0
+
+- Feature: Replace teams with group membership
+  We introduce group membership in 6.0 as a replacement for teams.
+  The old combination of groups and teams was confusing for a lot of people.
+  And when the members of a team where changed this wasn't reflected in the project permissions.
+  In GitLab 6.0 you will be able to add members to a group with a permission level for each member.
+  These group members will have access to the projects in that group.
+  Any changes to group members will immediately be reflected in the project permissions.
+  You can even have multiple owners for a group, greatly simplifying administration.
+- Feature: Ability to have multiple owners for group
+- Feature: Merge Requests between fork and project (Izaak Alpert)
+- Feature: Generate fingerprint for ssh keys
+- Feature: Ability to create and remove branches with UI
+- Feature: Ability to create and remove git tags with UI
+- Feature: Groups page in profile. You can leave group there
+- API: Allow login with LDAP credentials
+- Redesign: project settings navigation
+- Redesign: snippets area
+- Redesign: ssh keys page
+- Redesign: buttons, blocks and other ui elements
+- Add comment title to rss feed
+- You can use arrows to navigate at tree view
+- Add project filter on dashboard
+- Cache project graph
+- Drop support of root namespaces
+- Default theme is classic now
+- Cache result of methods like authorize_projects, project.team.members etc
+- Remove $.ready events
+- Fix onclick events being double binded
+- Add notification level to group membership
+- Move all project controllers/views under Projects:: module
+- Move all profile controllers/views under Profiles:: module
+- Apply user project limit only for personal projects
+- Unicorn is default web server again
+- Store satellites lock files inside satellites dir
+- Disabled threadsafety mode in rails
+- Fixed bug with loosing MR comments
+- Improved MR comments logic
+- Render readme file for projects in public area
+
+## 5.4.2
+
+- Security: Cast API private_token to string (CVE-2013-4580)
+- Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
+
+## 5.4.1
+
+- Security: Fixes for CVE-2013-4489
+- Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
+
+## 5.4.0
+
+- Ability to edit own comments
+- Documentation improvements
+- Improve dashboard projects page
+- Fixed nav for empty repos
+- GitLab Markdown help page
+- Misspelling fixes
+- Added support of unicorn and fog gems
+- Added client list to API doc
+- Fix PostgreSQL database restoration problem
+- Increase snippet content column size
+- allow project import via git:// url
+- Show participants on issues, including mentions
+- Notify mentioned users with email
+
+## 5.3.0
+
+- Refactored services
+- Campfire service added
+- HipChat service added
+- Fixed bug with LDAP + git over http
+- Fixed bug with google analytics code being ignored
+- Improve sign-in page if ldap enabled
+- Respect newlines in wall messages
+- Generate the Rails secret token on first run
+- Rename repo feature
+- Init.d: remove gitlab.socket on service start
+- Api: added teams api
+- Api: Prevent blob content being escaped
+- Api: Smart deploy key add behaviour
+- Api: projects/owned.json return user owned project
+- Fix bug with team assignation on project from #4109
+- Advanced snippets: public/private, project/personal (Andrew Kulakov)
+- Repository Graphs (Karlo Nicholas T. Soriano)
+- Fix dashboard lost if comment on commit
+- Update gitlab-grack. Fixes issue with --depth option
+- Fix project events duplicate on project page
+- Fix postgres error when displaying network graph.
+- Fix dashboard event filter when navigate via turbolinks
+- init.d: Ensure socket is removed before starting service
+- Admin area: Style teams:index, group:show pages
+- Own page for failed forking
+- Scrum view for milestone
+
+## 5.2.0
+
+- Turbolinks
+- Git over http with ldap credentials
+- Diff with better colors and some spacing on the corners
+- Default values for project features
+- Fixed huge_commit view
+- Restyle project clone panel
+- Move Gitlab::Git code to gitlab_git gem
+- Move update docs in repo
+- Requires gitlab-shell v1.4.0
+- Fixed submodules listing under file tab
+- Fork feature (Angus MacArthur)
+- git version check in gitlab:check
+- Shared deploy keys feature
+- Ability to generate default labels set for issues
+- Improve gfm autocomplete (Harold Luo)
+- Added support for Google Analytics
+- Code search feature (Javier Castro)
+
+## 5.1.0
+
+- You can login with email or username now
+- Corrected project transfer rollback when repository cannot be moved
+- Move both repo and wiki when project transfer requested
+- Admin area: project editing was removed from admin namespace
+- Access: admin user has now access to any project.
+- Notification settings
+- Gitlab::Git set of objects to abstract from grit library
+- Replace Unicorn web server with Puma
+- Backup/Restore refactored. Backup dump project wiki too now
+- Restyled Issues list. Show milestone version in issue row
+- Restyled Merge Request list
+- Backup now dump/restore uploads
+- Improved performance of dashboard (Andrew Kumanyaev)
+- File history now tracks renames (Akzhan Abdulin)
+- Drop wiki migration tools
+- Drop sqlite migration tools
+- project tagging
+- Paginate users in API
+- Restyled network graph (Hiroyuki Sato)
+
+## 5.0.1
+
+- Fixed issue with gitlab-grit being overridden by grit
+
+## 5.0.0
+
+- Replaced gitolite with gitlab-shell
+- Removed gitolite-related libraries
+- State machine added
+- Setup gitlab as git user
+- Internal API
+- Show team tab for empty projects
+- Import repository feature
+- Updated rails
+- Use lambda for scopes
+- Redesign admin area -> users
+- Redesign admin area -> user
+- Secure link to file attachments
+- Add validations for Group and Team names
+- Restyle team page for project
+- Update capybara, rspec-rails, poltergeist to recent versions
+- Wiki on git using Gollum
+- Added Solarized Dark theme for code review
+- Don't show user emails in autocomplete lists, profile pages
+- Added settings tab for group, team, project
+- Replace user popup with icons in header
+- Handle project moving with gitlab-shell
+- Added select2-rails for selectboxes with ajax data load
+- Fixed search field on projects page
+- Added teams to search autocomplete
+- Move groups and teams on dashboard sidebar to sub-tabs
+- API: improved return codes and docs. (Felix Gilcher, Sebastian Ziebell)
+- Redesign wall to be more like chat
+- Snippets, Wall features are disabled by default for new projects
+
+## 4.2.0
+
+- Teams
+- User show page. Via /u/username
+- Show help contents on pages for better navigation
+- Async gitolite calls
+- added satellites logs
+- can_create_group, can_create_team booleans for User
+- Process webhooks async
+- GFM: Fix images escaped inside links
+- Network graph improved
+- Switchable branches for network graph
+- API: Groups
+- Fixed project download
+
+## 4.1.0
+
+- Optional Sign-Up
+- Discussions
+- Satellites outside of tmp
+- Line numbers for blame
+- Project public mode
+- Public area with unauthorized access
+- Load dashboard events with ajax
+- remember dashboard filter in cookies
+- replace resque with sidekiq
+- fix routing issues
+- cleanup rake tasks
+- fix backup/restore
+- scss cleanup
+- show preview for note images
+- improved network-graph
+- get rid of app/roles/
+- added new classes Team, Repository
+- Reduce amount of gitolite calls
+- Ability to add user in all group projects
+- remove deprecated configs
+- replaced Korolev font with open font
+- restyled admin/dashboard page
+- restyled admin/projects page
+
+## 4.0.0
+
+- Remove project code and path from API. Use id instead
+- Return valid cloneable url to repo for webhook
+- Fixed backup issue
+- Reorganized settings
+- Fixed commits compare
+- Refactored scss
+- Improve status checks
+- Validates presence of User#name
+- Fixed postgres support
+- Removed sqlite support
+- Modified post-receive hook
+- Milestones can be closed now
+- Show comment events on dashboard
+- Quick add team members via group#people page
+- [API] expose created date for hooks and SSH keys
+- [API] list, create issue notes
+- [API] list, create snippet notes
+- [API] list, create wall notes
+- Remove project code - use path instead
+- added username field to user
+- rake task to fill usernames based on emails create namespaces for users
+- STI Group < Namespace
+- Project has namespace_id
+- Projects with namespaces also namespaced in gitolite and stored in subdir
+- Moving project to group will move it under group namespace
+- Ability to move project from namespaces to another
+- Fixes commit patches getting escaped (see #2036)
+- Support diff and patch generation for commits and merge request
+- MergeReqest doesn't generate a temporary file for the patch any more
+- Update the UI to allow downloading Patch or Diff
+
+## 3.1.0
+
+- Updated gems
+- Services: Gitlab CI integration
+- Events filter on dashboard
+- Own namespace for redis/resque
+- Optimized commit diff views
+- add alphabetical order for projects admin page
+- Improved web editor
+- Commit stats page
+- Documentation split and cleanup
+- Link to commit authors everywhere
+- Restyled milestones list
+- added Milestone to Merge Request
+- Restyled Top panel
+- Refactored Satellite Code
+- Added file line links
+- moved from capybara-webkit to poltergeist + phantomjs
+
+## 3.0.3
+
+- Fixed bug with issues list in Chrome
+- New Feature: Import team from another project
+
+## 3.0.2
+
+- Fixed gitlab:app:setup
+- Fixed application error on empty project in admin area
+- Restyled last push widget
+
+## 3.0.1
+
+- Fixed git over http
+
+## 3.0.0
+
+- Projects groups
+- Web Editor
+- Fixed bug with gitolite keys
+- UI improved
+- Increased performance of application
+- Show user avatar in last commit when browsing Files
+- Refactored Gitlab::Merge
+- Use Font Awesome for icons
+- Separate observing of Note and MergeRequests
+- Milestone "All Issues" filter
+- Fix issue close and reopen button text and styles
+- Fix forward/back while browsing Tree hierarchy
+- Show number of notes for commits and merge requests
+- Added support pg from box and update installation doc
+- Reject ssh keys that break gitolite
+- [API] list one project hook
+- [API] edit project hook
+- [API] list project snippets
+- [API] allow to authorize using private token in HTTP header
+- [API] add user creation
+
+## 2.9.1
+
+- Fixed resque custom config init
+
+## 2.9.0
+
+- fixed inline notes bugs
+- refactored rspecs
+- refactored gitolite backend
+- added factory_girl
+- restyled projects list on dashboard
+- ssh keys validation to prevent gitolite crash
+- send notifications if changed permission in project
+- scss refactoring. gitlab_bootstrap/ dir
+- fix git push http body bigger than 112k problem
+- list of labels  page under issues tab
+- API for milestones, keys
+- restyled buttons
+- OAuth
+- Comment order changed
+
+## 2.8.1
+
+- ability to disable gravatars
+- improved MR diff logic
+- ssh key help page
+
+## 2.8.0
+
+- Gitlab Flavored Markdown
+- Bulk issues update
+- Issues API
+- Cucumber coverage increased
+- Post-receive files fixed
+- UI improved
+- Application cleanup
+- more cucumber
+- capybara-webkit + headless
+
+## 2.7.0
+
+- Issue Labels
+- Inline diff
+- Git HTTP
+- API
+- UI improved
+- System hooks
+- UI improved
+- Dashboard events endless scroll
+- Source performance increased
+
+## 2.6.0
+
+- UI polished
+- Improved network graph + keyboard nav
+- Handle huge commits
+- Last Push widget
+- Bugfix
+- Better performance
+- Email in resque
+- Increased test coverage
+- Ability to remove branch with MR accept
+- a lot of code refactored
+
+## 2.5.0
+
+- UI polished
+- Git blame for file
+- Bugfix
+- Email in resque
+- Better test coverage
+
+## 2.4.0
+
+- Admin area stats page
+- Ability to block user
+- Simplified dashboard area
+- Improved admin area
+- Bootstrap 2.0
+- Responsive layout
+- Big commits handling
+- Performance improved
+- Milestones
+
+## 2.3.1
+
+- Issues pagination
+- ssl fixes
+- Merge Request pagination
+
+## 2.3.0
+
+- Dashboard r1
+- Search r1
+- Project page
+- Close merge request on push
+- Persist MR diff after merge
+- mysql support
+- Documentation
+
+## 2.2.0
+
+- We’ve added support of LDAP auth
+- Improved permission logic (4 roles system)
+- Protected branches (now only masters can push to protected branches)
+- Usability improved
+- twitter bootstrap integrated
+- compare view between commits
+- wiki feature
+- now you can enable/disable issues, wiki, wall features per project
+- security fixes
+- improved code browsing (ajax branch switch etc)
+- improved per-line commenting
+- git submodules displayed
+- moved to rails 3.2
+- help section improved
+
+## 2.1.0
+
+- Project tab r1
+- List branches/tags
+- per line comments
+- mass user import
+
+## 2.0.0
+
+- gitolite as main git host system
+- merge requests
+- project/repo access
+- link to commit/issue feed
+- design tab
+- improved email notifications
+- restyled dashboard
+- bugfix
+
+## 1.2.2
+
+- common config file gitlab.yml
+- issues restyle
+- snippets restyle
+- clickable news feed header on dashboard
+- bugfix
+
+## 1.2.1
+
+- bugfix
+
+## 1.2.0
+
+- new design
+- user dashboard
+- network graph
+- markdown support for comments
+- encoding issues
+- wall like twitter timeline
+
+## 1.1.0
+
+- project dashboard
+- wall redesigned
+- feature: code snippets
+- fixed horizontal scroll on file preview
+- fixed app crash if commit message has invalid chars
+- bugfix & code cleaning
+
+## 1.0.2
+
+- fixed bug with empty project
+- added adv validation for project path & code
+- feature: issues can be sortable
+- bugfix
+- username displayed on top panel
+
+## 1.0.1
+
+- fixed: with invalid source code for commit
+- fixed: lose branch/tag selection when use tree navigation
+- when history clicked - display path
+- bug fix & code cleaning
+
+## 1.0.0
+
+- bug fix
+- projects preview mode
+
+## 0.9.6
+
+- css fix
+- new repo empty tree until restart server - fixed
+
+## 0.9.4
+
+- security improved
+- authorization improved
+- html escaping
+- bug fix
+- increased test coverage
+- design improvements
+
+## 0.9.1
+
+- increased test coverage
+- design improvements
+- new issue email notification
+- updated app name
+- issue redesigned
+- issue can be edit
+
+## 0.8.0
+
+- syntax highlight for main file types
+- redesign
+- stability
+- security fixes
+- increased test coverage
+- email notification
diff --git a/changelogs/unreleased/.gitkeep b/changelogs/unreleased/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 7a9376d..195108b 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -212,7 +212,7 @@ Settings.gitlab.default_projects_features['builds']             = true if Settin
 Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
 Settings.gitlab.default_projects_features['visibility_level']   = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
 Settings.gitlab['domain_whitelist'] ||= []
-Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project]
+Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project]
 Settings.gitlab['trusted_proxies'] ||= []
 
 #
@@ -299,6 +299,9 @@ Settings.cron_jobs['remove_expired_members_worker']['job_class'] = 'RemoveExpire
 Settings.cron_jobs['remove_expired_group_links_worker'] ||= Settingslogic.new({})
 Settings.cron_jobs['remove_expired_group_links_worker']['cron'] ||= '10 0 * * *'
 Settings.cron_jobs['remove_expired_group_links_worker']['job_class'] = 'RemoveExpiredGroupLinksWorker'
+Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '* */6 * * *'
+Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker'
 
 #
 # GitLab Shell
diff --git a/config/initializers/ar_monkey_patch.rb b/config/initializers/ar_monkey_patch.rb
new file mode 100644
index 0000000..0da5846
--- /dev/null
+++ b/config/initializers/ar_monkey_patch.rb
@@ -0,0 +1,57 @@
+# rubocop:disable Lint/RescueException
+
+# This patch fixes https://github.com/rails/rails/issues/26024
+# TODO: Remove it when it's no longer necessary
+
+module ActiveRecord
+  module Locking
+    module Optimistic
+      # We overwrite this method because we don't want to have default value
+      # for newly created records
+      def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
+        super
+      end
+
+      def _update_record(attribute_names = self.attribute_names) #:nodoc:
+        return super unless locking_enabled?
+        return 0 if attribute_names.empty?
+
+        lock_col = self.class.locking_column
+
+        previous_lock_value = send(lock_col).to_i
+
+        # This line is added as a patch
+        previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0
+
+        increment_lock
+
+        attribute_names += [lock_col]
+        attribute_names.uniq!
+
+        begin
+          relation = self.class.unscoped
+
+          affected_rows = relation.where(
+            self.class.primary_key => id,
+            lock_col => previous_lock_value,
+          ).update_all(
+            attributes_for_update(attribute_names).map do |name|
+              [name, _read_attribute(name)]
+            end.to_h
+          )
+
+          unless affected_rows == 1
+            raise ActiveRecord::StaleObjectError.new(self, "update")
+          end
+
+          affected_rows
+
+        # If something went wrong, revert the version.
+        rescue Exception
+          send(lock_col + '=', previous_lock_value)
+          raise
+        end
+      end
+    end
+  end
+end
diff --git a/config/initializers/gitlab_workhorse_secret.rb b/config/initializers/gitlab_workhorse_secret.rb
new file mode 100644
index 0000000..ed54dc1
--- /dev/null
+++ b/config/initializers/gitlab_workhorse_secret.rb
@@ -0,0 +1,8 @@
+begin
+  Gitlab::Workhorse.secret
+rescue
+  Gitlab::Workhorse.write_secret
+end
+
+# Try a second time. If it does not work this will raise.
+Gitlab::Workhorse.secret
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 52522e0..be22085 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -68,7 +68,8 @@ if Gitlab::Metrics.enabled?
       ['app', 'mailers', 'emails']          => ['app', 'mailers'],
       ['app', 'services', '**']             => ['app', 'services'],
       ['lib', 'gitlab', 'diff']             => ['lib'],
-      ['lib', 'gitlab', 'email', 'message'] => ['lib']
+      ['lib', 'gitlab', 'email', 'message'] => ['lib'],
+      ['lib', 'gitlab', 'checks']           => ['lib']
     }
 
     paths_to_instrument.each do |(path, prefix)|
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index f498732..5e3e4c9 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -13,9 +13,5 @@ Mime::Type.register "video/mp4",  :mp4, [], [:m4v, :mov]
 Mime::Type.register "video/webm", :webm
 Mime::Type.register "video/ogg",  :ogv
 
-middlewares = Gitlab::Application.config.middleware
-middlewares.swap(ActionDispatch::ParamsParser, ActionDispatch::ParamsParser, {
-  Mime::Type.lookup('application/vnd.git-lfs+json') => lambda do |body|
-    ActiveSupport::JSON.decode(body)
-  end
-})
+Mime::Type.unregister :json
+Mime::Type.register 'application/json', :json, %w(application/vnd.git-lfs+json application/json)
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index 74fef7c..5892c1d 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -18,6 +18,7 @@ if Rails.env.production?
       
       # Sanitize fields based on those sanitized from Rails.
       config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
+      config.tags = { program: Gitlab::Sentry.program_context }
     end
   end
 end
diff --git a/config/routes.rb b/config/routes.rb
index e93b640..4d6ec69 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -35,6 +35,10 @@ Rails.application.routes.draw do
     post :approve_access_request, on: :member
   end
 
+  concern :awardable do
+    post :toggle_award_emoji, on: :member
+  end
+
   namespace :ci do
     # CI API
     Ci::API::API.logger Rails.logger
@@ -98,7 +102,7 @@ Rails.application.routes.draw do
   #
   # Global snippets
   #
-  resources :snippets do
+  resources :snippets, concerns: :awardable do
     member do
       get 'raw'
     end
@@ -110,7 +114,6 @@ Rails.application.routes.draw do
   #
   # Invites
   #
-
   resources :invites, only: [:show], constraints: { id: /[A-Za-z0-9_-]+/ } do
     member do
       post :accept
@@ -157,12 +160,6 @@ Rails.application.routes.draw do
       get :jobs
     end
 
-    resource :gitorious, only: [:create, :new], controller: :gitorious do
-      get :status
-      get :callback
-      get :jobs
-    end
-
     resource :google_code, only: [:create, :new], controller: :google_code do
       get :status
       post :callback
@@ -668,7 +665,7 @@ Rails.application.routes.draw do
           end
         end
 
-        resources :snippets, constraints: { id: /\d+/ } do
+        resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
           member do
             get 'raw'
           end
@@ -730,7 +727,7 @@ Rails.application.routes.draw do
           end
         end
 
-        resources :merge_requests, constraints: { id: /\d+/ } do
+        resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do
           member do
             get :commits
             get :diffs
@@ -742,7 +739,6 @@ Rails.application.routes.draw do
             post :cancel_merge_when_build_succeeds
             get :ci_status
             post :toggle_subscription
-            post :toggle_award_emoji
             post :remove_wip
             get :diff_for_path
             post :resolve_conflicts
@@ -753,6 +749,7 @@ Rails.application.routes.draw do
             get :branch_to
             get :update_branches
             get :diff_for_path
+            post :bulk_update
           end
 
           resources :discussions, only: [], constraints: { id: /\h{40}/ } do
@@ -785,9 +782,19 @@ Rails.application.routes.draw do
 
         resources :environments
 
+        resource :cycle_analytics, only: [:show]
+
         resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
           collection do
             post :cancel_all
+
+            resources :artifacts, only: [] do
+              collection do
+                get :latest_succeeded,
+                  path: '*ref_name_and_path',
+                  format: false
+              end
+            end
           end
 
           member do
@@ -835,10 +842,9 @@ Rails.application.routes.draw do
           end
         end
 
-        resources :issues, constraints: { id: /\d+/ } do
+        resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
           member do
             post :toggle_subscription
-            post :toggle_award_emoji
             post :mark_as_spam
             get :referenced_merge_requests
             get :related_branches
@@ -866,9 +872,8 @@ Rails.application.routes.draw do
 
         resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ }
 
-        resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
+        resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
           member do
-            post :toggle_award_emoji
             delete :delete_attachment
             post :resolve
             delete :resolve, action: :unresolve
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
deleted file mode 100644
index 069d9dd..0000000
--- a/db/fixtures/development/14_builds.rb
+++ /dev/null
@@ -1,138 +0,0 @@
-class Gitlab::Seeder::Builds
-  STAGES = %w[build test deploy notify]
-  BUILDS = [
-    { name: 'build:linux', stage: 'build', status: :success },
-    { name: 'build:osx', stage: 'build', status: :success },
-    { name: 'rspec:linux', stage: 'test', status: :success },
-    { name: 'rspec:windows', stage: 'test', status: :success },
-    { name: 'rspec:windows', stage: 'test', status: :success },
-    { name: 'rspec:osx', stage: 'test', status_event: :success },
-    { name: 'spinach:linux', stage: 'test', status: :pending },
-    { name: 'spinach:osx', stage: 'test', status: :canceled },
-    { name: 'cucumber:linux', stage: 'test', status: :running },
-    { name: 'cucumber:osx', stage: 'test', status: :failed },
-    { name: 'staging', stage: 'deploy', environment: 'staging', status: :success },
-    { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped },
-    { name: 'slack', stage: 'notify', when: 'manual', status: :created },
-  ]
-
-  def initialize(project)
-    @project = project
-  end
-
-  def seed!
-    pipelines.each do |pipeline|
-      begin
-        BUILDS.each { |opts| build_create!(pipeline, opts) }
-        commit_status_create!(pipeline, name: 'jenkins', stage: 'test', status: :success)
-        print '.'
-      rescue ActiveRecord::RecordInvalid
-        print 'F'
-      ensure
-        pipeline.build_updated
-      end
-    end
-  end
-
-  def pipelines
-    master_pipelines + merge_request_pipelines
-  end
-
-  def master_pipelines
-    create_pipelines_for(@project, 'master')
-  rescue
-    []
-  end
-
-  def merge_request_pipelines
-    @project.merge_requests.last(5).map do |merge_request|
-      create_pipelines(merge_request.source_project, merge_request.source_branch, merge_request.commits.last(5))
-    end.flatten
-  rescue
-    []
-  end
-
-  def create_pipelines_for(project, ref)
-    commits = project.repository.commits(ref, limit: 5)
-    create_pipelines(project, ref, commits)
-  end
-
-  def create_pipelines(project, ref, commits)
-    commits.map do |commit|
-      project.pipelines.create(sha: commit.id, ref: ref)
-    end
-  end
-
-  def build_create!(pipeline, opts = {})
-    attributes = build_attributes_for(pipeline, opts)
-
-    Ci::Build.create!(attributes) do |build|
-      if opts[:name].start_with?('build')
-        artifacts_cache_file(artifacts_archive_path) do |file|
-          build.artifacts_file = file
-        end
-
-        artifacts_cache_file(artifacts_metadata_path) do |file|
-          build.artifacts_metadata = file
-        end
-      end
-
-      if %w(running success failed).include?(build.status)
-        # We need to set build trace after saving a build (id required)
-        build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
-      end
-    end
-  end
-
-  def commit_status_create!(pipeline, opts = {})
-    attributes = commit_status_attributes_for(pipeline, opts)
-    GenericCommitStatus.create!(attributes)
-  end
-
-  def commit_status_attributes_for(pipeline, opts)
-    { name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
-      ref: 'master', tag: false, user: build_user, project: @project, pipeline: pipeline,
-      created_at: Time.now, updated_at: Time.now
-    }.merge(opts)
-  end
-
-  def build_attributes_for(pipeline, opts)
-    commit_status_attributes_for(pipeline, opts).merge(commands: '$ build command')
-  end
-
-  def build_user
-    @project.team.users.sample
-  end
-
-  def build_status
-    Ci::Build::AVAILABLE_STATUSES.sample
-  end
-
-  def stage_index(stage)
-    STAGES.index(stage) || 0
-  end
-
-  def artifacts_archive_path
-    Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
-  end
-
-  def artifacts_metadata_path
-    Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
-  end
-
-  def artifacts_cache_file(file_path)
-    cache_path = file_path.to_s.gsub('ci_', "p#{@project.id}_")
-
-    FileUtils.copy(file_path, cache_path)
-    File.open(cache_path) do |file|
-      yield file
-    end
-  end
-end
-
-Gitlab::Seeder.quiet do
-  Project.all.sample(10).each do |project|
-    project_builds = Gitlab::Seeder::Builds.new(project)
-    project_builds.seed!
-  end
-end
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
new file mode 100644
index 0000000..650b410
--- /dev/null
+++ b/db/fixtures/development/14_pipelines.rb
@@ -0,0 +1,157 @@
+class Gitlab::Seeder::Pipelines
+  STAGES = %w[build test deploy notify]
+  BUILDS = [
+    { name: 'build:linux', stage: 'build', status: :success },
+    { name: 'build:osx', stage: 'build', status: :success },
+    { name: 'rspec:linux 0 3', stage: 'test', status: :success },
+    { name: 'rspec:linux 1 3', stage: 'test', status: :success },
+    { name: 'rspec:linux 2 3', stage: 'test', status: :success },
+    { name: 'rspec:windows 0 3', stage: 'test', status: :success },
+    { name: 'rspec:windows 1 3', stage: 'test', status: :success },
+    { name: 'rspec:windows 2 3', stage: 'test', status: :success },
+    { name: 'rspec:windows 2 3', stage: 'test', status: :success },
+    { name: 'rspec:osx', stage: 'test', status_event: :success },
+    { name: 'spinach:linux', stage: 'test', status: :success },
+    { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true},
+    { name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending },
+    { name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running },
+    { name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled },
+    { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success },
+    { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped },
+    { name: 'slack', stage: 'notify', when: 'manual', status: :created },
+  ]
+
+  def initialize(project)
+    @project = project
+  end
+
+  def seed!
+    pipelines.each do |pipeline|
+      begin
+        BUILDS.each { |opts| build_create!(pipeline, opts) }
+        commit_status_create!(pipeline, name: 'jenkins', stage: 'test', status: :success)
+        print '.'
+      rescue ActiveRecord::RecordInvalid
+        print 'F'
+      ensure
+        pipeline.build_updated
+      end
+    end
+  end
+
+  private
+
+  def pipelines
+    create_master_pipelines + create_merge_request_pipelines
+  end
+
+  def create_master_pipelines
+    @project.repository.commits('master', limit: 4).map do |commit|
+      create_pipeline!(@project, 'master', commit)
+    end
+  rescue
+    []
+  end
+
+  def create_merge_request_pipelines
+    pipelines = @project.merge_requests.first(3).map do |merge_request|
+      project = merge_request.source_project
+      branch = merge_request.source_branch
+
+      merge_request.commits.last(4).map do |commit|
+        create_pipeline!(project, branch, commit)
+      end
+    end
+
+    pipelines.flatten
+  rescue
+    []
+  end
+
+
+  def create_pipeline!(project, ref, commit)
+    project.pipelines.create(sha: commit.id, ref: ref)
+  end
+
+  def build_create!(pipeline, opts = {})
+    attributes = job_attributes(pipeline, opts)
+      .merge(commands: '$ build command')
+
+    Ci::Build.create!(attributes).tap do |build|
+      # We need to set build trace and artifacts after saving a build
+      # (id required), that is why we need `#tap` method instead of passing
+      # block directly to `Ci::Build#create!`.
+
+      setup_artifacts(build)
+      setup_build_log(build)
+      build.save
+    end
+  end
+
+  def setup_artifacts(build)
+    return unless %w[build test].include?(build.stage)
+
+    artifacts_cache_file(artifacts_archive_path) do |file|
+      build.artifacts_file = file
+    end
+
+    artifacts_cache_file(artifacts_metadata_path) do |file|
+      build.artifacts_metadata = file
+    end
+  end
+
+  def setup_build_log(build)
+    if %w(running success failed).include?(build.status)
+      build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
+    end
+  end
+
+  def commit_status_create!(pipeline, opts = {})
+    attributes = job_attributes(pipeline, opts)
+
+    GenericCommitStatus.create!(attributes)
+  end
+
+  def job_attributes(pipeline, opts)
+    { name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
+      ref: 'master', tag: false, user: build_user, project: @project, pipeline: pipeline,
+      created_at: Time.now, updated_at: Time.now
+    }.merge(opts)
+  end
+
+  def build_user
+    @project.team.users.sample
+  end
+
+  def build_status
+    Ci::Build::AVAILABLE_STATUSES.sample
+  end
+
+  def stage_index(stage)
+    STAGES.index(stage) || 0
+  end
+
+  def artifacts_archive_path
+    Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
+  end
+
+  def artifacts_metadata_path
+    Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
+  end
+
+  def artifacts_cache_file(file_path)
+    cache_path = file_path.to_s.gsub('ci_', "p#{@project.id}_")
+
+    FileUtils.copy(file_path, cache_path)
+    File.open(cache_path) do |file|
+      yield file
+    end
+  end
+end
+
+Gitlab::Seeder.quiet do
+  Project.all.sample(5).each do |project|
+    project_builds = Gitlab::Seeder::Pipelines.new(project)
+    project_builds.seed!
+  end
+end
diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb
new file mode 100644
index 0000000..e882a49
--- /dev/null
+++ b/db/fixtures/development/17_cycle_analytics.rb
@@ -0,0 +1,246 @@
+require 'sidekiq/testing'
+require './spec/support/test_env'
+
+class Gitlab::Seeder::CycleAnalytics
+  def initialize(project, perf: false)
+    @project = project
+    @user = User.order(:id).last
+    @issue_count = perf ? 1000 : 5
+    stub_git_pre_receive!
+  end
+
+  # The GitLab API needn't be running for the fixtures to be
+  # created. Since we're performing a number of git actions
+  # here (like creating a branch or committing a file), we need
+  # to disable the `pre_receive` hook in order to remove this
+  # dependency on the GitLab API.
+  def stub_git_pre_receive!
+    GitHooksService.class_eval do
+      def run_hook(name)
+        [true, '']
+      end
+    end
+  end
+
+  def seed_metrics!
+    @issue_count.times do |index|
+      # Issue
+      Timecop.travel 5.days.from_now
+      title = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
+      issue = Issue.create(project: @project, title: title, author: @user)
+      issue_metrics = issue.metrics
+
+      # Milestones / Labels
+      Timecop.travel 5.days.from_now
+      if index.even?
+        issue_metrics.first_associated_with_milestone_at = rand(6..12).hours.from_now
+      else
+        issue_metrics.first_added_to_board_at = rand(6..12).hours.from_now
+      end
+
+      # Commit
+      Timecop.travel 5.days.from_now
+      issue_metrics.first_mentioned_in_commit_at = rand(6..12).hours.from_now
+
+      # MR
+      Timecop.travel 5.days.from_now
+      branch_name = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
+      @project.repository.add_branch(@user, branch_name, 'master')
+      merge_request = MergeRequest.create(target_project: @project, source_project: @project, source_branch: branch_name, target_branch: 'master', title: branch_name, author: @user)
+      merge_request_metrics = merge_request.metrics
+
+      # MR closing issues
+      Timecop.travel 5.days.from_now
+      MergeRequestsClosingIssues.create!(issue: issue, merge_request: merge_request)
+
+      # Merge
+      Timecop.travel 5.days.from_now
+      merge_request_metrics.merged_at = rand(6..12).hours.from_now
+
+      # Start build
+      Timecop.travel 5.days.from_now
+      merge_request_metrics.latest_build_started_at = rand(6..12).hours.from_now
+
+      # Finish build
+      Timecop.travel 5.days.from_now
+      merge_request_metrics.latest_build_finished_at = rand(6..12).hours.from_now
+
+      # Deploy to production
+      Timecop.travel 5.days.from_now
+      merge_request_metrics.first_deployed_to_production_at = rand(6..12).hours.from_now
+
+      issue_metrics.save!
+      merge_request_metrics.save!
+
+      print '.'
+    end
+  end
+
+  def seed!
+    Sidekiq::Testing.inline! do
+      issues = create_issues
+      puts '.'
+
+      # Stage 1
+      Timecop.travel 5.days.from_now
+      add_milestones_and_list_labels(issues)
+      print '.'
+
+      # Stage 2
+      Timecop.travel 5.days.from_now
+      branches = mention_in_commits(issues)
+      print '.'
+
+      # Stage 3
+      Timecop.travel 5.days.from_now
+      merge_requests = create_merge_requests_closing_issues(issues, branches)
+      print '.'
+
+      # Stage 4
+      Timecop.travel 5.days.from_now
+      run_builds(merge_requests)
+      print '.'
+
+      # Stage 5
+      Timecop.travel 5.days.from_now
+      merge_merge_requests(merge_requests)
+      print '.'
+
+      # Stage 6 / 7
+      Timecop.travel 5.days.from_now
+      deploy_to_production(merge_requests)
+      print '.'
+    end
+
+    print '.'
+  end
+
+  private
+
+  def create_issues
+    Array.new(@issue_count) do
+      issue_params = {
+        title: "Cycle Analytics: #{FFaker::Lorem.sentence(6)}",
+        description: FFaker::Lorem.sentence,
+        state: 'opened',
+        assignee: @project.team.users.sample
+      }
+
+      Issues::CreateService.new(@project, @project.team.users.sample, issue_params).execute
+    end
+  end
+
+  def add_milestones_and_list_labels(issues)
+    issues.shuffle.map.with_index do |issue, index|
+      Timecop.travel 12.hours.from_now
+
+      if index.even?
+        issue.update(milestone: @project.milestones.sample)
+      else
+        label_name = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
+        list_label = FactoryGirl.create(:label, title: label_name, project: issue.project)
+        FactoryGirl.create(:list, board: FactoryGirl.create(:board, project: issue.project), label: list_label)
+        issue.update(labels: [list_label])
+      end
+
+      issue
+    end
+  end
+
+  def mention_in_commits(issues)
+    issues.map do |issue|
+      Timecop.travel 12.hours.from_now
+
+      branch_name = filename = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
+
+      issue.project.repository.add_branch(@user, branch_name, 'master')
+
+      options = {
+        committer: issue.project.repository.user_to_committer(@user),
+        author: issue.project.repository.user_to_committer(@user),
+        commit: { message: "Commit for ##{issue.iid}", branch: branch_name, update_ref: true },
+        file: { content: "content", path: filename, update: false }
+      }
+
+      commit_sha = Gitlab::Git::Blob.commit(issue.project.repository, options)
+      issue.project.repository.commit(commit_sha)
+
+
+      GitPushService.new(issue.project,
+                         @user,
+                         oldrev: issue.project.repository.commit("master").sha,
+                         newrev: commit_sha,
+                         ref: 'refs/heads/master').execute
+
+      branch_name
+    end
+  end
+
+  def create_merge_requests_closing_issues(issues, branches)
+    issues.zip(branches).map do |issue, branch|
+      Timecop.travel 12.hours.from_now
+
+      opts = {
+        title: 'Cycle Analytics merge_request',
+        description: "Fixes #{issue.to_reference}",
+        source_branch: branch,
+        target_branch: 'master'
+      }
+
+      MergeRequests::CreateService.new(issue.project, @user, opts).execute
+    end
+  end
+
+  def run_builds(merge_requests)
+    merge_requests.each do |merge_request|
+      Timecop.travel 12.hours.from_now
+
+      service = Ci::CreatePipelineService.new(merge_request.project,
+                                              @user,
+                                              ref: "refs/heads/#{merge_request.source_branch}")
+      pipeline = service.execute(ignore_skip_ci: true, save_on_errors: false)
+
+      pipeline.run!
+      Timecop.travel rand(1..6).hours.from_now
+      pipeline.succeed!
+    end
+  end
+
+  def merge_merge_requests(merge_requests)
+    merge_requests.each do |merge_request|
+      Timecop.travel 12.hours.from_now
+
+      MergeRequests::MergeService.new(merge_request.project, @user).execute(merge_request)
+    end
+  end
+
+  def deploy_to_production(merge_requests)
+    merge_requests.each do |merge_request|
+      Timecop.travel 12.hours.from_now
+
+      CreateDeploymentService.new(merge_request.project, @user, {
+                                    environment: 'production',
+                                    ref: 'master',
+                                    tag: false,
+                                    sha: @project.repository.commit('master').sha
+                                  }).execute
+    end
+  end
+end
+
+Gitlab::Seeder.quiet do
+  if ENV['SEED_CYCLE_ANALYTICS']
+    Project.all.each do |project|
+      seeder = Gitlab::Seeder::CycleAnalytics.new(project)
+      seeder.seed!
+    end
+  elsif ENV['CYCLE_ANALYTICS_PERF_TEST']
+    seeder = Gitlab::Seeder::CycleAnalytics.new(Project.order(:id).first, perf: true)
+    seeder.seed!
+  elsif ENV['CYCLE_ANALYTICS_POPULATE_METRICS_DIRECTLY']
+    seeder = Gitlab::Seeder::CycleAnalytics.new(Project.order(:id).first, perf: true)
+    seeder.seed_metrics!
+  else
+    puts "Not running the cycle analytics seed file. Use the `SEED_CYCLE_ANALYTICS` environment variable to enable it."
+  end
+end
diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb
index 8446372..e8de7cc 100644
--- a/db/migrate/20140502125220_migrate_repo_size.rb
+++ b/db/migrate/20140502125220_migrate_repo_size.rb
@@ -1,12 +1,15 @@
 # rubocop:disable all
 class MigrateRepoSize < ActiveRecord::Migration
+  DOWNTIME = false
+
   def up
     project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id')
 
     project_data.each do |project|
       id = project['id']
       namespace_path = project['namespace_path'] || ''
-      path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git')
+      repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default
+      path = File.join(repos_path, namespace_path, project['project_path'] + '.git')
 
       begin
         repo = Gitlab::Git::Repository.new(path)
diff --git a/db/migrate/20160707104333_add_lock_to_issuables.rb b/db/migrate/20160707104333_add_lock_to_issuables.rb
new file mode 100644
index 0000000..54866d0
--- /dev/null
+++ b/db/migrate/20160707104333_add_lock_to_issuables.rb
@@ -0,0 +1,18 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLockToIssuables < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def up
+    add_column :issues, :lock_version, :integer
+    add_column :merge_requests, :lock_version, :integer
+  end
+
+  def down
+    remove_column :issues, :lock_version
+    remove_column :merge_requests, :lock_version
+  end
+end
diff --git a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb
new file mode 100644
index 0000000..75a3eb1
--- /dev/null
+++ b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb
@@ -0,0 +1,35 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MergeRequestDiffRemoveUniq < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+  disable_ddl_transaction!
+
+  DOWNTIME = false
+
+  def up
+    constraint_name = 'merge_request_diffs_merge_request_id_key'
+
+    transaction do
+      if index_exists?(:merge_request_diffs, :merge_request_id)
+        remove_index(:merge_request_diffs, :merge_request_id)
+      end
+
+      # In some bizarre cases PostgreSQL might have a separate unique constraint
+      # that we'll need to drop.
+      if constraint_exists?(constraint_name) && Gitlab::Database.postgresql?
+        execute("ALTER TABLE merge_request_diffs DROP CONSTRAINT IF EXISTS #{constraint_name};")
+      end
+    end
+  end
+
+  def down
+    unless index_exists?(:merge_request_diffs, :merge_request_id)
+      add_concurrent_index(:merge_request_diffs, :merge_request_id, unique: true)
+    end
+  end
+
+  def constraint_exists?(name)
+    indexes(:merge_request_diffs).map(&:name).include?(name)
+  end
+end
diff --git a/db/migrate/20160725104452_merge_request_diff_add_index.rb b/db/migrate/20160725104452_merge_request_diff_add_index.rb
new file mode 100644
index 0000000..6d04242
--- /dev/null
+++ b/db/migrate/20160725104452_merge_request_diff_add_index.rb
@@ -0,0 +1,17 @@
+class MergeRequestDiffAddIndex < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+  disable_ddl_transaction!
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  def up
+    add_concurrent_index :merge_request_diffs, :merge_request_id
+  end
+
+  def down
+    if index_exists?(:merge_request_diffs, :merge_request_id)
+      remove_index :merge_request_diffs, :merge_request_id
+    end
+  end
+end
diff --git a/db/migrate/20160808085531_add_token_to_build.rb b/db/migrate/20160808085531_add_token_to_build.rb
new file mode 100644
index 0000000..3ed2a10
--- /dev/null
+++ b/db/migrate/20160808085531_add_token_to_build.rb
@@ -0,0 +1,10 @@
+class AddTokenToBuild < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  def change
+    add_column :ci_builds, :token, :string
+  end
+end
diff --git a/db/migrate/20160808085602_add_index_for_build_token.rb b/db/migrate/20160808085602_add_index_for_build_token.rb
new file mode 100644
index 0000000..10ef42a
--- /dev/null
+++ b/db/migrate/20160808085602_add_index_for_build_token.rb
@@ -0,0 +1,12 @@
+class AddIndexForBuildToken < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def change
+    add_concurrent_index :ci_builds, :token, unique: true
+  end
+end
diff --git a/db/migrate/20160823081327_change_merge_error_to_text.rb b/db/migrate/20160823081327_change_merge_error_to_text.rb
new file mode 100644
index 0000000..7920389
--- /dev/null
+++ b/db/migrate/20160823081327_change_merge_error_to_text.rb
@@ -0,0 +1,10 @@
+class ChangeMergeErrorToText < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = true
+  DOWNTIME_REASON = 'This migration requires downtime because it alters a column from varchar(255) to text.'
+
+  def change
+    change_column :merge_requests, :merge_error, :text, limit: 65535
+  end
+end
diff --git a/db/migrate/20160823213309_add_lfs_enabled_to_projects.rb b/db/migrate/20160823213309_add_lfs_enabled_to_projects.rb
new file mode 100644
index 0000000..c169084
--- /dev/null
+++ b/db/migrate/20160823213309_add_lfs_enabled_to_projects.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLfsEnabledToProjects < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  # When a migration requires downtime you **must** uncomment the following
+  # constant and define a short and easy to understand explanation as to why the
+  # migration requires downtime.
+  # DOWNTIME_REASON = ''
+
+  # When using the methods "add_concurrent_index" or "add_column_with_default"
+  # you must disable the use of transactions as these methods can not run in an
+  # existing transaction. When using "add_concurrent_index" make sure that this
+  # method is the _only_ method called in the migration, any other changes
+  # should go in a separate migration. This ensures that upon failure _only_ the
+  # index creation fails and can be retried or reverted easily.
+  #
+  # To disable transactions uncomment the following line and remove these
+  # comments:
+  # disable_ddl_transaction!
+
+  def change
+    add_column :projects, :lfs_enabled, :boolean
+  end
+end
diff --git a/db/migrate/20160824103857_drop_unused_ci_tables.rb b/db/migrate/20160824103857_drop_unused_ci_tables.rb
new file mode 100644
index 0000000..65cf463
--- /dev/null
+++ b/db/migrate/20160824103857_drop_unused_ci_tables.rb
@@ -0,0 +1,11 @@
+class DropUnusedCiTables < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  def change
+    drop_table(:ci_services)
+    drop_table(:ci_web_hooks)
+  end
+end
diff --git a/db/migrate/20160824124900_add_table_issue_metrics.rb b/db/migrate/20160824124900_add_table_issue_metrics.rb
new file mode 100644
index 0000000..e9bb79b
--- /dev/null
+++ b/db/migrate/20160824124900_add_table_issue_metrics.rb
@@ -0,0 +1,37 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddTableIssueMetrics < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = true
+
+  # When a migration requires downtime you **must** uncomment the following
+  # constant and define a short and easy to understand explanation as to why the
+  # migration requires downtime.
+  DOWNTIME_REASON = 'Adding foreign key'
+
+  # When using the methods "add_concurrent_index" or "add_column_with_default"
+  # you must disable the use of transactions as these methods can not run in an
+  # existing transaction. When using "add_concurrent_index" make sure that this
+  # method is the _only_ method called in the migration, any other changes
+  # should go in a separate migration. This ensures that upon failure _only_ the
+  # index creation fails and can be retried or reverted easily.
+  #
+  # To disable transactions uncomment the following line and remove these
+  # comments:
+  # disable_ddl_transaction!
+
+  def change
+    create_table :issue_metrics do |t|
+      t.references :issue, index: { name: "index_issue_metrics" }, foreign_key: { on_delete: :cascade }, null: false
+
+      t.datetime 'first_mentioned_in_commit_at'
+      t.datetime 'first_associated_with_milestone_at'
+      t.datetime 'first_added_to_board_at'
+
+      t.timestamps null: false
+    end
+  end
+end
diff --git a/db/migrate/20160825052008_add_table_merge_request_metrics.rb b/db/migrate/20160825052008_add_table_merge_request_metrics.rb
new file mode 100644
index 0000000..e01cc50
--- /dev/null
+++ b/db/migrate/20160825052008_add_table_merge_request_metrics.rb
@@ -0,0 +1,38 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddTableMergeRequestMetrics < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = true
+
+  # When a migration requires downtime you **must** uncomment the following
+  # constant and define a short and easy to understand explanation as to why the
+  # migration requires downtime.
+  DOWNTIME_REASON = 'Adding foreign key'
+
+  # When using the methods "add_concurrent_index" or "add_column_with_default"
+  # you must disable the use of transactions as these methods can not run in an
+  # existing transaction. When using "add_concurrent_index" make sure that this
+  # method is the _only_ method called in the migration, any other changes
+  # should go in a separate migration. This ensures that upon failure _only_ the
+  # index creation fails and can be retried or reverted easily.
+  #
+  # To disable transactions uncomment the following line and remove these
+  # comments:
+  # disable_ddl_transaction!
+
+  def change
+    create_table :merge_request_metrics do |t|
+      t.references :merge_request, index: { name: "index_merge_request_metrics" }, foreign_key: { on_delete: :cascade }, null: false
+
+      t.datetime 'latest_build_started_at'
+      t.datetime 'latest_build_finished_at'
+      t.datetime 'first_deployed_to_production_at', index: true
+      t.datetime 'merged_at'
+
+      t.timestamps null: false
+    end
+  end
+end
diff --git a/db/migrate/20160827011312_ensure_lock_version_has_no_default.rb b/db/migrate/20160827011312_ensure_lock_version_has_no_default.rb
new file mode 100644
index 0000000..7c55bc2
--- /dev/null
+++ b/db/migrate/20160827011312_ensure_lock_version_has_no_default.rb
@@ -0,0 +1,16 @@
+class EnsureLockVersionHasNoDefault < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def up
+    change_column_default :issues, :lock_version, nil
+    change_column_default :merge_requests, :lock_version, nil
+
+    execute('UPDATE issues SET lock_version = 1 WHERE lock_version = 0')
+    execute('UPDATE merge_requests SET lock_version = 1 WHERE lock_version = 0')
+  end
+
+  def down
+  end
+end
diff --git a/db/migrate/20160830203109_add_confidential_issues_events_to_web_hooks.rb b/db/migrate/20160830203109_add_confidential_issues_events_to_web_hooks.rb
new file mode 100644
index 0000000..a279472
--- /dev/null
+++ b/db/migrate/20160830203109_add_confidential_issues_events_to_web_hooks.rb
@@ -0,0 +1,15 @@
+class AddConfidentialIssuesEventsToWebHooks < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_column_with_default :web_hooks, :confidential_issues_events, :boolean, default: false, allow_null: false
+  end
+
+  def down
+    remove_column :web_hooks, :confidential_issues_events
+  end
+end
diff --git a/db/migrate/20160830211132_add_confidential_issues_events_to_services.rb b/db/migrate/20160830211132_add_confidential_issues_events_to_services.rb
new file mode 100644
index 0000000..030e7c3
--- /dev/null
+++ b/db/migrate/20160830211132_add_confidential_issues_events_to_services.rb
@@ -0,0 +1,15 @@
+class AddConfidentialIssuesEventsToServices < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_column_with_default :services, :confidential_issues_events, :boolean, default: true, allow_null: false
+  end
+
+  def down
+    remove_column :services, :confidential_issues_events
+  end
+end
diff --git a/db/migrate/20160830232601_change_lock_version_not_null.rb b/db/migrate/20160830232601_change_lock_version_not_null.rb
new file mode 100644
index 0000000..01c58ed
--- /dev/null
+++ b/db/migrate/20160830232601_change_lock_version_not_null.rb
@@ -0,0 +1,13 @@
+class ChangeLockVersionNotNull < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def up
+    change_column_null :issues, :lock_version, true
+    change_column_null :merge_requests, :lock_version, true
+  end
+
+  def down
+  end
+end
diff --git a/db/migrate/20160831214002_create_project_features.rb b/db/migrate/20160831214002_create_project_features.rb
new file mode 100644
index 0000000..2d76a01
--- /dev/null
+++ b/db/migrate/20160831214002_create_project_features.rb
@@ -0,0 +1,16 @@
+class CreateProjectFeatures < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def change
+    create_table :project_features do |t|
+      t.belongs_to :project, index: true
+      t.integer  :merge_requests_access_level
+      t.integer  :issues_access_level
+      t.integer  :wiki_access_level
+      t.integer  :snippets_access_level
+      t.integer  :builds_access_level
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20160831214543_migrate_project_features.rb b/db/migrate/20160831214543_migrate_project_features.rb
new file mode 100644
index 0000000..93f9821
--- /dev/null
+++ b/db/migrate/20160831214543_migrate_project_features.rb
@@ -0,0 +1,44 @@
+class MigrateProjectFeatures < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = true
+  DOWNTIME_REASON =
+    <<-EOT
+      Migrating issues_enabled, merge_requests_enabled, wiki_enabled, builds_enabled, snippets_enabled fields from projects to
+      a new table called project_features.
+    EOT
+
+  def up
+    sql =
+      %Q{
+        INSERT INTO project_features(project_id, issues_access_level, merge_requests_access_level, wiki_access_level,
+        builds_access_level, snippets_access_level, created_at, updated_at)
+          SELECT
+          id AS project_id,
+          CASE WHEN issues_enabled IS true THEN 20 ELSE 0 END AS issues_access_level,
+          CASE WHEN merge_requests_enabled IS true THEN 20 ELSE 0 END AS merge_requests_access_level,
+          CASE WHEN wiki_enabled IS true THEN 20 ELSE 0 END AS wiki_access_level,
+          CASE WHEN builds_enabled IS true THEN 20 ELSE 0 END AS builds_access_level,
+          CASE WHEN snippets_enabled IS true THEN 20 ELSE 0 END AS snippets_access_level,
+          created_at,
+          updated_at
+          FROM projects
+      }
+
+    execute(sql)
+  end
+
+  def down
+    sql = %Q{
+      UPDATE projects
+      SET
+      issues_enabled = COALESCE((SELECT CASE WHEN issues_access_level = 20 THEN true ELSE false END AS issues_enabled FROM project_features WHERE project_features.project_id = projects.id), true),
+      merge_requests_enabled = COALESCE((SELECT CASE WHEN merge_requests_access_level = 20 THEN true ELSE false END AS merge_requests_enabled FROM project_features WHERE project_features.project_id = projects.id),true),
+      wiki_enabled = COALESCE((SELECT CASE WHEN wiki_access_level = 20 THEN true ELSE false END AS wiki_enabled FROM project_features WHERE project_features.project_id = projects.id), true),
+      builds_enabled = COALESCE((SELECT CASE WHEN builds_access_level = 20 THEN true ELSE false END AS builds_enabled FROM project_features WHERE project_features.project_id = projects.id), true),
+      snippets_enabled = COALESCE((SELECT CASE WHEN snippets_access_level = 20 THEN true ELSE false END AS snippets_enabled FROM project_features WHERE project_features.project_id = projects.id),true)
+    }
+
+    execute(sql)
+  end
+end
diff --git a/db/migrate/20160831223750_remove_features_enabled_from_projects.rb b/db/migrate/20160831223750_remove_features_enabled_from_projects.rb
new file mode 100644
index 0000000..a2c207b
--- /dev/null
+++ b/db/migrate/20160831223750_remove_features_enabled_from_projects.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveFeaturesEnabledFromProjects < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+  disable_ddl_transaction!
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = true
+  DOWNTIME_REASON = "Removing fields from database requires downtine."
+
+  def up
+    remove_column :projects, :issues_enabled
+    remove_column :projects, :merge_requests_enabled
+    remove_column :projects, :builds_enabled
+    remove_column :projects, :wiki_enabled
+    remove_column :projects, :snippets_enabled
+  end
+
+  # Ugly SQL but the only way i found to make it work on both Postgres and Mysql
+  # It will be slow but it is ok since it is a revert method
+  def down
+    add_column_with_default(:projects, :issues_enabled, :boolean, default: true, allow_null: false)
+    add_column_with_default(:projects, :merge_requests_enabled, :boolean, default: true, allow_null: false)
+    add_column_with_default(:projects, :builds_enabled, :boolean, default: true, allow_null: false)
+    add_column_with_default(:projects, :wiki_enabled, :boolean, default: true, allow_null: false)
+    add_column_with_default(:projects, :snippets_enabled, :boolean, default: true, allow_null: false)
+  end
+end
diff --git a/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb b/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb
new file mode 100644
index 0000000..f1a1f00
--- /dev/null
+++ b/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb
@@ -0,0 +1,15 @@
+class SetConfidentialIssuesEventsOnWebhooks < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def up
+    update_column_in_batches(:web_hooks, :confidential_issues_events, true) do |table, query|
+      query.where(table[:issues_events].eq(true))
+    end
+  end
+
+  def down
+    # noop
+  end
+end
diff --git a/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb
new file mode 100644
index 0000000..fd413d1
--- /dev/null
+++ b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb
@@ -0,0 +1,12 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLfsEnabledToNamespaces < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column :namespaces, :lfs_enabled, :boolean
+  end
+end
diff --git a/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb b/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb
new file mode 100644
index 0000000..a80a572
--- /dev/null
+++ b/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb
@@ -0,0 +1,39 @@
+class DropGitoriousFieldFromApplicationSettings < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # After the deploy the caches will be cold anyway
+  DOWNTIME = false
+
+  def up
+    require 'yaml'
+
+    import_sources = connection.execute('SELECT import_sources FROM application_settings;')
+    return unless import_sources.first # support empty databases
+
+    yaml = if Gitlab::Database.postgresql?
+             import_sources.values[0][0]
+           else
+             import_sources.first[0]
+           end
+
+    yaml = YAML.safe_load(yaml)
+    yaml.delete 'gitorious'
+
+    # No need for a WHERE clause as there is only one
+    connection.execute("UPDATE application_settings SET import_sources = #{update_yaml(yaml)}")
+  end
+
+  def down
+    # noop, gitorious still yields a 404 anyway
+  end
+
+  private
+
+  def connection
+    ActiveRecord::Base.connection
+  end
+
+  def update_yaml(yaml)
+    connection.quote(YAML.dump(yaml))
+  end
+end
diff --git a/db/migrate/20160907131111_add_environment_type_to_environments.rb b/db/migrate/20160907131111_add_environment_type_to_environments.rb
new file mode 100644
index 0000000..fac7375
--- /dev/null
+++ b/db/migrate/20160907131111_add_environment_type_to_environments.rb
@@ -0,0 +1,9 @@
+class AddEnvironmentTypeToEnvironments < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column :environments, :environment_type, :string
+  end
+end
diff --git a/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb
new file mode 100644
index 0000000..18ea9d4
--- /dev/null
+++ b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveProjectsPushesSinceGc < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = true
+  DOWNTIME_REASON = 'This migration removes an existing column'
+
+  disable_ddl_transaction!
+
+  def up
+    remove_column :projects, :pushes_since_gc
+  end
+
+  def down
+    add_column_with_default :projects, :pushes_since_gc, :integer, default: 0
+  end
+end
diff --git a/db/migrate/20160913212128_change_artifacts_size_column.rb b/db/migrate/20160913212128_change_artifacts_size_column.rb
new file mode 100644
index 0000000..063bbca
--- /dev/null
+++ b/db/migrate/20160913212128_change_artifacts_size_column.rb
@@ -0,0 +1,15 @@
+class ChangeArtifactsSizeColumn < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = true
+
+  DOWNTIME_REASON = 'Changing an integer column size requires a full table rewrite.'
+
+  def up
+    change_column :ci_builds, :artifacts_size, :integer, limit: 8
+  end
+
+  def down
+    # do nothing
+  end
+end
diff --git a/db/migrate/20160915042921_create_merge_requests_closing_issues.rb b/db/migrate/20160915042921_create_merge_requests_closing_issues.rb
new file mode 100644
index 0000000..94874a8
--- /dev/null
+++ b/db/migrate/20160915042921_create_merge_requests_closing_issues.rb
@@ -0,0 +1,34 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateMergeRequestsClosingIssues < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = true
+
+  # When a migration requires downtime you **must** uncomment the following
+  # constant and define a short and easy to understand explanation as to why the
+  # migration requires downtime.
+  DOWNTIME_REASON = 'Adding foreign keys'
+
+  # When using the methods "add_concurrent_index" or "add_column_with_default"
+  # you must disable the use of transactions as these methods can not run in an
+  # existing transaction. When using "add_concurrent_index" make sure that this
+  # method is the _only_ method called in the migration, any other changes
+  # should go in a separate migration. This ensures that upon failure _only_ the
+  # index creation fails and can be retried or reverted easily.
+  #
+  # To disable transactions uncomment the following line and remove these
+  # comments:
+  # disable_ddl_transaction!
+
+  def change
+    create_table :merge_requests_closing_issues do |t|
+      t.references :merge_request, foreign_key: { on_delete: :cascade }, index: true, null: false
+      t.references :issue, foreign_key: { on_delete: :cascade }, index: true, null: false
+
+      t.timestamps null: false
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 6cbc766..59b3e23 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: 20160819221833) do
+ActiveRecord::Schema.define(version: 20160915042921) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -84,8 +84,8 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.string   "health_check_access_token"
     t.boolean  "send_user_confirmation_email",          default: false
     t.integer  "container_registry_token_expire_delay", default: 5
-    t.boolean  "user_default_external",                 default: false,       null: false
     t.text     "after_sign_up_text"
+    t.boolean  "user_default_external",                 default: false,       null: false
     t.string   "repository_storage",                    default: "default"
     t.string   "enabled_git_access_protocol"
     t.boolean  "domain_blacklist_enabled",              default: false
@@ -158,9 +158,9 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.text     "commands"
     t.integer  "job_id"
     t.string   "name"
-    t.boolean  "deploy",              default: false
+    t.boolean  "deploy",                        default: false
     t.text     "options"
-    t.boolean  "allow_failure",       default: false, null: false
+    t.boolean  "allow_failure",                 default: false, null: false
     t.string   "stage"
     t.integer  "trigger_request_id"
     t.integer  "stage_idx"
@@ -175,12 +175,13 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.text     "artifacts_metadata"
     t.integer  "erased_by_id"
     t.datetime "erased_at"
-    t.string   "environment"
     t.datetime "artifacts_expire_at"
-    t.integer  "artifacts_size"
+    t.string   "environment"
+    t.integer  "artifacts_size",      limit: 8
     t.string   "when"
     t.text     "yaml_variables"
     t.datetime "queued_at"
+    t.string   "token"
   end
 
   add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
@@ -192,6 +193,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do
   add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
   add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
   add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
+  add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree
 
   create_table "ci_commits", force: :cascade do |t|
     t.integer  "project_id"
@@ -295,16 +297,6 @@ ActiveRecord::Schema.define(version: 20160819221833) do
   add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
   add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
 
-  create_table "ci_services", force: :cascade do |t|
-    t.string   "type"
-    t.string   "title"
-    t.integer  "project_id",                 null: false
-    t.datetime "created_at"
-    t.datetime "updated_at"
-    t.boolean  "active",     default: false, null: false
-    t.text     "properties"
-  end
-
   create_table "ci_sessions", force: :cascade do |t|
     t.string   "session_id", null: false
     t.text     "data"
@@ -360,13 +352,6 @@ ActiveRecord::Schema.define(version: 20160819221833) do
 
   add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree
 
-  create_table "ci_web_hooks", force: :cascade do |t|
-    t.string   "url",        null: false
-    t.integer  "project_id", null: false
-    t.datetime "created_at"
-    t.datetime "updated_at"
-  end
-
   create_table "deploy_keys_projects", force: :cascade do |t|
     t.integer  "deploy_key_id", null: false
     t.integer  "project_id",    null: false
@@ -407,10 +392,11 @@ ActiveRecord::Schema.define(version: 20160819221833) do
 
   create_table "environments", force: :cascade do |t|
     t.integer  "project_id"
-    t.string   "name",         null: false
+    t.string   "name",             null: false
     t.datetime "created_at"
     t.datetime "updated_at"
     t.string   "external_url"
+    t.string   "environment_type"
   end
 
   add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree
@@ -453,6 +439,17 @@ ActiveRecord::Schema.define(version: 20160819221833) do
 
   add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
 
+  create_table "issue_metrics", force: :cascade do |t|
+    t.integer  "issue_id",                           null: false
+    t.datetime "first_mentioned_in_commit_at"
+    t.datetime "first_associated_with_milestone_at"
+    t.datetime "first_added_to_board_at"
+    t.datetime "created_at",                         null: false
+    t.datetime "updated_at",                         null: false
+  end
+
+  add_index "issue_metrics", ["issue_id"], name: "index_issue_metrics", using: :btree
+
   create_table "issues", force: :cascade do |t|
     t.string   "title"
     t.integer  "assignee_id"
@@ -471,6 +468,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.datetime "deleted_at"
     t.date     "due_date"
     t.integer  "moved_to_id"
+    t.integer  "lock_version"
   end
 
   add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
@@ -592,7 +590,20 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.string   "start_commit_sha"
   end
 
-  add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
+  add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", using: :btree
+
+  create_table "merge_request_metrics", force: :cascade do |t|
+    t.integer  "merge_request_id",                null: false
+    t.datetime "latest_build_started_at"
+    t.datetime "latest_build_finished_at"
+    t.datetime "first_deployed_to_production_at"
+    t.datetime "merged_at"
+    t.datetime "created_at",                      null: false
+    t.datetime "updated_at",                      null: false
+  end
+
+  add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree
+  add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree
 
   create_table "merge_requests", force: :cascade do |t|
     t.string   "target_branch",                                null: false
@@ -612,13 +623,14 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.integer  "position",                     default: 0
     t.datetime "locked_at"
     t.integer  "updated_by_id"
-    t.string   "merge_error"
+    t.text     "merge_error"
+    t.text     "merge_params"
     t.boolean  "merge_when_build_succeeds",    default: false, null: false
     t.integer  "merge_user_id"
     t.string   "merge_commit_sha"
     t.datetime "deleted_at"
     t.string   "in_progress_merge_commit_sha"
-    t.text     "merge_params"
+    t.integer  "lock_version"
   end
 
   add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -634,6 +646,16 @@ ActiveRecord::Schema.define(version: 20160819221833) do
   add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
   add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
 
+  create_table "merge_requests_closing_issues", force: :cascade do |t|
+    t.integer  "merge_request_id", null: false
+    t.integer  "issue_id",         null: false
+    t.datetime "created_at",       null: false
+    t.datetime "updated_at",       null: false
+  end
+
+  add_index "merge_requests_closing_issues", ["issue_id"], name: "index_merge_requests_closing_issues_on_issue_id", using: :btree
+  add_index "merge_requests_closing_issues", ["merge_request_id"], name: "index_merge_requests_closing_issues_on_merge_request_id", using: :btree
+
   create_table "milestones", force: :cascade do |t|
     t.string   "title",       null: false
     t.integer  "project_id",  null: false
@@ -665,6 +687,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.integer  "visibility_level",       default: 20,    null: false
     t.boolean  "request_access_enabled", default: true,  null: false
     t.datetime "deleted_at"
+    t.boolean  "lfs_enabled"
   end
 
   add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
@@ -772,15 +795,28 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.integer  "user_id",                    null: false
     t.string   "token",                      null: false
     t.string   "name",                       null: false
-    t.datetime "created_at",                 null: false
-    t.datetime "updated_at",                 null: false
     t.boolean  "revoked",    default: false
     t.datetime "expires_at"
+    t.datetime "created_at",                 null: false
+    t.datetime "updated_at",                 null: false
   end
 
   add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree
   add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree
 
+  create_table "project_features", force: :cascade do |t|
+    t.integer  "project_id"
+    t.integer  "merge_requests_access_level"
+    t.integer  "issues_access_level"
+    t.integer  "wiki_access_level"
+    t.integer  "snippets_access_level"
+    t.integer  "builds_access_level"
+    t.datetime "created_at"
+    t.datetime "updated_at"
+  end
+
+  add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree
+
   create_table "project_group_links", force: :cascade do |t|
     t.integer  "project_id",                null: false
     t.integer  "group_id",                  null: false
@@ -805,11 +841,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.datetime "created_at"
     t.datetime "updated_at"
     t.integer  "creator_id"
-    t.boolean  "issues_enabled",                     default: true,      null: false
-    t.boolean  "merge_requests_enabled",             default: true,      null: false
-    t.boolean  "wiki_enabled",                       default: true,      null: false
     t.integer  "namespace_id"
-    t.boolean  "snippets_enabled",                   default: true,      null: false
     t.datetime "last_activity_at"
     t.string   "import_url"
     t.integer  "visibility_level",                   default: 0,         null: false
@@ -823,7 +855,6 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.integer  "commit_count",                       default: 0
     t.text     "import_error"
     t.integer  "ci_id"
-    t.boolean  "builds_enabled",                     default: true,      null: false
     t.boolean  "shared_runners_enabled",             default: true,      null: false
     t.string   "runners_token"
     t.string   "build_coverage_regex"
@@ -831,15 +862,15 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.integer  "build_timeout",                      default: 3600,      null: false
     t.boolean  "pending_delete",                     default: false
     t.boolean  "public_builds",                      default: true,      null: false
-    t.integer  "pushes_since_gc",                    default: 0
     t.boolean  "last_repository_check_failed"
     t.datetime "last_repository_check_at"
     t.boolean  "container_registry_enabled"
     t.boolean  "only_allow_merge_if_build_succeeds", default: false,     null: false
     t.boolean  "has_external_issue_tracker"
     t.string   "repository_storage",                 default: "default", null: false
-    t.boolean  "has_external_wiki"
     t.boolean  "request_access_enabled",             default: true,      null: false
+    t.boolean  "has_external_wiki"
+    t.boolean  "lfs_enabled"
   end
 
   add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
@@ -915,19 +946,20 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.integer  "project_id"
     t.datetime "created_at"
     t.datetime "updated_at"
-    t.boolean  "active",                default: false,    null: false
+    t.boolean  "active",                     default: false,    null: false
     t.text     "properties"
-    t.boolean  "template",              default: false
-    t.boolean  "push_events",           default: true
-    t.boolean  "issues_events",         default: true
-    t.boolean  "merge_requests_events", default: true
-    t.boolean  "tag_push_events",       default: true
-    t.boolean  "note_events",           default: true,     null: false
-    t.boolean  "build_events",          default: false,    null: false
-    t.string   "category",              default: "common", null: false
-    t.boolean  "default",               default: false
-    t.boolean  "wiki_page_events",      default: true
-    t.boolean  "pipeline_events",       default: false,    null: false
+    t.boolean  "template",                   default: false
+    t.boolean  "push_events",                default: true
+    t.boolean  "issues_events",              default: true
+    t.boolean  "merge_requests_events",      default: true
+    t.boolean  "tag_push_events",            default: true
+    t.boolean  "note_events",                default: true,     null: false
+    t.boolean  "build_events",               default: false,    null: false
+    t.string   "category",                   default: "common", null: false
+    t.boolean  "default",                    default: false
+    t.boolean  "wiki_page_events",           default: true
+    t.boolean  "pipeline_events",            default: false,    null: false
+    t.boolean  "confidential_issues_events", default: true,     null: false
   end
 
   add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
@@ -960,8 +992,8 @@ ActiveRecord::Schema.define(version: 20160819221833) do
     t.string   "noteable_type"
     t.string   "title"
     t.text     "description"
-    t.datetime "created_at",    null: false
-    t.datetime "updated_at",    null: false
+    t.datetime "created_at",                       null: false
+    t.datetime "updated_at",                       null: false
     t.boolean  "submitted_as_ham", default: false, null: false
   end
 
@@ -1032,13 +1064,13 @@ ActiveRecord::Schema.define(version: 20160819221833) do
   add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree
 
   create_table "user_agent_details", force: :cascade do |t|
-    t.string   "user_agent",   null: false
-    t.string   "ip_address",   null: false
-    t.integer  "subject_id",   null: false
-    t.string   "subject_type", null: false
+    t.string   "user_agent",                   null: false
+    t.string   "ip_address",                   null: false
+    t.integer  "subject_id",                   null: false
+    t.string   "subject_type",                 null: false
     t.boolean  "submitted",    default: false, null: false
-    t.datetime "created_at",   null: false
-    t.datetime "updated_at",   null: false
+    t.datetime "created_at",                   null: false
+    t.datetime "updated_at",                   null: false
   end
 
   create_table "users", force: :cascade do |t|
@@ -1127,31 +1159,36 @@ ActiveRecord::Schema.define(version: 20160819221833) do
   add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree
 
   create_table "web_hooks", force: :cascade do |t|
-    t.string   "url",                     limit: 2000
+    t.string   "url",                        limit: 2000
     t.integer  "project_id"
     t.datetime "created_at"
     t.datetime "updated_at"
-    t.string   "type",                                 default: "ProjectHook"
+    t.string   "type",                                    default: "ProjectHook"
     t.integer  "service_id"
-    t.boolean  "push_events",                          default: true,          null: false
-    t.boolean  "issues_events",                        default: false,         null: false
-    t.boolean  "merge_requests_events",                default: false,         null: false
-    t.boolean  "tag_push_events",                      default: false
-    t.boolean  "note_events",                          default: false,         null: false
-    t.boolean  "enable_ssl_verification",              default: true
-    t.boolean  "build_events",                         default: false,         null: false
-    t.boolean  "wiki_page_events",                     default: false,         null: false
+    t.boolean  "push_events",                             default: true,          null: false
+    t.boolean  "issues_events",                           default: false,         null: false
+    t.boolean  "merge_requests_events",                   default: false,         null: false
+    t.boolean  "tag_push_events",                         default: false
+    t.boolean  "note_events",                             default: false,         null: false
+    t.boolean  "enable_ssl_verification",                 default: true
+    t.boolean  "build_events",                            default: false,         null: false
+    t.boolean  "wiki_page_events",                        default: false,         null: false
     t.string   "token"
-    t.boolean  "pipeline_events",                      default: false,         null: false
+    t.boolean  "pipeline_events",                         default: false,         null: false
+    t.boolean  "confidential_issues_events",              default: false,         null: false
   end
 
   add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
 
   add_foreign_key "boards", "projects"
+  add_foreign_key "issue_metrics", "issues", on_delete: :cascade
   add_foreign_key "lists", "boards"
   add_foreign_key "lists", "labels"
+  add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade
+  add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade
+  add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade
   add_foreign_key "personal_access_tokens", "users"
   add_foreign_key "protected_branch_merge_access_levels", "protected_branches"
   add_foreign_key "protected_branch_push_access_levels", "protected_branches"
   add_foreign_key "u2f_registrations", "users"
-end
\ No newline at end of file
+end
diff --git a/doc/README.md b/doc/README.md
index 452cf1c..dd0eb97 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -2,6 +2,7 @@
 
 ## User documentation
 
+- [Account Security](user/account/security.md) Securing your account via two-factor authentication, etc.
 - [API](api/README.md) Automate GitLab via a simple and powerful API.
 - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples.
 - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
@@ -28,9 +29,9 @@
 - [Install](install/README.md) Requirements, directory structures and installation from source.
 - [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components.
 - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter.
-- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
+- [Issue closing pattern](administration/issue_closing_pattern.md) Customize how to close an issue from commit messages.
 - [Koding](administration/integration/koding.md) Set up Koding to use with GitLab.
-- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
+- [Libravatar](customization/libravatar.md) Use Libravatar instead of Gravatar for user avatars.
 - [Log system](administration/logs.md) Log system.
 - [Environment Variables](administration/environment_variables.md) to configure GitLab.
 - [Operations](operations/README.md) Keeping GitLab up and running.
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 28c4c7c..c5611e2 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -406,7 +406,8 @@ To configure the storage driver in Omnibus:
       's3' => {
         'accesskey' => 's3-access-key',
         'secretkey' => 's3-secret-key-for-access-key',
-        'bucket' => 'your-s3-bucket'
+        'bucket' => 'your-s3-bucket',
+        'region' => 'your-s3-region'
       }
     }
     ```
@@ -428,6 +429,7 @@ storage:
     accesskey: 'AKIAKIAKI'
     secretkey: 'secret123'
     bucket: 'gitlab-registry-bucket-AKIAKIAKI'
+    region: 'your-s3-region'
   cache:
     blobdescriptor: inmemory
   delete:
diff --git a/doc/administration/issue_closing_pattern.md b/doc/administration/issue_closing_pattern.md
new file mode 100644
index 0000000..28e1fd4
--- /dev/null
+++ b/doc/administration/issue_closing_pattern.md
@@ -0,0 +1,49 @@
+# Issue closing pattern
+
+>**Note:**
+This is the administration documentation.
+There is a separate [user documentation] on issue closing pattern.
+
+When a commit or merge request resolves one or more issues, it is possible to
+automatically have these issues closed when the commit or merge request lands
+in the project's default branch.
+
+## Change the issue closing pattern
+
+In order to change the pattern you need to have access to the server that GitLab
+is installed on.
+
+The default pattern can be located in [gitlab.yml.example] under the
+"Automatic issue closing" section.
+
+> **Tip:**
+You are advised to use http://rubular.com to test the issue closing pattern.
+Because Rubular doesn't understand `%{issue_ref}`, you can replace this by
+`#\d+` when testing your patterns, which matches only local issue references like `#123`.
+
+**For Omnibus installations**
+
+1. Open `/etc/gitlab/gitlab.rb` with your editor.
+1. Change the value of `gitlab_rails['issue_closing_pattern']` to a regular
+   expression of your liking:
+
+    ```ruby
+    gitlab_rails['issue_closing_pattern'] = "((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
+    ```
+1. [Reconfigure] GitLab for the changes to take effect.
+
+**For installations from source**
+
+1. Open `gitlab.yml` with your editor.
+1. Change the value of `issue_closing_pattern`:
+
+    ```yaml
+    issue_closing_pattern: "((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
+    ```
+
+1. [Restart] GitLab for the changes to take effect.
+
+[gitlab.yml.example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
+[reconfigure]: restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart]: restart_gitlab.md#installations-from-source
+[user documentation]: ../user/project/issues/automatic_issue_closing.md
diff --git a/doc/api/README.md b/doc/api/README.md
index 3e79cce..8e4f7f1 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -10,10 +10,12 @@ following locations:
 
 - [Award Emoji](award_emoji.md)
 - [Branches](branches.md)
+- [Broadcast Messages](broadcast_messages.md)
 - [Builds](builds.md)
-- [Build triggers](build_triggers.md)
+- [Build Triggers](build_triggers.md)
 - [Build Variables](build_variables.md)
 - [Commits](commits.md)
+- [Deployments](deployments.md)
 - [Deploy Keys](deploy_keys.md)
 - [Groups](groups.md)
 - [Group Access Requests](access_requests.md)
@@ -26,6 +28,7 @@ following locations:
 - [Open source license templates](licenses.md)
 - [Namespaces](namespaces.md)
 - [Notes](notes.md) (comments)
+- [Notification settings](notification_settings.md)
 - [Pipelines](pipelines.md)
 - [Projects](projects.md) including setting Webhooks
 - [Project Access Requests](access_requests.md)
@@ -40,8 +43,9 @@ following locations:
 - [Sidekiq metrics](sidekiq_metrics.md)
 - [System Hooks](system_hooks.md)
 - [Tags](tags.md)
-- [Users](users.md)
 - [Todos](todos.md)
+- [Users](users.md)
+- [Validate CI configuration](ci/lint.md)
 
 ### Internal CI API
 
@@ -52,11 +56,12 @@ The following documentation is for the [internal CI API](ci/README.md):
 
 ## Authentication
 
-All API requests require authentication via a token. There are three types of tokens
-available: private tokens, OAuth 2 tokens, and personal access tokens.
+All API requests require authentication via a session cookie or token. There are
+three types of tokens available: private tokens, OAuth 2 tokens, and personal
+access tokens.
 
-If a token is invalid or omitted, an error message will be returned with
-status code `401`:
+If authentication information is invalid or omitted, an error message will be
+returned with status code `401`:
 
 ```json
 {
@@ -95,6 +100,13 @@ that needs access to the GitLab API.
 Once you have your token, pass it to the API using either the `private_token`
 parameter or the `PRIVATE-TOKEN` header.
 
+
+### Session cookie
+
+When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is
+set. The API will use this cookie for authentication if it is present, but using
+the API to generate a new session cookie is currently not supported.
+
 ## Basic Usage
 
 API requests should be prefixed with `api` and the API version. The API version
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 72ec99b..c464e3f 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -1,12 +1,13 @@
 # Award Emoji
 
-> [Introduced][ce-4575] in GitLab 8.9.
+> [Introduced][ce-4575] in GitLab 8.9, Snippet support in 8.12
+
 
 An awarded emoji tells a thousand words, and can be awarded on issues, merge
-requests and notes/comments. Issues, merge requests and notes are further called
+requests, snippets, and notes/comments. Issues, merge requests, snippets, and notes are further called
 `awardables`.
 
-## Issues and merge requests
+## Issues, merge requests, and snippets
 
 ### List an awardable's award emoji
 
@@ -15,6 +16,7 @@ Gets a list of all award emoji
 ```
 GET /projects/:id/issues/:issue_id/award_emoji
 GET /projects/:id/merge_requests/:merge_request_id/award_emoji
+GET /projects/:id/snippets/:snippet_id/award_emoji
 ```
 
 Parameters:
@@ -69,11 +71,12 @@ Example Response:
 
 ### Get single award emoji
 
-Gets a single award emoji from an issue or merge request.
+Gets a single award emoji from an issue, snippet, or merge request.
 
 ```
 GET /projects/:id/issues/:issue_id/award_emoji/:award_id
 GET /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id
+GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
 ```
 
 Parameters:
@@ -116,6 +119,7 @@ This end point creates an award emoji on the specified resource
 ```
 POST /projects/:id/issues/:issue_id/award_emoji
 POST /projects/:id/merge_requests/:merge_request_id/award_emoji
+POST /projects/:id/snippets/:snippet_id/award_emoji
 ```
 
 Parameters:
@@ -159,6 +163,7 @@ admins or the author of the award. Status code 200 on success, 401 if unauthoriz
 ```
 DELETE /projects/:id/issues/:issue_id/award_emoji/:award_id
 DELETE /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id
+DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
 ```
 
 Parameters:
@@ -197,7 +202,7 @@ Example Response:
 ## Award Emoji on Notes
 
 The endpoints documented above are available for Notes as well. Notes
-are a sub-resource of Issues and Merge Requests. The examples below
+are a sub-resource of Issues, Merge Requests, or Snippets. The examples below
 describe working with Award Emoji on notes for an Issue, but can be
 easily adapted for notes on a Merge Request.
 
diff --git a/doc/api/broadcast_messages.md b/doc/api/broadcast_messages.md
new file mode 100644
index 0000000..c3a9207
--- /dev/null
+++ b/doc/api/broadcast_messages.md
@@ -0,0 +1,158 @@
+# Broadcast Messages
+
+> **Note:** This feature was introduced in GitLab 8.12.
+
+The broadcast message API is only accessible to administrators. All requests by
+guests will respond with `401 Unauthorized`, and all requests by normal users
+will respond with `403 Forbidden`.
+
+## Get all broadcast messages
+
+```
+GET /broadcast_messages
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages
+```
+
+Example response:
+
+```json
+[
+    {
+        "message":"Example broadcast message",
+        "starts_at":"2016-08-24T23:21:16.078Z",
+        "ends_at":"2016-08-26T23:21:16.080Z",
+        "color":"#E75E40",
+        "font":"#FFFFFF",
+        "id":1,
+        "active": false
+    }
+]
+```
+
+## Get a specific broadcast message
+
+```
+GET /broadcast_messages/:id
+```
+
+| Attribute   | Type     | Required | Description               |
+| ----------- | -------- | -------- | ------------------------- |
+| `id`        | integer  | yes      | Broadcast message ID      |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1
+```
+
+Example response:
+
+```json
+{
+    "message":"Deploy in progress",
+    "starts_at":"2016-08-24T23:21:16.078Z",
+    "ends_at":"2016-08-26T23:21:16.080Z",
+    "color":"#cecece",
+    "font":"#FFFFFF",
+    "id":1,
+    "active":false
+}
+```
+
+## Create a broadcast message
+
+Responds with `400 Bad request` when the `message` parameter is missing or the
+`color` or `font` values are invalid, and `201 Created` when the broadcast
+message was successfully created.
+
+```
+POST /broadcast_messages
+```
+
+| Attribute   | Type     | Required | Description                                          |
+| ----------- | -------- | -------- | ---------------------------------------------------- |
+| `message`   | string   | yes      | Message to display                                   |
+| `starts_at` | datetime | no       | Starting time (defaults to current time)             |
+| `ends_at`   | datetime | no       | Ending time (defaults to one hour from current time) |
+| `color`     | string   | no       | Background color hex code                            |
+| `font`      | string   | no       | Foreground color hex code                            |
+
+```bash
+curl --data "message=Deploy in progress&color=#cecece" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages
+```
+
+Example response:
+
+```json
+{
+    "message":"Deploy in progress",
+    "starts_at":"2016-08-26T00:41:35.060Z",
+    "ends_at":"2016-08-26T01:41:35.060Z",
+    "color":"#cecece",
+    "font":"#FFFFFF",
+    "id":1,
+    "active": true
+}
+```
+
+## Update a broadcast message
+
+```
+PUT /broadcast_messages/:id
+```
+
+| Attribute   | Type     | Required | Description               |
+| ----------- | -------- | -------- | ------------------------- |
+| `id`        | integer  | yes      | Broadcast message ID      |
+| `message`   | string   | no       | Message to display        |
+| `starts_at` | datetime | no       | Starting time             |
+| `ends_at`   | datetime | no       | Ending time               |
+| `color`     | string   | no       | Background color hex code |
+| `font`      | string   | no       | Foreground color hex code |
+
+```bash
+curl --request PUT --data "message=Update message&color=#000" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1
+```
+
+Example response:
+
+```json
+{
+    "message":"Update message",
+    "starts_at":"2016-08-26T00:41:35.060Z",
+    "ends_at":"2016-08-26T01:41:35.060Z",
+    "color":"#000",
+    "font":"#FFFFFF",
+    "id":1,
+    "active": true
+}
+```
+
+## Delete a broadcast message
+
+```
+DELETE /broadcast_messages/:id
+```
+
+| Attribute   | Type     | Required | Description               |
+| ----------- | -------- | -------- | ------------------------- |
+| `id`        | integer  | yes      | Broadcast message ID      |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1
+```
+
+Example response:
+
+```json
+{
+    "message":"Update message",
+    "starts_at":"2016-08-26T00:41:35.060Z",
+    "ends_at":"2016-08-26T01:41:35.060Z",
+    "color":"#000",
+    "font":"#FFFFFF",
+    "id":1,
+    "active": true
+}
+```
diff --git a/doc/api/ci/builds.md b/doc/api/ci/builds.md
index 2a71b08..b6d7970 100644
--- a/doc/api/ci/builds.md
+++ b/doc/api/ci/builds.md
@@ -38,6 +38,15 @@ POST /ci/api/v1/builds/register
 curl --request POST "https://gitlab.example.com/ci/api/v1/builds/register" --form "token=t0k3n"
 ```
 
+**Responses:**
+
+| Status | Data |Description                                                                |
+|--------|------|---------------------------------------------------------------------------|
+| `201`  | yes  | When a build is scheduled for a runner                                    |
+| `204`  | no   | When no builds are scheduled for a runner (for GitLab Runner >= `v1.3.0`) |
+| `403`  | no   | When invalid token is used or no token is sent                            |
+| `404`  | no   | When no builds are scheduled for a runner (for GitLab Runner < `v1.3.0`) **or** when the runner is set to `paused` in GitLab runner's configuration page |
+
 ### Update details of an existing build
 
 ```
diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md
new file mode 100644
index 0000000..0c96b3e
--- /dev/null
+++ b/doc/api/ci/lint.md
@@ -0,0 +1,49 @@
+# Validate the .gitlab-ci.yml
+
+> [Introduced][ce-5953] in GitLab 8.12.
+
+Checks if your .gitlab-ci.yml file is valid.
+
+```
+POST ci/lint
+```
+
+| Attribute  | Type    | Required | Description |
+| ---------- | ------- | -------- | -------- |
+| `content`  | string    | yes      | the .gitlab-ci.yaml content|
+
+```bash
+curl --header "Content-Type: application/json" https://gitlab.example.com/api/v3/ci/lint --data '{"content": "{ \"image\": \"ruby:2.1\", \"services\": [\"postgres\"], \"before_script\": [\"gem install bundler\", \"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}'
+```
+
+Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky about indentation and spaces.
+
+Example responses:
+
+* Valid content:
+
+    ```json
+    {
+      "status": "valid",
+      "errors": []
+    }
+    ```
+
+* Invalid content:
+
+    ```json
+    {
+      "status": "invalid",
+      "errors": [
+        "variables config should be a hash of key value pairs"
+      ]
+    }
+    ```
+
+* Without the content attribute:
+
+    ```json
+    {
+      "error": "content is missing"
+    }
+    ```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 5c98c5d..682151d 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -10,7 +10,7 @@ GET /projects/:id/repository/commits
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id`      | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
 | `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
 | `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
 | `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
@@ -58,7 +58,7 @@ Parameters:
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id`      | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
 | `sha` | string | yes | The commit hash or name of a repository branch or tag |
 
 ```bash
@@ -102,7 +102,7 @@ Parameters:
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id`      | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
 | `sha` | string | yes | The commit hash or name of a repository branch or tag |
 
 ```bash
@@ -138,7 +138,7 @@ Parameters:
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id`      | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
 | `sha` | string | yes | The commit hash or name of a repository branch or tag |
 
 ```bash
@@ -187,7 +187,7 @@ POST /projects/:id/repository/commits/:sha/comments
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id`        | integer | yes | The ID of a project |
+| `id`      | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
 | `sha`       | string  | yes | The commit SHA or name of a repository branch or tag |
 | `note`      | string  | yes | The text of the comment |
 | `path`      | string  | no  | The file path relative to the repository |
@@ -232,7 +232,7 @@ GET /projects/:id/repository/commits/:sha/statuses
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id`      | integer | yes | The ID of a project
+| `id`      | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
 | `sha`     | string  | yes | The commit SHA
 | `ref_name`| string  | no  | The name of a repository branch or tag or, if not given, the default branch
 | `stage`   | string  | no  | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
@@ -306,7 +306,7 @@ POST /projects/:id/statuses/:sha
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id`      | integer | yes   | The ID of a project
+| `id`      | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
 | `sha`     | string  | yes   | The commit SHA
 | `state`   | string  | yes   | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled`
 | `ref`     | string  | no    | The `ref` (branch or tag) to which the status refers
diff --git a/doc/api/groups.md b/doc/api/groups.md
index a898387..e81d6f9 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -84,7 +84,8 @@ Parameters:
     "forks_count": 0,
     "open_issues_count": 3,
     "public_builds": true,
-    "shared_with_groups": []
+    "shared_with_groups": [],
+    "request_access_enabled": false
   }
 ]
 ```
@@ -118,6 +119,7 @@ Example response:
   "visibility_level": 20,
   "avatar_url": null,
   "web_url": "https://gitlab.example.com/groups/twitter",
+  "request_access_enabled": false,
   "projects": [
     {
       "id": 7,
@@ -163,7 +165,8 @@ Example response:
       "forks_count": 0,
       "open_issues_count": 3,
       "public_builds": true,
-      "shared_with_groups": []
+      "shared_with_groups": [],
+      "request_access_enabled": false
     },
     {
       "id": 6,
@@ -209,7 +212,8 @@ Example response:
       "forks_count": 0,
       "open_issues_count": 8,
       "public_builds": true,
-      "shared_with_groups": []
+      "shared_with_groups": [],
+      "request_access_enabled": false
     }
   ],
   "shared_projects": [
@@ -288,6 +292,8 @@ Parameters:
 - `path` (required) - The path of the group
 - `description` (optional) - The group's description
 - `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public.
+- `lfs_enabled` (optional)      - Enable/disable Large File Storage (LFS) for the projects in this group
+- `request_access_enabled` (optional) - Allow users to request member access.
 
 ## Transfer project to group
 
@@ -317,6 +323,8 @@ PUT /groups/:id
 | `path` | string | no | The path of the group |
 | `description` | string | no | The description of the group |
 | `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. |
+| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
+| `request_access_enabled` | boolean | no | Allow users to request member access. |
 
 ```bash
 curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental"
@@ -334,6 +342,7 @@ Example response:
   "visibility_level": 10,
   "avatar_url": null,
   "web_url": "http://gitlab.example.com/groups/h5bp",
+  "request_access_enabled": false,
   "projects": [
     {
       "id": 9,
@@ -378,7 +387,8 @@ Example response:
       "forks_count": 0,
       "open_issues_count": 3,
       "public_builds": true,
-      "shared_with_groups": []
+      "shared_with_groups": [],
+      "request_access_enabled": false
     }
   ]
 }
diff --git a/doc/api/issues.md b/doc/api/issues.md
index a665645..eed0d2f 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -79,7 +79,9 @@ Example response:
       "labels" : [],
       "subscribed" : false,
       "user_notes_count": 1,
-      "due_date": "2016-07-22"
+      "due_date": "2016-07-22",
+      "web_url": "http://example.com/example/example/issues/6",
+      "confidential": false
    }
 ]
 ```
@@ -156,7 +158,9 @@ Example response:
       "created_at" : "2016-01-04T15:31:46.176Z",
       "subscribed" : false,
       "user_notes_count": 1,
-      "due_date": null
+      "due_date": null,
+      "web_url": "http://example.com/example/example/issues/1",
+      "confidential": false
    }
 ]
 ```
@@ -235,7 +239,9 @@ Example response:
       "created_at" : "2016-01-04T15:31:46.176Z",
       "subscribed" : false,
       "user_notes_count": 1,
-      "due_date": "2016-07-22"
+      "due_date": "2016-07-22",
+      "web_url": "http://example.com/example/example/issues/1",
+      "confidential": false
    }
 ]
 ```
@@ -299,7 +305,9 @@ Example response:
    "created_at" : "2016-01-04T15:31:46.176Z",
    "subscribed": false,
    "user_notes_count": 1,
-   "due_date": null
+   "due_date": null,
+   "web_url": "http://example.com/example/example/issues/1",
+   "confidential": false
 }
 ```
 
@@ -320,11 +328,12 @@ POST /projects/:id/issues
 | `id`            | integer | yes | The ID of a project |
 | `title`         | string  | yes | The title of an issue |
 | `description`   | string  | no  | The description of an issue  |
+| `confidential`  | boolean | no  | Set an issue to be confidential. Default is `false`.  |
 | `assignee_id`   | integer | no  | The ID of a user to assign issue |
 | `milestone_id`  | integer | no  | The ID of a milestone to assign issue |
 | `labels`        | string  | no  | Comma-separated label names for an issue  |
-| `created_at`    | string  | no  | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
-| `due_date`      | string  | no   | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
+| `created_at`    | string  | no  | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
+| `due_date`      | string  | no  | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
 
 ```bash
 curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
@@ -357,7 +366,9 @@ Example response:
    "milestone" : null,
    "subscribed" : true,
    "user_notes_count": 0,
-   "due_date": null
+   "due_date": null,
+   "web_url": "http://example.com/example/example/issues/14",
+   "confidential": false
 }
 ```
 
@@ -380,12 +391,13 @@ PUT /projects/:id/issues/:issue_id
 | `issue_id`      | integer | yes | The ID of a project's issue |
 | `title`         | string  | no  | The title of an issue |
 | `description`   | string  | no  | The description of an issue  |
+| `confidential`  | boolean | no  | Updates an issue to be confidential |
 | `assignee_id`   | integer | no  | The ID of a user to assign the issue to |
 | `milestone_id`  | integer | no  | The ID of a milestone to assign the issue to |
 | `labels`        | string  | no  | Comma-separated label names for an issue  |
 | `state_event`   | string  | no  | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
-| `updated_at`    | string  | no  | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
-| `due_date`      | string  | no   | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
+| `updated_at`    | string  | no  | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
+| `due_date`      | string  | no  | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
 
 ```bash
 curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close
@@ -418,7 +430,9 @@ Example response:
    "milestone" : null,
    "subscribed" : true,
    "user_notes_count": 0,
-   "due_date": "2016-07-22"
+   "due_date": "2016-07-22",
+   "web_url": "http://example.com/example/example/issues/15",
+   "confidential": false
 }
 ```
 
@@ -496,7 +510,9 @@ Example response:
     "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
     "web_url": "https://gitlab.example.com/u/solon.cremin"
   },
-  "due_date": null
+  "due_date": null,
+  "web_url": "http://example.com/example/example/issues/11",
+  "confidential": false
 }
 ```
 
@@ -551,7 +567,9 @@ Example response:
     "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
     "web_url": "https://gitlab.example.com/u/solon.cremin"
   },
-  "due_date": null
+  "due_date": null,
+  "web_url": "http://example.com/example/example/issues/11",
+  "confidential": false
 }
 ```
 
@@ -607,7 +625,9 @@ Example response:
     "web_url": "https://gitlab.example.com/u/orville"
   },
   "subscribed": false,
-  "due_date": null
+  "due_date": null,
+  "web_url": "http://example.com/example/example/issues/12",
+  "confidential": false
 }
 ```
 
@@ -693,7 +713,10 @@ Example response:
     "subscribed": true,
     "user_notes_count": 7,
     "upvotes": 0,
-    "downvotes": 0
+    "downvotes": 0,
+    "due_date": null,
+    "web_url": "http://example.com/example/example/issues/110",
+    "confidential": false
   },
   "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10",
   "body": "Vel voluptas atque dicta mollitia adipisci qui at.",
diff --git a/doc/api/members.md b/doc/api/members.md
index fd6d728..6535e9a 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -110,8 +110,8 @@ POST /projects/:id/members
 | `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
 
 ```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id?access_level=30
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id?access_level=30
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v3/groups/:id/members
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v3/projects/:id/members
 ```
 
 Example response:
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 3e88a75..494040a 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -68,9 +68,12 @@ Parameters:
     "merge_when_build_succeeds": true,
     "merge_status": "can_be_merged",
     "subscribed" : false,
+    "sha": "8888888888888888888888888888888888888888",
+    "merge_commit_sha": null,
     "user_notes_count": 1,
     "should_remove_source_branch": true,
-    "force_remove_source_branch": false
+    "force_remove_source_branch": false,
+    "web_url": "http://example.com/example/example/merge_requests/1"
   }
 ]
 ```
@@ -134,9 +137,12 @@ Parameters:
   "merge_when_build_succeeds": true,
   "merge_status": "can_be_merged",
   "subscribed" : true,
+  "sha": "8888888888888888888888888888888888888888",
+  "merge_commit_sha": "9999999999999999999999999999999999999999",
   "user_notes_count": 1,
   "should_remove_source_branch": true,
-  "force_remove_source_branch": false
+  "force_remove_source_branch": false,
+  "web_url": "http://example.com/example/example/merge_requests/1"
 }
 ```
 
@@ -236,9 +242,12 @@ Parameters:
   "merge_when_build_succeeds": true,
   "merge_status": "can_be_merged",
   "subscribed" : true,
+  "sha": "8888888888888888888888888888888888888888",
+  "merge_commit_sha": null,
   "user_notes_count": 1,
   "should_remove_source_branch": true,
   "force_remove_source_branch": false,
+  "web_url": "http://example.com/example/example/merge_requests/1",
   "changes": [
     {
     "old_path": "VERSION",
@@ -319,9 +328,12 @@ Parameters:
   "merge_when_build_succeeds": true,
   "merge_status": "can_be_merged",
   "subscribed" : true,
+  "sha": "8888888888888888888888888888888888888888",
+  "merge_commit_sha": null,
   "user_notes_count": 0,
   "should_remove_source_branch": true,
-  "force_remove_source_branch": false
+  "force_remove_source_branch": false,
+  "web_url": "http://example.com/example/example/merge_requests/1"
 }
 ```
 
@@ -393,9 +405,12 @@ Parameters:
   "merge_when_build_succeeds": true,
   "merge_status": "can_be_merged",
   "subscribed" : true,
+  "sha": "8888888888888888888888888888888888888888",
+  "merge_commit_sha": null,
   "user_notes_count": 1,
   "should_remove_source_branch": true,
-  "force_remove_source_branch": false
+  "force_remove_source_branch": false,
+  "web_url": "http://example.com/example/example/merge_requests/1"
 }
 ```
 
@@ -494,9 +509,12 @@ Parameters:
   "merge_when_build_succeeds": true,
   "merge_status": "can_be_merged",
   "subscribed" : true,
+  "sha": "8888888888888888888888888888888888888888",
+  "merge_commit_sha": "9999999999999999999999999999999999999999",
   "user_notes_count": 1,
   "should_remove_source_branch": true,
-  "force_remove_source_branch": false
+  "force_remove_source_branch": false,
+  "web_url": "http://example.com/example/example/merge_requests/1"
 }
 ```
 
@@ -563,9 +581,12 @@ Parameters:
   "merge_when_build_succeeds": true,
   "merge_status": "can_be_merged",
   "subscribed" : true,
+  "sha": "8888888888888888888888888888888888888888",
+  "merge_commit_sha": null,
   "user_notes_count": 1,
   "should_remove_source_branch": true,
-  "force_remove_source_branch": false
+  "force_remove_source_branch": false,
+  "web_url": "http://example.com/example/example/merge_requests/1"
 }
 ```
 
@@ -717,7 +738,9 @@ Example response:
   },
   "merge_when_build_succeeds": false,
   "merge_status": "cannot_be_merged",
-  "subscribed": true
+  "subscribed": true,
+  "sha": "8888888888888888888888888888888888888888",
+  "merge_commit_sha": null
 }
 ```
 
@@ -791,7 +814,9 @@ Example response:
   },
   "merge_when_build_succeeds": false,
   "merge_status": "cannot_be_merged",
-  "subscribed": false
+  "subscribed": false,
+  "sha": "8888888888888888888888888888888888888888",
+  "merge_commit_sha": null
 }
 ```
 
@@ -884,9 +909,12 @@ Example response:
     "merge_when_build_succeeds": false,
     "merge_status": "unchecked",
     "subscribed": true,
+    "sha": "8888888888888888888888888888888888888888",
+    "merge_commit_sha": null,
     "user_notes_count": 7,
     "should_remove_source_branch": true,
-    "force_remove_source_branch": false
+    "force_remove_source_branch": false,
+    "web_url": "http://example.com/example/example/merge_requests/1"
   },
   "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7",
   "body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.",
@@ -894,3 +922,112 @@ Example response:
   "created_at": "2016-07-01T11:14:15.530Z"
 }
 ```
+
+## Get MR diff versions
+
+Get a list of merge request diff versions.
+
+```
+GET /projects/:id/merge_requests/:merge_request_id/versions
+```
+
+| Attribute | Type    | Required | Description           |
+| --------- | ------- | -------- | --------------------- |
+| `id`      | String  | yes      | The ID of the project |
+| `merge_request_id` | integer | yes | The ID of the merge request |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions
+```
+
+Example response:
+
+```json
+[{
+  "id": 110,
+  "head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30",
+  "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
+  "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
+  "created_at": "2016-07-26T14:44:48.926Z",
+  "merge_request_id": 105,
+  "state": "collected",
+  "real_size": "1"
+}, {
+  "id": 108,
+  "head_commit_sha": "3eed087b29835c48015768f839d76e5ea8f07a24",
+  "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
+  "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
+  "created_at": "2016-07-25T14:21:33.028Z",
+  "merge_request_id": 105,
+  "state": "collected",
+  "real_size": "1"
+}]
+```
+
+## Get a single MR diff version
+
+Get a single merge request diff version.
+
+```
+GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id
+```
+
+| Attribute | Type    | Required | Description           |
+| --------- | ------- | -------- | --------------------- |
+| `id`      | String  | yes      | The ID of the project |
+| `merge_request_id` | integer | yes | The ID of the merge request |
+| `version_id` | integer | yes | The ID of the merge request diff version |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions/1
+```
+
+Example response:
+
+```json
+{
+  "id": 110,
+  "head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30",
+  "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
+  "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd",
+  "created_at": "2016-07-26T14:44:48.926Z",
+  "merge_request_id": 105,
+  "state": "collected",
+  "real_size": "1",
+  "commits": [{
+    "id": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30",
+    "short_id": "33e2ee85",
+    "title": "Change year to 2018",
+    "author_name": "Administrator",
+    "author_email": "admin at example.com",
+    "created_at": "2016-07-26T17:44:29.000+03:00",
+    "message": "Change year to 2018"
+  }, {
+    "id": "aa24655de48b36335556ac8a3cd8bb521f977cbd",
+    "short_id": "aa24655d",
+    "title": "Update LICENSE",
+    "author_name": "Administrator",
+    "author_email": "admin at example.com",
+    "created_at": "2016-07-25T17:21:53.000+03:00",
+    "message": "Update LICENSE"
+  }, {
+    "id": "3eed087b29835c48015768f839d76e5ea8f07a24",
+    "short_id": "3eed087b",
+    "title": "Add license",
+    "author_name": "Administrator",
+    "author_email": "admin at example.com",
+    "created_at": "2016-07-25T17:21:20.000+03:00",
+    "message": "Add license"
+  }],
+  "diffs": [{
+    "old_path": "LICENSE",
+    "new_path": "LICENSE",
+    "a_mode": "0",
+    "b_mode": "100644",
+    "diff": "--- /dev/null\n+++ b/LICENSE\n@@ -0,0 +1,21 @@\n+The MIT License (MIT)\n+\n+Copyright (c) 2018 Administrator\n+\n+Permission is hereby granted, free of charge, to any person obtaining a copy\n+of this software and associated documentation files (the \"Software\"), to deal\n+in the Software without restriction, including without limitation the rights\n+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+copies of the Software, and to permit persons to  [...]
+    "new_file": true,
+    "renamed_file": false,
+    "deleted_file": false
+  }]
+}
+```
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 85d140d..572844b 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -78,7 +78,8 @@ Parameters:
 
 ### Create new issue note
 
-Creates a new note to a single project issue.
+Creates a new note to a single project issue. If you create a note where the body
+only contains an Award Emoji, you'll receive this object back.
 
 ```
 POST /projects/:id/issues/:issue_id/notes
@@ -204,6 +205,7 @@ Parameters:
 ### Create new snippet note
 
 Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet.
+If you create a note where the body only contains an Award Emoji, you'll receive this object back.
 
 ```
 POST /projects/:id/snippets/:snippet_id/notes
@@ -332,6 +334,8 @@ Parameters:
 ### Create new merge request note
 
 Creates a new note for a single merge request.
+If you create a note where the body only contains an Award Emoji, you'll receive
+this object back.
 
 ```
 POST /projects/:id/merge_requests/:merge_request_id/notes
diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md
new file mode 100644
index 0000000..ff6c9e4
--- /dev/null
+++ b/doc/api/notification_settings.md
@@ -0,0 +1,169 @@
+# Notification settings
+
+>**Note:** This feature was [introduced][ce-5632] in GitLab 8.12.
+
+**Valid notification levels**
+
+The notification levels are defined in the `NotificationSetting::level` model enumeration. Currently, these levels are recognized:
+
+```
+disabled
+participating
+watch
+global
+mention
+custom
+```
+
+If the `custom` level is used, specific email events can be controlled. Notification email events are defined in the `NotificationSetting::EMAIL_EVENTS` model variable. Currently, these events are recognized:
+
+```
+new_note
+new_issue
+reopen_issue
+close_issue
+reassign_issue
+new_merge_request
+reopen_merge_request
+close_merge_request
+reassign_merge_request
+merge_merge_request
+```
+
+## Global notification settings
+
+Get current notification settings and email address.
+
+```
+GET /notification_settings
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings
+```
+
+Example response:
+
+```json
+{
+  "level": "participating",
+  "notification_email": "admin at example.com"
+}
+```
+
+## Update global notification settings
+
+Update current notification settings and email address.
+
+```
+PUT /notification_settings
+```
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings?level=watch
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `level` | string | no | The global notification level |
+| `notification_email` | string | no | The email address to send notifications |
+| `new_note` | boolean | no | Enable/disable this notification |
+| `new_issue` | boolean | no | Enable/disable this notification |
+| `reopen_issue` | boolean | no | Enable/disable this notification |
+| `close_issue` | boolean | no | Enable/disable this notification |
+| `reassign_issue` | boolean | no | Enable/disable this notification |
+| `new_merge_request` | boolean | no | Enable/disable this notification |
+| `reopen_merge_request` | boolean | no | Enable/disable this notification |
+| `close_merge_request` | boolean | no | Enable/disable this notification |
+| `reassign_merge_request` | boolean | no | Enable/disable this notification |
+| `merge_merge_request` | boolean | no | Enable/disable this notification |
+
+Example response:
+
+```json
+{
+  "level": "watch",
+  "notification_email": "admin at example.com"
+}
+```
+
+## Group / project level notification settings
+
+Get current group or project notification settings.
+
+```
+GET /groups/:id/notification_settings
+GET /projects/:id/notification_settings
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+
+Example response:
+
+```json
+{
+  "level": "global"
+}
+```
+
+## Update group/project level notification settings
+
+Update current group/project notification settings.
+
+```
+PUT /groups/:id/notification_settings
+PUT /projects/:id/notification_settings
+```
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings?level=watch
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings?level=custom&new_note=true
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+| `level` | string | no | The global notification level |
+| `new_note` | boolean | no | Enable/disable this notification |
+| `new_issue` | boolean | no | Enable/disable this notification |
+| `reopen_issue` | boolean | no | Enable/disable this notification |
+| `close_issue` | boolean | no | Enable/disable this notification |
+| `reassign_issue` | boolean | no | Enable/disable this notification |
+| `new_merge_request` | boolean | no | Enable/disable this notification |
+| `reopen_merge_request` | boolean | no | Enable/disable this notification |
+| `close_merge_request` | boolean | no | Enable/disable this notification |
+| `reassign_merge_request` | boolean | no | Enable/disable this notification |
+| `merge_merge_request` | boolean | no | Enable/disable this notification |
+
+Example responses:
+
+```json
+{
+  "level": "watch"
+}
+
+{
+  "level": "custom",
+  "events": {
+    "new_note": true,
+    "new_issue": false,
+    "reopen_issue": false,
+    "close_issue": false,
+    "reassign_issue": false,
+    "new_merge_request": false,
+    "reopen_merge_request": false,
+    "close_merge_request": false,
+    "reassign_merge_request": false,
+    "merge_merge_request": false
+  }
+}
+```
+
+[ce-5632]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5632
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index a7acf37..c6685f5 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -53,7 +53,8 @@ Parameters:
   },
   "expires_at": null,
   "updated_at": "2012-06-28T10:52:04Z",
-  "created_at": "2012-06-28T10:52:04Z"
+  "created_at": "2012-06-28T10:52:04Z",
+  "web_url": "http://example.com/example/example/snippets/1"
 }
 ```
 
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 37d97b2..750ce15 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -84,7 +84,9 @@ Parameters:
     "star_count": 0,
     "runners_token": "b8547b1dc37721d05889db52fa2f02",
     "public_builds": true,
-    "shared_with_groups": []
+    "shared_with_groups": [],
+    "only_allow_merge_if_build_succeeds": false,
+    "request_access_enabled": false
   },
   {
     "id": 6,
@@ -144,7 +146,9 @@ Parameters:
     "star_count": 0,
     "runners_token": "b8547b1dc37721d05889db52fa2f02",
     "public_builds": true,
-    "shared_with_groups": []
+    "shared_with_groups": [],
+    "only_allow_merge_if_build_succeeds": false,
+    "request_access_enabled": false
   }
 ]
 ```
@@ -280,7 +284,9 @@ Parameters:
       "group_name": "Gitlab Org",
       "group_access_level": 10
     }
-  ]
+  ],
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
@@ -448,6 +454,9 @@ Parameters:
 - `visibility_level` (optional)
 - `import_url` (optional)
 - `public_builds` (optional)
+- `only_allow_merge_if_build_succeeds` (optional)
+- `lfs_enabled` (optional)
+- `request_access_enabled` (optional) - Allow users to request member access.
 
 ### Create project for user
 
@@ -473,6 +482,9 @@ Parameters:
 - `visibility_level` (optional)
 - `import_url` (optional)
 - `public_builds` (optional)
+- `only_allow_merge_if_build_succeeds` (optional)
+- `lfs_enabled` (optional)
+- `request_access_enabled` (optional) - Allow users to request member access.
 
 ### Edit project
 
@@ -484,7 +496,7 @@ PUT /projects/:id
 
 Parameters:
 
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
 - `name` (optional) - project name
 - `path` (optional) - repository name for project
 - `description` (optional) - short project description
@@ -499,13 +511,16 @@ Parameters:
 - `public` (optional) - if `true` same as setting visibility_level = 20
 - `visibility_level` (optional)
 - `public_builds` (optional)
+- `only_allow_merge_if_build_succeeds` (optional)
+- `lfs_enabled` (optional)
+- `request_access_enabled` (optional) - Allow users to request member access.
 
 On success, method returns 200 with the updated project. If parameters are
 invalid, 400 is returned.
 
 ### Fork project
 
-Forks a project into the user namespace of the authenticated user.
+Forks a project into the user namespace of the authenticated user or the one provided.
 
 ```
 POST /projects/fork/:id
@@ -513,7 +528,8 @@ POST /projects/fork/:id
 
 Parameters:
 
-- `id` (required) - The ID of the project to be forked
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
+- `namespace` (optional) - The ID or path of the namespace that the project will be forked to
 
 ### Star a project
 
@@ -526,7 +542,7 @@ POST /projects/:id/star
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id`      | integer | yes | The ID of the project |
+| `id`      | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
 
 ```bash
 curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
@@ -577,7 +593,9 @@ Example response:
   "forks_count": 0,
   "star_count": 1,
   "public_builds": true,
-  "shared_with_groups": []
+  "shared_with_groups": [],
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
@@ -592,7 +610,7 @@ DELETE /projects/:id/star
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id`      | integer | yes | The ID of the project |
+| `id`      | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
 
 ```bash
 curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
@@ -643,7 +661,9 @@ Example response:
   "forks_count": 0,
   "star_count": 0,
   "public_builds": true,
-  "shared_with_groups": []
+  "shared_with_groups": [],
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
@@ -662,7 +682,7 @@ POST /projects/:id/archive
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id`      | integer | yes | The ID of the project |
+| `id`      | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
 
 ```bash
 curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
@@ -729,7 +749,9 @@ Example response:
   "star_count": 0,
   "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
   "public_builds": true,
-  "shared_with_groups": []
+  "shared_with_groups": [],
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
@@ -748,7 +770,7 @@ POST /projects/:id/unarchive
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `id`      | integer | yes | The ID of the project |
+| `id`      | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
 
 ```bash
 curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
@@ -815,7 +837,9 @@ Example response:
   "star_count": 0,
   "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
   "public_builds": true,
-  "shared_with_groups": []
+  "shared_with_groups": [],
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
@@ -829,7 +853,7 @@ DELETE /projects/:id
 
 Parameters:
 
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
 
 ## Uploads
 
@@ -843,7 +867,7 @@ POST /projects/:id/uploads
 
 Parameters:
 
-- `id` (required) - The ID of the project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
 - `file` (required) - The file to be uploaded
 
 ```json
@@ -872,7 +896,7 @@ POST /projects/:id/share
 
 Parameters:
 
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
 - `group_id` (required) - The ID of a group
 - `group_access` (required) - Level of permissions for sharing
 
@@ -914,7 +938,11 @@ Parameters:
   "push_events": true,
   "issues_events": true,
   "merge_requests_events": true,
+  "tag_push_events": true,
   "note_events": true,
+  "build_events": true,
+  "pipeline_events": true,
+  "wiki_page_events": true,
   "enable_ssl_verification": true,
   "created_at": "2012-10-12T17:04:47Z"
 }
@@ -937,6 +965,9 @@ Parameters:
 - `merge_requests_events` - Trigger hook on merge_requests events
 - `tag_push_events` - Trigger hook on push_tag events
 - `note_events` - Trigger hook on note events
+- `build_events` - Trigger hook on build events
+- `pipeline_events` - Trigger hook on pipeline events
+- `wiki_page_events` - Trigger hook on wiki page events
 - `enable_ssl_verification` - Do SSL verification when triggering the hook
 
 ### Edit project hook
@@ -957,6 +988,9 @@ Parameters:
 - `merge_requests_events` - Trigger hook on merge_requests events
 - `tag_push_events` - Trigger hook on push_tag events
 - `note_events` - Trigger hook on note events
+- `build_events` - Trigger hook on build events
+- `pipeline_events` - Trigger hook on pipeline events
+- `wiki_page_events` - Trigger hook on wiki page events
 - `enable_ssl_verification` - Do SSL verification when triggering the hook
 
 ### Delete project hook
@@ -978,6 +1012,8 @@ is available before it is returned in the JSON response or an empty response is
 
 ## Branches
 
+For more information please consult the [Branches](branches.md) documentation.
+
 ### List branches
 
 Lists all branches of a project.
@@ -996,56 +1032,46 @@ Parameters:
     "name": "async",
     "commit": {
       "id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca",
-      "parents": [
-        {
-          "id": "3f94fc7c85061973edc9906ae170cc269b07ca55"
-        }
+      "parent_ids": [
+        "3f94fc7c85061973edc9906ae170cc269b07ca55"
       ],
-      "tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee",
       "message": "give Caolan credit where it's due (up top)",
-      "author": {
-        "name": "Jeremy Ashkenas",
-        "email": "jashkenas at example.com"
-      },
-      "committer": {
-        "name": "Jeremy Ashkenas",
-        "email": "jashkenas at example.com"
-      },
+      "author_name": "Jeremy Ashkenas",
+      "author_email": "jashkenas at example.com",
       "authored_date": "2010-12-08T21:28:50+00:00",
+      "committer_name": "Jeremy Ashkenas",
+      "committer_email": "jashkenas at example.com",
       "committed_date": "2010-12-08T21:28:50+00:00"
     },
-    "protected": false
+    "protected": false,
+    "developers_can_push": false,
+    "developers_can_merge": false
   },
   {
     "name": "gh-pages",
     "commit": {
       "id": "101c10a60019fe870d21868835f65c25d64968fc",
-      "parents": [
-        {
-          "id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
-        }
+      "parent_ids": [
+          "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
       ],
-      "tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a",
       "message": "Underscore.js 1.5.2",
-      "author": {
-        "name": "Jeremy Ashkenas",
-        "email": "jashkenas at example.com"
-      },
-      "committer": {
-        "name": "Jeremy Ashkenas",
-        "email": "jashkenas at example.com"
-      },
+      "author_name": "Jeremy Ashkenas",
+      "author_email": "jashkenas at example.com",
       "authored_date": "2013-09-07T12:58:21+00:00",
+      "committer_name": "Jeremy Ashkenas",
+      "committer_email": "jashkenas at example.com",
       "committed_date": "2013-09-07T12:58:21+00:00"
     },
-    "protected": false
+    "protected": false,
+    "developers_can_push": false,
+    "developers_can_merge": false
   }
 ]
 ```
 
-### List single branch
+### Single branch
 
-Lists a specific branch of a project.
+A specific branch of a project.
 
 ```
 GET /projects/:id/repository/branches/:branch
@@ -1055,6 +1081,8 @@ Parameters:
 
 - `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
 - `branch` (required) - The name of the branch.
+- `developers_can_push` - Flag if developers can push to the branch.
+- `developers_can_merge` - Flag if developers can merge to the branch.
 
 ### Protect single branch
 
@@ -1094,7 +1122,7 @@ POST /projects/:id/fork/:forked_from_id
 
 Parameters:
 
-- `id` (required) - The ID of the project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
 - `forked_from_id:` (required) - The ID of the project that was forked from
 
 ### Delete an existing forked from relationship
@@ -1105,7 +1133,7 @@ DELETE /projects/:id/fork
 
 Parameter:
 
-- `id` (required) - The ID of the project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
 
 ## Search for projects by name
 
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index fc3af55..1bc6a24 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -44,7 +44,7 @@ POST /projects/:id/repository/files
 ```
 
 ```bash
-curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&content=some%20content&commit_message=create%20a%20new%20file'
+curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
 ```
 
 Example response:
@@ -61,6 +61,8 @@ Parameters:
 - `file_path` (required) - Full path to new file. Ex. lib/class.rb
 - `branch_name` (required) - The name of branch
 - `encoding` (optional) - 'text' or 'base64'. Text is default.
+- `author_email` (optional) - Specify the commit author's email address
+- `author_name` (optional) - Specify the commit author's name
 - `content` (required) - File content
 - `commit_message` (required) - Commit message
 
@@ -71,7 +73,7 @@ PUT /projects/:id/repository/files
 ```
 
 ```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&content=some%20other%20content&commit_message=update%20file'
+curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
 ```
 
 Example response:
@@ -88,6 +90,8 @@ Parameters:
 - `file_path` (required) - Full path to file. Ex. lib/class.rb
 - `branch_name` (required) - The name of branch
 - `encoding` (optional) - 'text' or 'base64'. Text is default.
+- `author_email` (optional) - Specify the commit author's email address
+- `author_name` (optional) - Specify the commit author's name
 - `content` (required) - New file content
 - `commit_message` (required) - Commit message
 
@@ -107,7 +111,7 @@ DELETE /projects/:id/repository/files
 ```
 
 ```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&commit_message=delete%20file'
+curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
 ```
 
 Example response:
@@ -123,4 +127,6 @@ Parameters:
 
 - `file_path` (required) - Full path to file. Ex. lib/class.rb
 - `branch_name` (required) - The name of branch
+- `author_email` (optional) - Specify the commit author's email address
+- `author_name` (optional) - Specify the commit author's name
 - `commit_message` (required) - Commit message
diff --git a/doc/api/settings.md b/doc/api/settings.md
index a76dad0..aaa2c99 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -67,7 +67,7 @@ PUT /application/settings
 | `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
 | `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
 | `domain_blacklist_enabled` | boolean | no | Enable/disable the `domain_blacklist` |
-| `domain_blacklist` | array of strings | yes (if `domain_whitelist_enabled` is `true` | People trying to sign-up with emails from this domain will not be allowed to do so. |
+| `domain_blacklist` | array of strings | yes (if `domain_blacklist_enabled` is `true`) | People trying to sign-up with emails from this domain will not be allowed to do so. |
 | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
 | `after_sign_out_path` | string | no | Where to redirect users after logout |
 | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes |
diff --git a/doc/api/users.md b/doc/api/users.md
index 7e84858..54f7a2a 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -310,8 +310,7 @@ GET /user
   "can_create_group": true,
   "can_create_project": true,
   "two_factor_enabled": true,
-  "external": false,
-  "private_token": "dd34asd13as"
+  "external": false
 }
 ```
 
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 10ce4ac..341bc85 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -16,5 +16,7 @@
 - [Trigger builds through the API](triggers/README.md)
 - [Build artifacts](../user/project/builds/artifacts.md)
 - [User permissions](../user/permissions.md#gitlab-ci)
+- [Build permissions](../user/permissions.md#build-permissions)
 - [API](../api/ci/README.md)
 - [CI services (linked docker containers)](services/README.md)
+- [**New CI build permissions model**](../user/project/new_ci_build_permissions_model.md) Read about what changed in GitLab 8.12 and how that affects your builds. There's a new way to access your Git submodules and LFS objects in builds.
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index c134106..71670e6 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -1,17 +1,19 @@
 # CI Examples
 
+A collection of `.gitlab-ci.yml` files is maintained at the [GitLab CI Yml project][gitlab-ci-templates].
+If your favorite programming language or framework are missing we would love your help by sending a merge request
+with a `.gitlab-ci.yml`.
+
+Apart from those, here is an collection of tutorials and guides on setting up your CI pipeline:
+
 - [Testing a PHP application](php.md)
 - [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
 - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
 - [Test a Clojure application](test-clojure-application.md)
 - [Test a Scala application](test-scala-application.md)
 - [Using `dpl` as deployment tool](deployment/README.md)
-- Help your favorite programming language and GitLab by sending a merge request
-  with a guide for that language.
-
-## Outside the documentation
-
 - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
 - [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples)
 - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
-- [A collection of useful .gitlab-ci.yml templates](https://gitlab.com/gitlab-org/gitlab-ci-yml)
+
+[gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 20cd88c..ca9b986 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -5,7 +5,7 @@ Introduced in GitLab 8.8.
 
 ## Pipelines
 
-A pipeline is a group of [builds] that get executed in [stages] (batches). All
+A pipeline is a group of [builds] that get executed in [stages] \(batches). All
 of the builds in a stage are executed in parallel (if there are enough
 concurrent [runners]), and if they all succeed, the pipeline moves on to the
 next stage. If one of the builds fails, the next stage is not (usually)
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index c835ebc..c40cdd5 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -105,7 +105,8 @@ What is important is that each job is run independently from each other.
 
 If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
 Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
-the link under **Settings > CI settings** in your project.
+a "CI Lint" button to go to this page under **Pipelines > Pipelines** and
+**Pipelines > Builds** in your project.
 
 For more information and a complete `.gitlab-ci.yml` syntax, please read
 [the documentation on .gitlab-ci.yml](../yaml/README.md).
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index 7c0fb22..b858029 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -30,7 +30,8 @@ This is the universal solution which works with any type of executor
 ## SSH keys when using the Docker executor
 
 You will first need to create an SSH key pair. For more information, follow the
-instructions to [generate an SSH key](../../ssh/README.md).
+instructions to [generate an SSH key](../../ssh/README.md). Do not add a comment
+to the SSH key, or the `before_script` will prompt for a passphrase.
 
 Then, create a new **Secret Variable** in your project settings on GitLab
 following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY`
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 6c6767f..b78422f 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -2,6 +2,10 @@
 
 > [Introduced][ci-229] in GitLab CE 7.14.
 
+> **Note**:
+GitLab 8.12 has a completely redesigned build permissions system.
+Read all about the [new model and its implications][../../user/project/new_ci_build_permissions_model.md#build-triggers].
+
 Triggers can be used to force a rebuild of a specific branch, tag or commit,
 with an API call.
 
diff --git a/doc/ci/triggers/img/builds_page.png b/doc/ci/triggers/img/builds_page.png
index 2dee8ee..c2cf4b1 100644
Binary files a/doc/ci/triggers/img/builds_page.png and b/doc/ci/triggers/img/builds_page.png differ
diff --git a/doc/ci/triggers/img/trigger_single_build.png b/doc/ci/triggers/img/trigger_single_build.png
index baf3fc1..fa86f0f 100644
Binary files a/doc/ci/triggers/img/trigger_single_build.png and b/doc/ci/triggers/img/trigger_single_build.png differ
diff --git a/doc/ci/triggers/img/trigger_variables.png b/doc/ci/triggers/img/trigger_variables.png
index 908355c..b2fcc65 100644
Binary files a/doc/ci/triggers/img/trigger_variables.png and b/doc/ci/triggers/img/trigger_variables.png differ
diff --git a/doc/ci/triggers/img/triggers_page.png b/doc/ci/triggers/img/triggers_page.png
index 69cec5c..438f285 100644
Binary files a/doc/ci/triggers/img/triggers_page.png and b/doc/ci/triggers/img/triggers_page.png differ
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 4a7c21f..6a971c3 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -34,6 +34,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`.
 | **CI_BUILD_REF_NAME**   | all    | all    | The branch or tag name for which project is built |
 | **CI_BUILD_REPO**       | all    | all    | The URL to clone the Git repository |
 | **CI_BUILD_TRIGGERED**  | all    | 0.5    | The flag to indicate that build was [triggered] |
+| **CI_BUILD_MANUAL**     | 8.12   | all    | The flag to indicate that build was manually started |
 | **CI_BUILD_TOKEN**      | all    | 1.2    | Token used for authenticating with the GitLab Container Registry |
 | **CI_PIPELINE_ID**      | 8.10   | 0.5    | The unique id of the current pipeline that GitLab CI uses internally |
 | **CI_PROJECT_ID**       | all    | all    | The unique id of the current project that GitLab CI uses internally |
@@ -47,6 +48,8 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`.
 | **CI_RUNNER_ID**        | 8.10   | 0.5    | The unique id of runner being used |
 | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5    | The description of the runner as saved in GitLab |
 | **CI_RUNNER_TAGS**      | 8.10   | 0.5    | The defined runner tags |
+| **GITLAB_USER_ID**      | 8.12   | all    | The id of the user who started the build |
+| **GITLAB_USER_EMAIL**   | 8.12   | all    | The email of the user who started the build |
 
 **Some of the variables are only available when using runner with at least defined version.**
 
@@ -60,6 +63,7 @@ export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/git
 export CI_BUILD_TAG="1.0.0"
 export CI_BUILD_NAME="spec:other"
 export CI_BUILD_STAGE="test"
+export CI_BUILD_MANUAL="true"
 export CI_BUILD_TRIGGERED="true"
 export CI_BUILD_TOKEN="abcde-1234ABCD5678ef"
 export CI_PIPELINE_ID="1000"
@@ -76,8 +80,10 @@ export CI_RUNNER_DESCRIPTION="my runner"
 export CI_RUNNER_TAGS="docker, linux"
 export CI_SERVER="yes"
 export CI_SERVER_NAME="GitLab"
-export CI_SERVER_REVISION="8.9.0"
-export CI_SERVER_VERSION="70606bf"
+export CI_SERVER_REVISION="70606bf"
+export CI_SERVER_VERSION="8.9.0"
+export GITLAB_USER_ID="42"
+export GITLAB_USER_EMAIL="alexzander at sporer.com"
 ```
 
 ### YAML-defined variables
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 01d7108..1686855 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -6,50 +6,6 @@ GitLab Runner to manage your project's builds.
 If you want a quick introduction to GitLab CI, follow our
 [quick start guide](../quick_start/README.md).
 
----
-
-<!-- START doctoc generated TOC please keep comment here to allow auto update -->
-<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
-**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
-
-- [.gitlab-ci.yml](#gitlab-ci-yml)
-    - [image and services](#image-and-services)
-    - [before_script](#before_script)
-    - [after_script](#after_script)
-    - [stages](#stages)
-    - [types](#types)
-    - [variables](#variables)
-    - [cache](#cache)
-        - [cache:key](#cache-key)
-- [Jobs](#jobs)
-    - [script](#script)
-    - [stage](#stage)
-    - [only and except](#only-and-except)
-    - [job variables](#job-variables)
-    - [tags](#tags)
-    - [allow_failure](#allow_failure)
-    - [when](#when)
-        - [Manual actions](#manual-actions)
-    - [environment](#environment)
-    - [artifacts](#artifacts)
-        - [artifacts:name](#artifacts-name)
-        - [artifacts:when](#artifacts-when)
-        - [artifacts:expire_in](#artifacts-expire_in)
-    - [dependencies](#dependencies)
-    - [before_script and after_script](#before_script-and-after_script)
-- [Git Strategy](#git-strategy)
-- [Shallow cloning](#shallow-cloning)
-- [Hidden jobs](#hidden-jobs)
-- [Special YAML features](#special-yaml-features)
-    - [Anchors](#anchors)
-- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml)
-- [Skipping builds](#skipping-builds)
-- [Examples](#examples)
-
-<!-- END doctoc generated TOC please keep comment here to allow auto update -->
-
----
-
 ## .gitlab-ci.yml
 
 From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML)
@@ -134,8 +90,7 @@ builds, including deploy builds. This can be an array or a multi-line string.
 
 ### after_script
 
->**Note:**
-Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
+> Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
 
 `after_script` is used to define the command that will be run after for all
 builds. This has to be an array or a multi-line string.
@@ -179,11 +134,10 @@ Alias for [stages](#stages).
 
 ### variables
 
->**Note:**
-Introduced in GitLab Runner v0.5.0.
+> Introduced in GitLab Runner v0.5.0.
 
 GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the
-build environment. The variables are stored in the git repository and are meant
+build environment. The variables are stored in the Git repository and are meant
 to store non-sensitive project configuration, for example:
 
 ```yaml
@@ -198,10 +152,11 @@ thus allowing to fine tune them.
 
 Variables can be also defined on [job level](#job-variables).
 
+[Learn more about variables.](../variables/README.md)
+
 ### cache
 
->**Note:**
-Introduced in GitLab Runner v0.7.0.
+> Introduced in GitLab Runner v0.7.0.
 
 `cache` is used to specify a list of files and directories which should be
 cached between builds.
@@ -262,8 +217,7 @@ will be always present. For implementation details, please check GitLab Runner.
 
 #### cache:key
 
->**Note:**
-Introduced in GitLab Runner v1.0.0.
+> Introduced in GitLab Runner v1.0.0.
 
 The `key` directive allows you to define the affinity of caching
 between jobs, allowing to have a single cache for all jobs,
@@ -353,7 +307,7 @@ job_name:
 | except        | no | Defines a list of git refs for which build is not created |
 | tags          | no | Defines a list of tags which are used to select Runner |
 | allow_failure | no | Allow build to fail. Failed build doesn't contribute to commit status |
-| when          | no | Define when to run build. Can be `on_success`, `on_failure` or `always` |
+| when          | no | Define when to run build. Can be `on_success`, `on_failure`, `always` or `manual` |
 | dependencies  | no | Define other builds that a build depends on so that you can pass artifacts between them|
 | artifacts     | no | Define list of build artifacts |
 | cache         | no | Define list of files that should be cached between subsequent runs |
@@ -573,8 +527,7 @@ The above script will:
 
 #### Manual actions
 
->**Note:**
-Introduced in GitLab 8.10.
+> Introduced in GitLab 8.10.
 
 Manual actions are a special type of job that are not executed automatically;
 they need to be explicitly started by a user. Manual actions can be started
@@ -585,17 +538,16 @@ An example usage of manual actions is deployment to production.
 
 ### environment
 
->**Note:**
-Introduced in GitLab 8.9.
+> Introduced in GitLab 8.9.
 
-`environment` is used to define that a job deploys to a specific environment.
+`environment` is used to define that a job deploys to a specific [environment].
 This allows easy tracking of all deployments to your environments straight from
 GitLab.
 
 If `environment` is specified and no environment under that name exists, a new
 one will be created automatically.
 
-The `environment` name must contain only letters, digits, '-' and '_'. Common
+The `environment` name must contain only letters, digits, '-', '_', '/', '$', '{', '}' and spaces. Common
 names are `qa`, `staging`, and `production`, but you can use whatever name works
 with your workflow.
 
@@ -613,6 +565,35 @@ deploy to production:
 The `deploy to production` job will be marked as doing deployment to
 `production` environment.
 
+#### dynamic environments
+
+> [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6.
+
+`environment` can also represent a configuration hash with `name` and `url`.
+These parameters can use any of the defined CI [variables](#variables)
+(including predefined, secure variables and `.gitlab-ci.yml` variables).
+
+The common use case is to create dynamic environments for branches and use them
+as review apps.
+
+---
+
+**Example configurations**
+
+```
+deploy as review app:
+  stage: deploy
+  script: ...
+  environment:
+    name: review-apps/$CI_BUILD_REF_NAME
+    url: https://$CI_BUILD_REF_NAME.review.example.com/
+```
+
+The `deploy as review app` job will be marked as deployment to dynamically
+create the `review-apps/branch-name` environment.
+
+This environment should be accessible under `https://branch-name.review.example.com/`.
+
 ### artifacts
 
 >**Notes:**
@@ -680,8 +661,7 @@ be available for download in the GitLab UI.
 
 #### artifacts:name
 
->**Note:**
-Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
+> Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
 
 The `name` directive allows you to define the name of the created artifacts
 archive. That way, you can have a unique name for every archive which could be
@@ -744,8 +724,7 @@ job:
 
 #### artifacts:when
 
->**Note:**
-Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
+> Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
 
 `artifacts:when` is used to upload artifacts on build failure or despite the
 failure.
@@ -770,8 +749,7 @@ job:
 
 #### artifacts:expire_in
 
->**Note:**
-Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
+> Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
 
 `artifacts:expire_in` is used to delete uploaded artifacts after the specified
 time. By default, artifacts are stored on GitLab forever. `expire_in` allows you
@@ -806,8 +784,7 @@ job:
 
 ### dependencies
 
->**Note:**
-Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
+> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
 
 This feature should be used in conjunction with [`artifacts`](#artifacts) and
 allows you to define the artifacts to pass between different builds.
@@ -881,9 +858,8 @@ job:
 
 ## Git Strategy
 
->**Note:**
-Introduced in GitLab 8.9 as an experimental feature. May change in future
-releases or be removed completely.
+> Introduced in GitLab 8.9 as an experimental feature. May change in future
+  releases or be removed completely.
 
 You can set the `GIT_STRATEGY` used for getting recent application code. `clone`
 is slower, but makes sure you have a clean directory before every build. `fetch`
@@ -905,8 +881,7 @@ variables:
 
 ## Shallow cloning
 
->**Note:**
-Introduced in GitLab 8.9 as an experimental feature. May change in future
+> Introduced in GitLab 8.9 as an experimental feature. May change in future
 releases or be removed completely.
 
 You can specify the depth of fetching and cloning using `GIT_DEPTH`. This allows
@@ -934,24 +909,26 @@ variables:
   GIT_DEPTH: "3"
 ```
 
-## Hidden jobs
+## Hidden keys
 
->**Note:**
-Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
+> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
 
-Jobs that start with a dot (`.`) will be not processed by GitLab CI. You can
+Keys that start with a dot (`.`) will be not processed by GitLab CI. You can
 use this feature to ignore jobs, or use the
-[special YAML features](#special-yaml-features) and transform the hidden jobs
+[special YAML features](#special-yaml-features) and transform the hidden keys
 into templates.
 
-In the following example, `.job_name` will be ignored:
+In the following example, `.key_name` will be ignored:
 
 ```yaml
-.job_name:
+.key_name:
   script:
     - rake spec
 ```
 
+Hidden keys can be hashes like normal CI jobs, but you are also allowed to use
+different types of structures to leverage special YAML features.
+
 ## Special YAML features
 
 It's possible to use special YAML features like anchors (`&`), aliases (`*`)
@@ -962,12 +939,11 @@ Read more about the various [YAML features](https://learnxinyminutes.com/docs/ya
 
 ### Anchors
 
->**Note:**
-Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
+> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
 
 YAML also has a handy feature called 'anchors', which let you easily duplicate
 content across your document. Anchors can be used to duplicate/inherit
-properties, and is a perfect example to be used with [hidden jobs](#hidden-jobs)
+properties, and is a perfect example to be used with [hidden keys](#hidden-keys)
 to provide templates for your jobs.
 
 The following example uses anchors and map merging. It will create two jobs,
@@ -975,7 +951,7 @@ The following example uses anchors and map merging. It will create two jobs,
 having their own custom `script` defined:
 
 ```yaml
-.job_template: &job_definition  # Hidden job that defines an anchor named 'job_definition'
+.job_template: &job_definition  # Hidden key that defines an anchor named 'job_definition'
   image: ruby:2.1
   services:
     - postgres
@@ -1081,7 +1057,14 @@ test:mysql:
     - ruby
 ```
 
-You can see that the hidden jobs are conveniently used as templates.
+You can see that the hidden keys are conveniently used as templates.
+
+## Triggers
+
+Triggers can be used to force a rebuild of a specific branch, tag or commit,
+with an API call.
+
+[Read more in the triggers documentation.](../triggers/README.md)
 
 ## Validate the .gitlab-ci.yml
 
@@ -1099,3 +1082,5 @@ Visit the [examples README][examples] to see a list of examples using GitLab
 CI with various languages.
 
 [examples]: ../examples/README.md
+[ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323
+[environment]: ../environments.md
diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md
index 047a0b0..d774064 100644
--- a/doc/container_registry/README.md
+++ b/doc/container_registry/README.md
@@ -78,9 +78,9 @@ delete them.
 > **Note:**
 This feature requires GitLab 8.8 and GitLab Runner 1.2.
 
-Make sure that your GitLab Runner is configured to allow building docker images.
-You have to check the [Using Docker Build documentation](../ci/docker/using_docker_build.md).
-Then see the CI documentation on [Using the GitLab Container Registry](../ci/docker/using_docker_build.md#using-the-gitlab-container-registry).
+Make sure that your GitLab Runner is configured to allow building Docker images by
+following the [Using Docker Build](../ci/docker/using_docker_build.md)
+and [Using the GitLab Container Registry documentation](../ci/docker/using_docker_build.md#using-the-gitlab-container-registry).
 
 ## Limitations
 
diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md
index 4620bb2..31164cc 100644
--- a/doc/customization/issue_closing.md
+++ b/doc/customization/issue_closing.md
@@ -1,39 +1,4 @@
-# Issue closing pattern
+This document was split into:
 
-When a commit or merge request resolves one or more issues, it is possible to automatically have these issues closed when the commit or merge request lands in the project's default branch.
-
-If a commit message or merge request description contains a sentence matching the regular expression below, all issues referenced from
-the matched text will be closed. This happens when the commit is pushed to a project's default branch, or when a commit or merge request is merged into there.
-
-When not specified, the default `issue_closing_pattern` as shown below will be used:
-
-```bash
-((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)
-```
-
-Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that matches a reference to a local issue (`#123`), cross-project issue (`group/project#123`) or a link to an issue (`https://gitlab.example.com/group/project/issues/123`).
-
-For example:
-
-```
-git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#22). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
-```
-
-will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages.
-
-Tip: you can test this closing pattern at [http://rubular.com][1]. Use this site
-to test your own patterns.
-Because Rubular doesn't understand `%{issue_ref}`, you can replace this by `#\d+` in testing, which matches only local issue references like `#123`.
-
-## Change the pattern
-
-For Omnibus installs you can change the default pattern in `/etc/gitlab/gitlab.rb`:
-
-```
-issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)'
-```
-
-For manual installs you can customize the pattern in [gitlab.yml][0] using the `issue_closing_pattern` key.
-
-[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
-[1]: http://rubular.com/r/Xmbexed1OJ
+- [administration/issue_closing_pattern.md](../administration/issue_closing_pattern.md).
+- [user/project/issues/automatic_issue_closing](../user/project/issues/automatic_issue_closing.md).
diff --git a/doc/development/README.md b/doc/development/README.md
index 57f37da..58c00f6 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -18,6 +18,8 @@
 ## Process
 
 - [Code review guidelines](code_review.md) for reviewing code and having code reviewed.
+- [Merge request performance guidelines](merge_request_performance_guidelines.md)
+  for ensuring merge requests do not negatively impact GitLab performance
 
 ## Backend howtos
 
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 927a187..39b801f 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -6,7 +6,7 @@ it organized and easy to find.
 ## Location and naming of documents
 
 >**Note:**
-These guidelines derive from the discussion taken place in issue [#3349](ce-3349).
+These guidelines derive from the discussion taken place in issue [#3349][ce-3349].
 
 The documentation hierarchy can be vastly improved by providing a better layout
 and organization of directories.
@@ -155,15 +155,30 @@ Inside the document:
 
 - Every piece of documentation that comes with a new feature should declare the
   GitLab version that feature got introduced. Right below the heading add a
-  note: `> Introduced in GitLab 8.3.`.
+  note:
+
+    ```
+    > Introduced in GitLab 8.3.
+    ```
+
 - If possible every feature should have a link to the MR that introduced it.
   The above note would be then transformed to:
-  `> [Introduced][ce-1242] in GitLab 8.3.`, where
-  the [link identifier](#links) is named after the repository (CE) and the MR
-  number.
-- If the feature is only in GitLab EE, don't forget to mention it, like:
-  `> Introduced in GitLab EE 8.3.`. Otherwise, leave
-  this mention out.
+
+    ```
+    > [Introduced][ce-1242] in GitLab 8.3.
+    ```
+
+    , where the [link identifier](#links) is named after the repository (CE) and
+    the MR number.
+
+- If the feature is only in GitLab Enterprise Edition, don't forget to mention
+  it, like:
+
+    ```
+    > Introduced in GitLab Enterprise Edition 8.3.
+    ```
+
+    Otherwise, leave this mention out.
 
 ## References
 
@@ -222,18 +237,26 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
     ```
 
 1. Find and replace any occurrences of the old location with the new one.
-   A quick way to find them is to use `grep`:
+   A quick way to find them is to use `git grep`. First go to the root directory
+   where you cloned the `gitlab-ce` repository and then do:
 
     ```
-    grep -nR "lfs_administration.md" doc/
+    git grep -n "workflow/lfs/lfs_administration"
+    git grep -n "lfs/lfs_administration"
     ```
 
-    The above command will search in the `doc/` directory for
-    `lfs_administration.md` recursively and will print the file and the line
-    where this file is mentioned. Note that we used just the filename
-    (`lfs_administration.md`) and not the whole the relative path
-    (`workflow/lfs/lfs_administration.md`).
+Things to note:
 
+- Since we also use inline documentation, except for the documentation itself,
+  the document might also be referenced in the views of GitLab (`app/`) which will
+  render when visiting `/help`, and sometimes in the testing suite (`spec/`).
+- The above `git grep` command will search recursively in the directory you run
+  it in for `workflow/lfs/lfs_administration` and `lfs/lfs_administration`
+  and will print the file and the line where this file is mentioned.
+  You may ask why the two greps. Since we use relative paths to link to
+  documentation, sometimes it might be useful to search a path deeper.
+- The `*.md` extension is not used when a document is linked to GitLab's
+  built-in help page, that's why we omit it in `git grep`.
 
 ## Configuration documentation for source and Omnibus installations
 
@@ -422,7 +445,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain
 
 [cURL]: http://curl.haxx.se/ "cURL website"
 [single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
-[gfm]: http://docs.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
+[gfm]: http://docs.gitlab.com/ce/user/markdown.html#newlines "GitLab flavored markdown documentation"
 [doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
 [ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure"
 [graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index c2272ab..105e2f1 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -137,3 +137,18 @@ end
 ```
 
 Here the final value of `sleep_real_time` will be `3`, _not_ `1`.
+
+## Tracking Custom Events
+
+Besides instrumenting code GitLab Performance Monitoring also supports tracking
+of custom events. This is primarily intended to be used for tracking business
+metrics such as the number of Git pushes, repository imports, and so on.
+
+To track a custom event simply call `Gitlab::Metrics.add_event` passing it an
+event name and a custom set of (optional) tags. For example:
+
+```ruby
+Gitlab::Metrics.add_event(:user_login, email: current_user.email)
+```
+
+Event names should be verbs such as `push_repository` and `remove_branch`.
diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md
new file mode 100644
index 0000000..0363bf8
--- /dev/null
+++ b/doc/development/merge_request_performance_guidelines.md
@@ -0,0 +1,171 @@
+# Merge Request Performance Guidelines
+
+To ensure a merge request does not negatively impact performance of GitLab
+_every_ merge request **must** adhere to the guidelines outlined in this
+document. There are no exceptions to this rule unless specifically discussed
+with and agreed upon by merge request endbosses and performance specialists.
+
+To measure the impact of a merge request you can use
+[Sherlock](profiling.md#sherlock). It's also highly recommended that you read
+the following guides:
+
+* [Performance Guidelines](performance.md)
+* [What requires downtime?](what_requires_downtime.md)
+
+## Impact Analysis
+
+**Summary:** think about the impact your merge request may have on performance
+and those maintaining a GitLab setup.
+
+Any change submitted can have an impact not only on the application itself but
+also those maintaining it and those keeping it up and running (e.g. production
+engineers). As a result you should think carefully about the impact of your
+merge request on not only the application but also on the people keeping it up
+and running.
+
+Can the queries used potentially take down any critical services and result in
+engineers being woken up in the night? Can a malicious user abuse the code to
+take down a GitLab instance? Will my changes simply make loading a certain page
+slower? Will execution time grow exponentially given enough load or data in the
+database?
+
+These are all questions one should ask themselves before submitting a merge
+request. It may sometimes be difficult to assess the impact, in which case you
+should ask a performance specialist to review your code. See the "Reviewing"
+section below for more information.
+
+## Performance Review
+
+**Summary:** ask performance specialists to review your code if you're not sure
+about the impact.
+
+Sometimes it's hard to assess the impact of a merge request. In this case you
+should ask one of the merge request (mini) endbosses to review your changes. You
+can find a list of these endbosses at <https://about.gitlab.com/team/>. An
+endboss in turn can request a performance specialist to review the changes.
+
+## Query Counts
+
+**Summary:** a merge request **should not** increase the number of executed SQL
+queries unless absolutely necessary.
+
+The number of queries executed by the code modified or added by a merge request
+must not increase unless absolutely necessary. When building features it's
+entirely possible you will need some extra queries, but you should try to keep
+this at a minimum.
+
+As an example, say you introduce a feature that updates a number of database
+rows with the same value. It may be very tempting (and easy) to write this using
+the following pseudo code:
+
+```ruby
+objects_to_update.each do |object|
+  object.some_field = some_value
+  object.save
+end
+```
+
+This will end up running one query for every object to update. This code can
+easily overload a database given enough rows to update or many instances of this
+code running in parallel. This particular problem is known as the
+["N+1 query problem"](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations).
+
+In this particular case the workaround is fairly easy:
+
+```ruby
+objects_to_update.update_all(some_field: some_value)
+```
+
+This uses ActiveRecord's `update_all` method to update all rows in a single
+query. This in turn makes it much harder for this code to overload a database.
+
+## Executing Queries in Loops
+
+**Summary:** SQL queries **must not** be executed in a loop unless absolutely
+necessary.
+
+Executing SQL queries in a loop can result in many queries being executed
+depending on the number of iterations in a loop. This may work fine for a
+development environment with little data, but in a production environment this
+can quickly spiral out of control.
+
+There are some cases where this may be needed. If this is the case this should
+be clearly mentioned in the merge request description.
+
+## Eager Loading
+
+**Summary:** always eager load associations when retrieving more than one row.
+
+When retrieving multiple database records for which you need to use any
+associations you **must** eager load these associations. For example, if you're
+retrieving a list of blog posts and you want to display their authors you
+**must** eager load the author associations.
+
+In other words, instead of this:
+
+```ruby
+Post.all.each do |post|
+  puts post.author.name
+end
+```
+
+You should use this:
+
+```ruby
+Post.all.includes(:author).each do |post|
+  puts post.author.name
+end
+```
+
+## Memory Usage
+
+**Summary:** merge requests **must not** increase memory usage unless absolutely
+necessary.
+
+A merge request must not increase the memory usage of GitLab by more than the
+absolute bare minimum required by the code. This means that if you have to parse
+some large document (e.g. an HTML document) it's best to parse it as a stream
+whenever possible, instead of loading the entire input into memory. Sometimes
+this isn't possible, in that case this should be stated explicitly in the merge
+request.
+
+## Lazy Rendering of UI Elements
+
+**Summary:** only render UI elements when they're actually needed.
+
+Certain UI elements may not always be needed. For example, when hovering over a
+diff line there's a small icon displayed that can be used to create a new
+comment. Instead of always rendering these kind of elements they should only be
+rendered when actually needed. This ensures we don't spend time generating
+Haml/HTML when it's not going to be used.
+
+## Instrumenting New Code
+
+**Summary:** always add instrumentation for new classes, modules, and methods.
+
+Newly added classes, modules, and methods must be instrumented. This ensures
+we can track the performance of this code over time.
+
+For more information see [Instrumentation](instrumentation.md). This guide
+describes how to add instrumentation and where to add it.
+
+## Use of Caching
+
+**Summary:** cache data in memory or in Redis when it's needed multiple times in
+a transaction or has to be kept around for a certain time period.
+
+Sometimes certain bits of data have to be re-used in different places during a
+transaction. In these cases this data should be cached in memory to remove the
+need for running complex operations to fetch the data. You should use Redis if
+data should be cached for a certain time period instead of the duration of the
+transaction.
+
+For example, say you process multiple snippets of text containiner username
+mentions (e.g. `Hello @alice` and `How are you doing @alice?`). By caching the
+user objects for every username we can remove the need for running the same
+query for every mention of `@alice`.
+
+Caching data per transaction can be done using
+[RequestStore](https://github.com/steveklabnik/request_store). Caching data in
+Redis can be done using [Rails' caching
+system](http://guides.rubyonrails.org/caching_with_rails.html).
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index b8fab3a..295eae0 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -111,6 +111,28 @@ class MyMigration < ActiveRecord::Migration
 end
 ```
 
+
+## Integer column type
+
+By default, an integer column can hold up to a 4-byte (32-bit) number. That is
+a max value of 2,147,483,647. Be aware of this when creating a column that will
+hold file sizes in byte units. If you are tracking file size in bytes this
+restricts the maximum file size to just over 2GB.
+
+To allow an integer column to hold up to an 8-byte (64-bit) number, explicitly
+set the limit to 8-bytes. This will allow the column to hold a value up to
+9,223,372,036,854,775,807.
+
+Rails migration example:
+
+```
+add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
+
+# or
+
+add_column(:projects, :foo, :integer, default: 10, limit: 8)
+```
+
 ## Testing
 
 Make sure that your migration works with MySQL and PostgreSQL with data. An empty database does not guarantee that your migration is correct.
diff --git a/doc/development/newlines_styleguide.md b/doc/development/newlines_styleguide.md
index e03adca..32aac25 100644
--- a/doc/development/newlines_styleguide.md
+++ b/doc/development/newlines_styleguide.md
@@ -2,7 +2,7 @@
 
 This style guide recommends best practices for newlines in Ruby code.
 
-## Rule: separate code with newlines only when it makes sense from logic perspectice
+## Rule: separate code with newlines only to group together related logic
 
 ```ruby
 # bad
diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md
index 57136ac..ff10a98 100644
--- a/doc/gitlab-basics/add-file.md
+++ b/doc/gitlab-basics/add-file.md
@@ -25,7 +25,3 @@ Add all the information that you'd like to include in your file:
 Add a commit message based on what you just added and then click on "commit changes":
 
 ![Commit changes](basicsimages/commit_changes.png)
-
-### Note
-Besides its regular files, every directory needs a README.md or README.html file which works like an index, telling
-what the directory is about. It's the first document you'll find when you open a directory.
diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md
index 5221d85..da9a165 100644
--- a/doc/gitlab-basics/create-issue.md
+++ b/doc/gitlab-basics/create-issue.md
@@ -1,6 +1,6 @@
 # How to create an Issue in GitLab
 
-The Issue Tracker is a good place to add things that need to be improved or solved in a project.  
+The Issue Tracker is a good place to add things that need to be improved or solved in a project.
 
 To create an Issue, sign in to GitLab.
 
@@ -24,4 +24,4 @@ You may assign the Issue to a user, add a milestone and add labels (they are all
 
 ![Submit new issue](basicsimages/submit_new_issue.png)
 
-Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](http://docs.gitlab.com/ce/customization/issue_closing.html).
+Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](../user/project/issues/automatic_issue_closing.md).
diff --git a/doc/install/installation.md b/doc/install/installation.md
index d4b89fa..3ac813a 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -268,9 +268,9 @@ sudo usermod -aG redis git
 ### Clone the Source
 
     # Clone GitLab repository
-    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-11-stable gitlab
+    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-12-stable gitlab
 
-**Note:** You can change `8-11-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-12-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
 
 ### Configure It
 
@@ -331,6 +331,9 @@ sudo usermod -aG redis git
     # Disable 'git gc --auto' because GitLab already runs 'git gc' when needed
     sudo -u git -H git config --global gc.auto 0
 
+    # Enable packfile bitmaps
+    sudo -u git -H git config --global repack.writeBitmaps true
+
     # Configure Redis connection settings
     sudo -u git -H cp config/resque.yml.example config/resque.yml
 
@@ -397,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of
     cd /home/git
     sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
     cd gitlab-workhorse
-    sudo -u git -H git checkout v0.7.11
+    sudo -u git -H git checkout v0.8.2
     sudo -u git -H make
 
 ### Initialize Database and Activate Advanced Features
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index a65ac8a..766a711 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -32,7 +32,7 @@ Please consider using a virtual machine to run GitLab.
 
 ## Ruby versions
 
-GitLab requires Ruby (MRI) 2.1.x and currently does not work with versions 2.2 or 2.3.
+GitLab requires Ruby (MRI) 2.3. Support for Ruby versions below 2.3 (2.1, 2.2) will stop with GitLab 8.13.
 
 You will have to use the standard MRI implementation of Ruby.
 We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
@@ -63,30 +63,30 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
 
 ### Memory
 
-You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
+You need at least 4GB of addressable memory (RAM + swap) to install and use GitLab!
 The operating system and any other running applications will also be using memory
-so keep in mind that you need at least 2GB available before running GitLab. With
+so keep in mind that you need at least 4GB available before running GitLab. With
 less memory GitLab will give strange errors during the reconfigure run and 500
 errors during usage.
 
-- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice.
-- 1GB RAM + 1GB swap supports up to 100 users but it will be very slow
-- **2GB RAM** is the **recommended** memory size for all installations and supports up to 100 users
-- 4GB RAM supports up to 1,000 users
-- 8GB RAM supports up to 2,000 users
-- 16GB RAM supports up to 4,000 users
-- 32GB RAM supports up to 8,000 users
-- 64GB RAM supports up to 16,000 users
-- 128GB RAM supports up to 32,000 users
+- 1GB RAM + 3GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice.
+- 2GB RAM + 2GB swap supports up to 100 users but it will be very slow
+- **4GB RAM** is the **recommended** memory size for all installations and supports up to 100 users
+- 8GB RAM supports up to 1,000 users
+- 16GB RAM supports up to 2,000 users
+- 32GB RAM supports up to 4,000 users
+- 64GB RAM supports up to 8,000 users
+- 128GB RAM supports up to 16,000 users
+- 256GB RAM supports up to 32,000 users
 - More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
 
-We recommend having at least 1GB of swap on your server, even if you currently have
+We recommend having at least 2GB of swap on your server, even if you currently have
 enough available RAM. Having swap will help reduce the chance of errors occurring
 if your available memory changes.
 
 Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
 
-## Gitlab Runner
+## GitLab Runner
 
 We strongly advise against installing GitLab Runner on the same machine you plan
 to install GitLab on. Depending on how you decide to configure GitLab Runner and
@@ -113,10 +113,8 @@ It's possible to increase the amount of unicorn workers and this will usually he
 For most instances we recommend using: CPU cores + 1 = unicorn workers.
 So for a machine with 2 cores, 3 unicorn workers is ideal.
 
-For all machines that have 1GB and up we recommend a minimum of three unicorn workers.
-If you have a 512MB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping.
-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 have a 512MB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping.
+For all machines that have 2GB and up we recommend a minimum of three unicorn workers.
+If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive swapping.
 
 To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings).
 
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index 2eb6266..556d71b 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -1,111 +1,164 @@
-# Integrate your server with Bitbucket
+# Integrate your GitLab server with Bitbucket
 
-Import projects from Bitbucket and login to your GitLab instance with your Bitbucket account.
+Import projects from Bitbucket.org and login to your GitLab instance with your
+Bitbucket.org account.
 
-To enable the Bitbucket OmniAuth provider you must register your application with Bitbucket.
-Bitbucket will generate an application ID and secret key for you to use.
+## Overview
 
-1.  Sign in to Bitbucket.
+You can set up Bitbucket.org as an OAuth provider so that you can use your
+credentials to authenticate into GitLab or import your projects from
+Bitbucket.org.
 
-1.  Navigate to your individual user settings or a team's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you.
+- To use Bitbucket.org as an OmniAuth provider, follow the [Bitbucket OmniAuth
+  provider](#bitbucket-omniauth-provider) section.
+- To import projects from Bitbucket, follow both the
+  [Bitbucket OmniAuth provider](#bitbucket-omniauth-provider) and
+  [Bitbucket project import](#bitbucket-project-import) sections.
 
-1.  Select "OAuth" in the left menu.
+## Bitbucket OmniAuth provider
 
-1.  Select "Add consumer".
+> **Note:**
+Make sure to first follow the [Initial OmniAuth configuration][init-oauth]
+before proceeding with setting up the Bitbucket integration.
 
-1.  Provide the required details.
-    - Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
-    - Application description: Fill this in if you wish.
-    - URL: The URL to your GitLab installation. 'https://gitlab.company.com'
-1.  Select "Save".
+To enable the Bitbucket OmniAuth provider you must register your application
+with Bitbucket.org. Bitbucket will generate an application ID and secret key for
+you to use.
 
-1.  You should now see a Key and Secret in the list of OAuth customers.
-    Keep this page open as you continue configuration.
+1.  Sign in to [Bitbucket.org](https://bitbucket.org).
+1.  Navigate to your individual user settings (**Bitbucket settings**) or a team's
+    settings (**Manage team**), depending on how you want the application registered.
+    It does not matter if the application is registered as an individual or a
+    team, that is entirely up to you.
+1.  Select **OAuth** in the left menu under "Access Management".
+1.  Select **Add consumer**.
+1.  Provide the required details:
 
-1.  On your GitLab server, open the configuration file.
+    | Item | Description |
+    | :--- | :---------- |
+    | **Name** | This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive. |
+    | **Application description** | Fill this in if you wish. |
+    | **Callback URL** | Leave blank. |
+    | **URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com`. |
 
-    For omnibus package:
+    And grant at least the following permissions:
 
-    ```sh
-      sudo editor /etc/gitlab/gitlab.rb
+    ```
+    Account: Email
+    Repositories: Read, Admin
     ```
 
-    For installations from source:
+    >**Note:**
+    It may seem a little odd to giving GitLab admin permissions to repositories,
+    but this is needed in order for GitLab to be able to clone the repositories.
 
-    ```sh
-      cd /home/git/gitlab
+    ![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png)
+
+1.  Select **Save**.
+1.  Select your newly created OAuth consumer and you should now see a Key and
+    Secret in the list of OAuth customers. Keep this page open as you continue
+    the configuration.
+
+      ![Bitbucket OAuth key](img/bitbucket_oauth_keys.png)
+
+1.  On your GitLab server, open the configuration file:
 
-      sudo -u git -H editor config/gitlab.yml
     ```
+    # For Omnibus packages
+    sudo editor /etc/gitlab/gitlab.rb
 
-1.  See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+    # For installations from source
+    sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
+    ```
 
-1.  Add the provider configuration:
+1.  Follow the [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
+    for initial settings.
+1.  Add the Bitbucket provider configuration:
 
-    For omnibus package:
+    For Omnibus packages:
 
     ```ruby
-      gitlab_rails['omniauth_providers'] = [
-        {
-          "name" => "bitbucket",
-          "app_id" => "YOUR_KEY",
-          "app_secret" => "YOUR_APP_SECRET",
-          "url" => "https://bitbucket.org/"
-        }
-      ]
+    gitlab_rails['omniauth_providers'] = [
+      {
+        "name" => "bitbucket",
+        "app_id" => "BITBUCKET_APP_KEY",
+        "app_secret" => "BITBUCKET_APP_SECRET",
+        "url" => "https://bitbucket.org/"
+      }
+    ]
     ```
 
-    For installation from source:
+    For installations from source:
 
-    ```
-      - { name: 'bitbucket', app_id: 'YOUR_KEY',
-        app_secret: 'YOUR_APP_SECRET' }
+    ```yaml
+    - { name: 'bitbucket',
+        app_id: 'BITBUCKET_APP_KEY',
+        app_secret: 'BITBUCKET_APP_SECRET' }
     ```
 
-1.  Change 'YOUR_APP_ID' to the key from the Bitbucket application page from step 7.
+    ---
 
-1.  Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7.
+    Where `BITBUCKET_APP_KEY` is the Key and `BITBUCKET_APP_SECRET` the Secret
+    from the Bitbucket application page.
 
 1.  Save the configuration file.
+1.  [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+    installed GitLab via Omnibus or from source respectively.
 
-1.  If you're using the omnibus package, reconfigure GitLab (```gitlab-ctl reconfigure```).
-
-1.  Restart GitLab for the changes to take effect.
-
-On the sign in page there should now be a Bitbucket icon below the regular sign in form.
-Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application.
-If everything goes well the user will be returned to GitLab and will be signed in.
+On the sign in page there should now be a Bitbucket icon below the regular sign
+in form. Click the icon to begin the authentication process. Bitbucket will ask
+the user to sign in and authorize the GitLab application. If everything goes
+well, the user will be returned to GitLab and will be signed in.
 
 ## Bitbucket project import
 
-To allow projects to be imported directly into GitLab, Bitbucket requires two extra setup steps compared to GitHub and GitLab.com.
+To allow projects to be imported directly into GitLab, Bitbucket requires two
+extra setup steps compared to [GitHub](github.md) and [GitLab.com](gitlab.md).
 
-Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key.
+Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and
+instead requires GitLab to use SSH and identify itself using your GitLab
+server's SSH key.
 
-### Step 1: Public key
+To be able to access repositories on Bitbucket, GitLab will automatically
+register your public key with Bitbucket as a deploy key for the repositories to
+be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa` which
+translates to `/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages and to
+`/home/git/.ssh/bitbucket_rsa.pub` for installations from source.
 
-To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations.
+---
 
-If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
+Below are the steps that will allow GitLab to be able to import your projects
+from Bitbucket.
 
-1. Create a new SSH key:
+1. Make sure you [have enabled the Bitbucket OAuth support](#bitbucket-omniauth-provider).
+1. Create a new SSH key with an **empty passphrase**:
 
     ```sh
     sudo -u git -H ssh-keygen
     ```
 
-    When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`.
-    Make sure to use an **empty passphrase**.
+    When asked to 'Enter file in which to save the key' enter:
+    `/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages or
+    `/home/git/.ssh/bitbucket_rsa` for installations from source. The name is
+    important so make sure to get it right.
 
-1. Configure SSH client to use your new key:
+    > **Warning:**
+    This key must NOT be associated with ANY existing Bitbucket accounts. If it
+    is, the import will fail with an `Access denied! Please verify you can add
+    deploy keys to this repository.` error.
 
-    Open the SSH configuration file of the git user.
+1. Next, you need to to configure the SSH client to use your new key. Open the
+   SSH configuration file of the `git` user:
 
-    ```sh
-      sudo editor /home/git/.ssh/config
+    ```
+    # For Omnibus packages
+    sudo editor /var/opt/gitlab/.ssh/config
+
+    # For installations from source
+    sudo editor /home/git/.ssh/config
     ```
 
-    Add a host configuration for `bitbucket.org`.
+1. Add a host configuration for `bitbucket.org`:
 
     ```sh
     Host bitbucket.org
@@ -113,28 +166,46 @@ If you have that file in place, you're all set and should see the "Import projec
       User git
     ```
 
-### Step 2: Known hosts
-
-To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
-
-1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
+1. Save the file and exit.
+1. Manually connect to `bitbucket.org` over SSH, while logged in as the `git`
+   user that GitLab will use:
 
     ```sh
     sudo -u git -H ssh bitbucket.org
     ```
 
-1.  Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
+    That step is performed because GitLab needs to connect to Bitbucket over SSH,
+    in order to add `bitbucket.org` to your GitLab server's known SSH hosts.
+
+1.  Verify the RSA key fingerprint you'll see in the response matches the one
+    in the [Bitbucket documentation][bitbucket-docs] (the specific IP address
+    doesn't matter):
 
     ```sh
-    The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
-    RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
+    The authenticity of host 'bitbucket.org (104.192.143.1)' can't be established.
+    RSA key fingerprint is SHA256:zzXQOXSRBEiUtuE8AikJYKwbHaxvSc0ojez9YXaGp1A.
     Are you sure you want to continue connecting (yes/no)?
     ```
 
-1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
+1. If the fingerprint matches, type `yes` to continue connecting and have
+   `bitbucket.org` be added to your known SSH hosts. After confirming you should
+   see a permission denied message. If you see an authentication successful
+   message you have done something wrong. The key you are using has already been
+   added to a Bitbucket account and will cause the import script to fail. Ensure
+   the key you are using CANNOT authenticate with Bitbucket.
+1. Restart GitLab to allow it to find the new public key.
 
-1. Your GitLab server is now able to connect to Bitbucket over SSH.
+Your GitLab server is now able to connect to Bitbucket over SSH. You should be
+able to see the "Import projects from Bitbucket" option on the New Project page
+enabled.
 
-1. Restart GitLab to allow it to find the new public key.
+## Acknowledgemts
+
+Special thanks to the writer behind the following article:
+
+- http://stratus3d.com/blog/2015/09/06/migrating-from-bitbucket-to-local-gitlab-server/
 
-You should now see the "Import projects from Bitbucket" option on the New Project page enabled.
+[init-oauth]: omniauth.md#initial-omniauth-configuration
+[bitbucket-docs]: https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/img/bitbucket_oauth_keys.png b/doc/integration/img/bitbucket_oauth_keys.png
new file mode 100644
index 0000000..3fb2f75
Binary files /dev/null and b/doc/integration/img/bitbucket_oauth_keys.png differ
diff --git a/doc/integration/img/bitbucket_oauth_settings_page.png b/doc/integration/img/bitbucket_oauth_settings_page.png
new file mode 100644
index 0000000..a304771
Binary files /dev/null and b/doc/integration/img/bitbucket_oauth_settings_page.png differ
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 46b260e..8a55fce 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -102,8 +102,8 @@ To change these settings:
         block_auto_created_users: true
     ```
 
-Now we can choose one or more of the Supported Providers listed above to continue
-the configuration process.
+Now we can choose one or more of the [Supported Providers](#supported-providers)
+listed above to continue the configuration process.
 
 ## Enable OmniAuth for an Existing User
 
diff --git a/doc/intro/README.md b/doc/intro/README.md
index 1850031..1790b2b 100644
--- a/doc/intro/README.md
+++ b/doc/intro/README.md
@@ -22,10 +22,10 @@ Create merge requests and review code.
 
 - [Fork a project and contribute to it](../workflow/forking_workflow.md)
 - [Create a new merge request](../gitlab-basics/add-merge-request.md)
-- [Automatically close issues from merge requests](../customization/issue_closing.md)
-- [Automatically merge when your builds succeed](../workflow/merge_when_build_succeeds.md)
-- [Revert any commit](../workflow/revert_changes.md)
-- [Cherry-pick any commit](../workflow/cherry_pick_changes.md)
+- [Automatically close issues from merge requests](../user/project/issues/automatic_issue_closing.md)
+- [Automatically merge when your builds succeed](../user/project/merge_requests/merge_when_build_succeeds.md)
+- [Revert any commit](../user/project/merge_requests/revert_changes.md)
+- [Cherry-pick any commit](../user/project/merge_requests/cherry_pick_changes.md)
 
 ## Test and Deploy
 
diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md
index edd6c59..7f08188 100644
--- a/doc/legal/corporate_contributor_license_agreement.md
+++ b/doc/legal/corporate_contributor_license_agreement.md
@@ -16,7 +16,7 @@ Subject to the terms and conditions of this Agreement, You hereby grant to GitLa
 
 Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone o [...]
 
-4.  You represent that You are legally entitled to grant the above license. You represent further that each employee of the Corporation is authorized to submit Contributions on behalf of the Corporation, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of [name of corporation here]."
+4.  You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of [name of Your corporation here]." Such designations of exclusion for unauthorized employees are to be submitted via email to legal at gitlab.com.
 
 5.  You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others).
 
@@ -24,6 +24,6 @@ Subject to the terms and conditions of this Agreement, You hereby grant to GitLa
 
 7.  Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
 
-8.  It is your responsibility to notify GitLab B.V. when any change is required to the designation of employees not authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab B.V..
+8.  It is Your responsibility to notify GitLab.com when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf per Section 4. Such notification should be sent via email to legal at gitlab.com.
 
 This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 835af54..3f4056d 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -79,6 +79,9 @@ gitlab_rails['backup_upload_connection'] = {
   'region' => 'eu-west-1',
   'aws_access_key_id' => 'AKIAKIAKI',
   'aws_secret_access_key' => 'secret123'
+  # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty
+  # ie. 'aws_access_key_id' => '',
+  # 'use_iam_profile' => 'true'
 }
 gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
 ```
@@ -95,6 +98,9 @@ For installations from source:
         region: eu-west-1
         aws_access_key_id: AKIAKIAKI
         aws_secret_access_key: 'secret123'
+        # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty
+        # ie. aws_access_key_id: ''
+        # use_iam_profile: 'true'
       # The remote 'directory' to store your backups. For S3, this would be the bucket name.
       remote_directory: 'my.s3.bucket'
       # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md
index 9872176..b058f8e 100644
--- a/doc/update/8.10-to-8.11.md
+++ b/doc/update/8.10-to-8.11.md
@@ -82,7 +82,7 @@ GitLab 8.1.
 ```bash
 cd /home/git/gitlab-workhorse
 sudo -u git -H git fetch --all
-sudo -u git -H git checkout v0.7.8
+sudo -u git -H git checkout v0.7.11
 sudo -u git -H make
 ```
 
diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md
new file mode 100644
index 0000000..076696f
--- /dev/null
+++ b/doc/update/8.11-to-8.12.md
@@ -0,0 +1,199 @@
+# From 8.11 to 8.12
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+    sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+If you are you running Ruby 2.1.x, you do not _need_ to upgrade Ruby yet, but you should note that support for 2.1.x is deprecated and we will require 2.3.x in 8.13. It's strongly recommended that you upgrade as soon as possible.
+
+You can check which version you are running with `ruby -v`.
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
+echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711  ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz
+cd ruby-2.3.1
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-12-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-12-stable-ee
+```
+
+### 5. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v3.6.0
+```
+
+### 6. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v0.8.2
+sudo -u git -H make
+```
+
+### 7. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# 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
+```
+
+### 8. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-11-stable:config/gitlab.yml.example origin/8-12-stable:config/gitlab.yml.example
+```
+
+#### Git configuration
+
+Configure Git to generate packfile bitmaps (introduced in Git 2.0) on
+the GitLab server during `git gc`.
+
+```sh
+sudo -u git -H git config --global repack.writeBitmaps true
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-11-stable:lib/support/nginx/gitlab-ssl origin/8-12-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-11-stable:lib/support/nginx/gitlab origin/8-12-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-12-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]:  https://gitlab.com/gitlab-org/gitlab-ce/blob/8-12-stable/config/initializers/smtp_settings.rb.sample#L13?
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+    sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+### 9. Start application
+
+    sudo service gitlab start
+    sudo service nginx restart
+
+### 10. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+    sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+    sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.11)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.10 to 8.11](8.10-to-8.11.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/user/account/security.md b/doc/user/account/security.md
new file mode 100644
index 0000000..816094b
--- /dev/null
+++ b/doc/user/account/security.md
@@ -0,0 +1,3 @@
+# Account Security
+
+- [Two-Factor Authentication](two_factor_authentication.md)
diff --git a/doc/user/account/two_factor_authentication.md b/doc/user/account/two_factor_authentication.md
new file mode 100644
index 0000000..881358e
--- /dev/null
+++ b/doc/user/account/two_factor_authentication.md
@@ -0,0 +1,68 @@
+# Two-Factor Authentication
+
+## Recovery options
+
+If you lose your code generation device (such as your mobile phone) and you need
+to disable two-factor authentication on your account, you have several options.
+
+### Use a saved recovery code
+
+When you enabled two-factor authentication for your account, a series of
+recovery codes were generated. If you saved those codes somewhere safe, you
+may use one to sign in.
+
+First, enter your username/email and password on the GitLab sign in page. When
+prompted for a two-factor code, enter one of the recovery codes you saved
+previously.
+
+> **Note:** Once a particular recovery code has been used, it cannot be used again.
+  You may still use the other saved recovery codes at a later time.
+
+### Generate new recovery codes using SSH
+
+It's not uncommon for users to forget to save the recovery codes when enabling
+two-factor authentication. If you have an SSH key added to your GitLab account,
+you can generate a new set of recovery codes using SSH.
+
+Run `ssh git at gitlab.example.com 2fa_recovery_codes`. You will be prompted to
+confirm that you wish to generate new codes. If you choose to continue, any
+previously saved codes will be invalidated.
+
+```bash
+$ ssh git at gitlab.example.com 2fa_recovery_codes
+Are you sure you want to generate new two-factor recovery codes?
+Any existing recovery codes you saved will be invalidated. (yes/no)
+yes
+
+Your two-factor authentication recovery codes are:
+
+119135e5a3ebce8e
+11f6v2a498810dcd
+3924c7ab2089c902
+e79a3398bfe4f224
+34bd7b74adbc8861
+f061691d5107df1a
+169bf32a18e63e7f
+b510e7422e81c947
+20dbed24c5e74663
+df9d3b9403b9c9f0
+
+During sign in, use one of the codes above when prompted for
+your two-factor code. Then, visit your Profile Settings and add
+a new device so you do not lose access to your account again.
+```
+
+Next, go to the GitLab sign in page and enter your username/email and password.
+When prompted for a two-factor code, enter one of the recovery codes obtained
+from the command line output.
+
+> **Note:** After signing in, you should immediately visit your **Profile Settings
+  -> Account** to set up two-factor authentication with a new device.
+
+### Ask a GitLab administrator to disable two-factor on your account
+
+If the above two methods are not possible, you may ask a GitLab global
+administrator to disable two-factor authentication for your account. Please
+be aware that this will temporarily leave your account in a less secure state.
+You should sign in and re-enable two-factor authentication as soon as possible
+after the administrator disables it.
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 7fe96e6..c7fda8a 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -66,7 +66,7 @@ dependency to do so. Please see the [github-markup gem readme](https://github.co
 ## Newlines
 
 > If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#newlines
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#newlines
 
 GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p).
 
@@ -86,7 +86,7 @@ Sugar is sweet
 ## Multiple underscores in words
 
 > If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiple-underscores-in-words
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiple-underscores-in-words
 
 It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words:
 
@@ -101,7 +101,7 @@ do_this_and_do_that_and_another_thing
 ## URL auto-linking
 
 > If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#url-auto-linking
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#url-auto-linking
 
 GFM will autolink almost any URL you copy and paste into your text:
 
@@ -122,7 +122,7 @@ GFM will autolink almost any URL you copy and paste into your text:
 ## Multiline Blockquote
 
 > If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiline-blockquote
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiline-blockquote
 
 On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines,
 GFM supports multiline blockquotes fenced by <code>>>></code>:
@@ -156,7 +156,7 @@ you can quote that without having to manually prepend `>` to every line!
 ## Code and Syntax Highlighting
 
 > If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting
 
 _GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a
 list of supported languages visit the Rouge website._
@@ -226,7 +226,7 @@ But let's throw in a <b>tag</b>.
 ## Inline Diff
 
 > If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#inline-diff
 
 With inline diffs tags you can display {+ additions +} or [- deletions -].
 
@@ -242,7 +242,7 @@ However the wrapping tags cannot be mixed as such:
 ## Emoji
 
 > If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji
 
 	Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
 
@@ -307,7 +307,7 @@ GFM also recognizes certain cross-project references:
 ## Task Lists
 
 > If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#task-lists
 
 You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so:
 
@@ -330,7 +330,7 @@ Task lists can only be created in descriptions, not in titles. Task item state c
 ## Videos
 
 > If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#videos
 
 Image tags with a video extension are automatically converted to a video player.
 
@@ -780,7 +780,7 @@ A link starting with a `/` is relative to the wiki root.
 - The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
 - [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
 
-[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md
+[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md
 [rouge]: http://rouge.jneen.net/ "Rouge website"
 [redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
 [^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 6654286..12d5b8f 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -63,7 +63,7 @@ The following table depicts the various user permission levels in a project.
 | Force push to protected branches [^2] |         |            |             |          |        |
 | Remove protected branches [^2]        |         |            |             |          |        |
 
-[^1]: If **Allow guest to access builds** is enabled in CI settings
+[^1]: If **Public pipelines** is enabled in **Project Settings > CI/CD Pipelines**
 [^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
 
 ## Group
@@ -104,6 +104,15 @@ will find the option to flag the user as external.
 By default new users are not set as external users. This behavior can be changed
 by an administrator under **Admin > Application Settings**.
 
+## Project features
+
+Project features like wiki and issues can be hidden from users depending on
+which visibility level you select on project settings.
+
+- Disabled: disabled for everyone
+- Only team members: only team members will see even if your project is public or internal
+- Everyone with access: everyone can see depending on your project visibility level
+
 ## GitLab CI
 
 GitLab CI permissions rely on the role the user has in GitLab. There are four
@@ -129,3 +138,33 @@ instance and project. In addition, all admins can use the admin interface under
 | Add shared runners                    |                 |             |          | ✓      |
 | See events in the system              |                 |             |          | ✓      |
 | Admin interface                       |                 |             |          | ✓      |
+
+### Build permissions
+
+> Changed in GitLab 8.12.
+
+GitLab 8.12 has a completely redesigned build permissions system.
+Read all about the [new model and its implications][new-mod].
+
+This table shows granted privileges for builds triggered by specific types of
+users:
+
+| Action                                      | Guest, Reporter | Developer   | Master   | Admin  |
+|---------------------------------------------|-----------------|-------------|----------|--------|
+| Run CI build                                |                 | ✓           | ✓        | ✓      |
+| Clone source and LFS from current project   |                 | ✓           | ✓        | ✓      |
+| Clone source and LFS from public projects   |                 | ✓           | ✓        | ✓      |
+| Clone source and LFS from internal projects |                 | ✓ [^3]      | ✓ [^3]   | ✓      |
+| Clone source and LFS from private projects  |                 | ✓ [^4]      | ✓ [^4]   | ✓ [^4] |
+| Push source and LFS                         |                 |             |          |        |
+| Pull container images from current project  |                 | ✓           | ✓        | ✓      |
+| Pull container images from public projects  |                 | ✓           | ✓        | ✓      |
+| Pull container images from internal projects|                 | ✓ [^3]      | ✓ [^3]   | ✓      |
+| Pull container images from private projects |                 | ✓ [^4]      | ✓ [^4]   | ✓ [^4] |
+| Push container images to current project    |                 | ✓           | ✓        | ✓      |
+| Push container images to other projects     |                 |             |          |        |
+
+[^3]: Only if user is not external one.
+[^4]: Only if user is a member of the project.
+[ce-18994]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18994
+[new-mod]: project/new_ci_build_permissions_model.md
diff --git a/doc/user/project/builds/artifacts.md b/doc/user/project/builds/artifacts.md
index c93ae1c..88f1863 100644
--- a/doc/user/project/builds/artifacts.md
+++ b/doc/user/project/builds/artifacts.md
@@ -101,4 +101,36 @@ inside GitLab that make that possible.
 
     ![Build artifacts browser](img/build_artifacts_browser.png)
 
+## Downloading the latest build artifacts
+
+It is possible to download the latest artifacts of a build via a well known URL
+so you can use it for scripting purposes.
+
+The structure of the URL is the following:
+
+```
+https://example.com/<namespace>/<project>/builds/artifacts/<ref>/download?job=<job_name>
+```
+
+For example, to download the latest artifacts of the job named `rspec 6 20` of
+the `master` branch of the `gitlab-ce` project that belongs to the `gitlab-org`
+namespace, the URL would be:
+
+```
+https://gitlab.com/gitlab-org/gitlab-ce/builds/artifacts/master/download?job=rspec+6+20
+```
+
+The latest builds are also exposed in the UI in various places. Specifically,
+look for the download button in:
+
+- the main project's page
+- the branches page
+- the tags page
+
+If the latest build has failed to upload the artifacts, you can see that
+information in the UI.
+
+![Latest artifacts button](img/build_latest_artifacts_browser.png)
+
+
 [gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
diff --git a/doc/user/project/builds/img/build_latest_artifacts_browser.png b/doc/user/project/builds/img/build_latest_artifacts_browser.png
new file mode 100644
index 0000000..d8e9071
Binary files /dev/null and b/doc/user/project/builds/img/build_latest_artifacts_browser.png differ
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
new file mode 100644
index 0000000..abef80e
--- /dev/null
+++ b/doc/user/project/cycle_analytics.md
@@ -0,0 +1,114 @@
+# Cycle Analytics
+
+> [Introduced][ce-5986] in GitLab 8.12.
+>
+> **Note:**
+This the first iteration of Cycle Analytics, you can follow the following issue
+to track the changes that are coming to this feature: [#20975][ce-20975].
+
+Cycle Analytics measures the time it takes to go from [an idea to production] for
+each project you have. This is achieved by not only indicating the total time it
+takes to reach at that point, but the total time is broken down into the
+multiple stages an idea has to pass through to be shipped.
+
+Cycle Analytics is that it is tightly coupled with the [GitLab flow] and
+calculates a separate median for each stage.
+
+## Overview
+
+You can find the Cycle Analytics page under your project's **Pipelines > Cycle
+Analytics** tab.
+
+![Cycle Analytics landing page](img/cycle_analytics_landing_page.png)
+
+You can see that there are seven stages in total:
+
+- **Issue** (Tracker)
+    - Median time from issue creation until given a milestone or list label
+      (first assignment, any milestone, milestone date or assignee is not required)
+- **Plan** (Board)
+    - Median time from giving an issue a milestone or label until pushing the
+      first commit
+- **Code** (IDE)
+    - Median time from the first commit until the merge request is created
+- **Test** (CI)
+    - Median total test time for all commits/merges
+- **Review** (Merge Request/MR)
+    - Median time from merge request creation until the merge request is merged
+      (closed merge requests won't be taken into account)
+- **Staging** (Continuous Deployment)
+    - Median time from when the merge request got merged until the deploy to
+      production (production is last stage/environment)
+- **Production** (Total)
+   - Sum of all the above stages excluding the Test (CI) time
+
+## How the data is measured
+
+Cycle Analytics records cycle time so only data on the issues that have been
+deployed to production are measured. In case you just started a new project and
+you have not pushed anything to production, then you will not be able to
+properly see the Cycle Analytics of your project.
+
+Specifically, if your CI is not set up and you have not defined a `production`
+[environment], then you will not have any data.
+
+Below you can see in more detail what the various stages of Cycle Analytics mean.
+
+| **Stage** | **Description** |
+| --------- | --------------- |
+| Issue     | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list][board] created for it. |
+| Plan      | Measures the median time between the action you took for the previous stage, and pushing the first commit to the repository. To make this change tracked, the pushed commit needs to contain the [issue closing pattern], for example `Closes #xxx`, where `xxx` is the number of the issue related to this commit. If the commit does not contain the issue closing pattern, it is not considered to the measurement time of the stage. |
+| Code      | Measures the median time between pushing a first commit (previous stage) and creating a merge request related to that commit. The key to keep the process tracked is include the [issue closing pattern] to the description of the merge request. |
+| Test      | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. `master` is not excluded. It does not attempt to track time for any particular stages. |
+| Review    | Measures the median time taken to review the merge request, between its creation and until it's merged. |
+| Staging   | Measures the median time between merging the merge request until the very first deployment to production. It's tracked by the [environment] set to `production` in your GitLab CI configuration. If there isn't a `production` environment, this is not tracked. |
+| Production| The sum of all time taken to run the entire process, from issue creation to deploying the code to production. |
+
+---
+
+Here's a little explanation of how this works behind the scenes:
+
+1. Issues and merge requests are grouped together in pairs, such that for each
+   `<issue, merge request>` pair, the merge request has `Fixes #xxx` for the
+   corresponding issue. All other issues and merge requests are **not** considered.
+
+1. Then the <issue, merge request> pairs are filtered out. Any merge request
+   that has **not** been deployed to production in the last XX days (specified
+   by the UI - default is 90 days) prohibits these pairs from being considered.
+
+1. For the remaining `<issue, merge request>` pairs, we check the information that
+   we need for the stages, like issue creation date, merge request merge time,
+   etc.
+
+To sum up, anything that doesn't follow the [GitLab flow] won't be tracked at all.
+So, if a merge request doesn't close an issue or an issue is not labeled with a
+label present in the Issue Board or assigned a milestone or a project has no
+`production` environment, the Cycle Analytics dashboard won't present any data
+at all.
+
+## Permissions
+
+The current permissions on the Cycle Analytics dashboard are:
+
+- Public projects - anyone can access
+- Private/internal projects - any member (guest level and above) can access
+
+You can [read more about permissions][permissions] in general.
+
+## More resources
+
+Learn more about Cycle Analytics in the following resources:
+
+- [Cycle Analytics feature page](https://about.gitlab.com/solutions/cycle-analytics/)
+- [Cycle Analytics feature preview](https://about.gitlab.com/2016/09/16/feature-preview-introducing-cycle-analytics/)
+- [Cycle Analytics feature highlight](https://about.gitlab.com/2016/09/21/cycle-analytics-feature-highlight/)
+
+
+[ce-5986]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5986
+[ce-20975]: https://gitlab.com/gitlab-org/gitlab-ce/issues/20975
+[GitLab flow]: ../../workflow/gitlab_flow.md
+[permissions]: ../permissions.md
+[environment]: ../../ci/yaml/README.md#environment
+[board]: issue_board.md#creating-a-new-list
+[idea to production]: https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab
+[issue closing pattern]: issues/automatic_issue_closing.md
diff --git a/doc/user/project/img/cycle_analytics_landing_page.png b/doc/user/project/img/cycle_analytics_landing_page.png
new file mode 100644
index 0000000..4fa42c8
Binary files /dev/null and b/doc/user/project/img/cycle_analytics_landing_page.png differ
diff --git a/doc/user/project/issues/automatic_issue_closing.md b/doc/user/project/issues/automatic_issue_closing.md
new file mode 100644
index 0000000..d6f3a7d
--- /dev/null
+++ b/doc/user/project/issues/automatic_issue_closing.md
@@ -0,0 +1,55 @@
+# Automatic issue closing
+
+>**Note:**
+This is the user docs. In order to change the default issue closing pattern,
+follow the steps in the [administration docs].
+
+When a commit or merge request resolves one or more issues, it is possible to
+automatically have these issues closed when the commit or merge request lands
+in the project's default branch.
+
+If a commit message or merge request description contains a sentence matching
+a certain regular expression, all issues referenced from the matched text will
+be closed. This happens when the commit is pushed to a project's **default**
+branch, or when a commit or merge request is merged into it.
+
+## Default closing pattern value
+
+When not specified, the default issue closing pattern as shown below will be
+used:
+
+```bash
+((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)
+```
+
+Note that `%{issue_ref}` is a complex regular expression defined inside GitLab's
+source code that can match a reference to 1) a local issue (`#123`),
+2) a cross-project issue (`group/project#123`) or 3) a link to an issue
+(`https://gitlab.example.com/group/project/issues/123`).
+
+---
+
+This translates to the following keywords:
+
+- Close, Closes, Closed, Closing, close, closes, closed, closing
+- Fix, Fixes, Fixed, Fixing, fix, fixes, fixed, fixing
+- Resolve, Resolves, Resolved, Resolving, resolve, resolves, resolved, resolving
+
+---
+
+For example the following commit message:
+
+```
+Awesome commit message
+
+Fix #20, Fixes #21 and Closes group/otherproject#22.
+This commit is also related to #17 and fixes #18, #19
+and https://gitlab.example.com/group/otherproject/issues/23.
+```
+
+will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed
+to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as
+it does not match the pattern. It works with multi-line commit messages as well
+as one-liners when used with `git commit -m`.
+
+[administration docs]: ../../../administration/issue_closing_pattern.md
diff --git a/doc/user/project/koding.md b/doc/user/project/koding.md
index e54587f..c56a1ef 100644
--- a/doc/user/project/koding.md
+++ b/doc/user/project/koding.md
@@ -68,7 +68,7 @@ GitLab instance. For details about what's next you can follow
 [this guide](https://www.koding.com/docs/creating-an-aws-stack) from step 8.
 
 Once stack initialized you will see the `README.md` content from your project
-in `Stack Build` wizard, this wizard will let you to build the stack and import
+in `Stack Build` wizard, this wizard will let you build the stack and import
 your project into it. **Once it's completed it will automatically open the
 related vm instead of importing from scratch**.
 
diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md
new file mode 100644
index 0000000..5af9a5d
--- /dev/null
+++ b/doc/user/project/merge_requests.md
@@ -0,0 +1,169 @@
+# Merge Requests
+
+Merge requests allow you to exchange changes you made to source code and
+collaborate with other people on the same project.
+
+## Authorization for merge requests
+
+There are two main ways to have a merge request flow with GitLab:
+
+1. Working with [protected branches][] in a single repository
+1. Working with forks of an authoritative project
+
+[Learn more about the authorization for merge requests.](merge_requests/authorization_for_merge_requests.md)
+
+## Cherry-pick changes
+
+Cherry-pick any commit in the UI by simply clicking the **Cherry-pick** button
+in a merged merge requests or a commit.
+
+[Learn more about cherry-picking changes.](merge_requests/cherry_pick_changes.md)
+
+## Merge when build succeeds
+
+When reviewing a merge request that looks ready to merge but still has one or
+more CI builds running, you can set it to be merged automatically when all
+builds succeed. This way, you don't have to wait for the builds to finish and
+remember to merge the request manually.
+
+[Learn more about merging when build succeeds.](merge_requests/merge_when_build_succeeds.md)
+
+## Resolve discussion comments in merge requests reviews
+
+Keep track of the progress during a code review with resolving comments.
+Resolving comments prevents you from forgetting to address feedback and lets
+you hide discussions that are no longer relevant.
+
+[Read more about resolving discussion comments in merge requests reviews.](merge_requests/merge_request_discussion_resolution.md)
+
+## Resolve conflicts
+
+When a merge request has conflicts, GitLab may provide the option to resolve
+those conflicts in the GitLab UI.
+
+[Learn more about resolving merge conflicts in the UI.](merge_requests/resolve_conflicts.md)
+
+## Revert changes
+
+GitLab implements Git's powerful feature to revert any commit with introducing
+a **Revert** button in merge requests and commit details.
+
+[Learn more about reverting changes in the UI](merge_requests/revert_changes.md)
+
+## Merge requests versions
+
+Every time you push to a branch that is tied to a merge request, a new version
+of merge request diff is created. When you visit a merge request that contains
+more than one pushes, you can select and compare the versions of those merge
+request diffs.
+
+[Read more about the merge requests versions.](merge_requests/versions.md)
+
+## Work In Progress merge requests
+
+To prevent merge requests from accidentally being accepted before they're
+completely ready, GitLab blocks the "Accept" button for merge requests that
+have been marked as a **Work In Progress**.
+
+[Learn more about settings a merge request as "Work In Progress".](merge_requests/work_in_progress_merge_requests.md)
+
+## Ignore whitespace changes in Merge Request diff view
+
+If you click the **Hide whitespace changes** button, you can see the diff
+without whitespace changes (if there are any). This is also working when on a
+specific commit page.
+
+![MR diff](merge_requests/img/merge_request_diff.png)
+
+>**Tip:**
+You can append `?w=1` while on the diffs page of a merge request to ignore any
+whitespace changes.
+
+## Tips
+
+Here are some tips that will help you be more efficient with merge requests in
+the command line.
+
+> **Note:**
+This section might move in its own document in the future.
+
+### Checkout merge requests locally
+
+A merge request contains all the history from a repository, plus the additional
+commits added to the branch associated with the merge request. Here's a few
+tricks to checkout a merge request locally.
+
+Please note that you can checkout a merge request locally even if the source
+project is a fork (even a private fork) of the target project.
+
+#### Checkout locally by adding a git alias
+
+Add the following alias to your `~/.gitconfig`:
+
+```
+[alias]
+    mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' -
+```
+
+Now you can check out a particular merge request from any repository and any
+remote. For example, to check out the merge request with ID 5 as shown in GitLab
+from the `upstream` remote, do:
+
+```
+git mr upstream 5
+```
+
+This will fetch the merge request into a local `mr-upstream-5` branch and check
+it out.
+
+#### Checkout locally by modifying `.git/config` for a given repository
+
+Locate the section for your GitLab remote in the `.git/config` file. It looks
+like this:
+
+```
+[remote "origin"]
+  url = https://gitlab.com/gitlab-org/gitlab-ce.git
+  fetch = +refs/heads/*:refs/remotes/origin/*
+```
+
+You can open the file with:
+
+```
+git config -e
+```
+
+Now add the following line to the above section:
+
+```
+fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
+```
+
+In the end, it should look like this:
+
+```
+[remote "origin"]
+  url = https://gitlab.com/gitlab-org/gitlab-ce.git
+  fetch = +refs/heads/*:refs/remotes/origin/*
+  fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
+```
+
+Now you can fetch all the merge requests:
+
+```
+git fetch origin
+
+...
+From https://gitlab.com/gitlab-org/gitlab-ce.git
+ * [new ref]         refs/merge-requests/1/head -> origin/merge-requests/1
+ * [new ref]         refs/merge-requests/2/head -> origin/merge-requests/2
+...
+```
+
+And to check out a particular merge request:
+
+```
+git checkout origin/merge-requests/1
+```
+
+[protected branches]: protected_branches.md
diff --git a/doc/user/project/merge_requests/authorization_for_merge_requests.md b/doc/user/project/merge_requests/authorization_for_merge_requests.md
new file mode 100644
index 0000000..59b3fe7
--- /dev/null
+++ b/doc/user/project/merge_requests/authorization_for_merge_requests.md
@@ -0,0 +1,56 @@
+# Authorization for Merge requests
+
+There are two main ways to have a merge request flow with GitLab:
+
+1. Working with [protected branches] in a single repository.
+1. Working with forks of an authoritative project.
+
+## Protected branch flow
+
+With the protected branch flow everybody works within the same GitLab project.
+
+The project maintainers get Master access and the regular developers get
+Developer access.
+
+The maintainers mark the authoritative branches as 'Protected'.
+
+The developers push feature branches to the project and create merge requests
+to have their feature branches reviewed and merged into one of the protected
+branches.
+
+By default, only users with Master access can merge changes into a protected
+branch.
+
+**Advantages**
+
+- Fewer projects means less clutter.
+- Developers need to consider only one remote repository.
+
+**Disadvantages**
+
+- Manual setup of protected branch required for each new project
+
+## Forking workflow
+
+With the forking workflow the maintainers get Master access and the regular
+developers get Reporter access to the authoritative repository, which prohibits
+them from pushing any changes to it.
+
+Developers create forks of the authoritative project and push their feature
+branches to their own forks.
+
+To get their changes into master they need to create a merge request across
+forks.
+
+**Advantages**
+
+- In an appropriately configured GitLab group, new projects automatically get
+  the required access restrictions for regular developers: fewer manual steps
+  to configure authorization for new projects.
+
+**Disadvantages**
+
+- The project need to keep their forks up to date, which requires more advanced
+  Git skills (managing multiple remotes).
+
+[protected branches]: ../protected_branches.md
diff --git a/doc/workflow/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md
similarity index 100%
copy from doc/workflow/cherry_pick_changes.md
copy to doc/user/project/merge_requests/cherry_pick_changes.md
diff --git a/doc/workflow/img/cherry_pick_changes_commit.png b/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png
similarity index 100%
rename from doc/workflow/img/cherry_pick_changes_commit.png
rename to doc/user/project/merge_requests/img/cherry_pick_changes_commit.png
diff --git a/doc/workflow/img/cherry_pick_changes_commit_modal.png b/doc/user/project/merge_requests/img/cherry_pick_changes_commit_modal.png
similarity index 100%
rename from doc/workflow/img/cherry_pick_changes_commit_modal.png
rename to doc/user/project/merge_requests/img/cherry_pick_changes_commit_modal.png
diff --git a/doc/workflow/img/cherry_pick_changes_mr.png b/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png
similarity index 100%
rename from doc/workflow/img/cherry_pick_changes_mr.png
rename to doc/user/project/merge_requests/img/cherry_pick_changes_mr.png
diff --git a/doc/workflow/img/cherry_pick_changes_mr_modal.png b/doc/user/project/merge_requests/img/cherry_pick_changes_mr_modal.png
similarity index 100%
rename from doc/workflow/img/cherry_pick_changes_mr_modal.png
rename to doc/user/project/merge_requests/img/cherry_pick_changes_mr_modal.png
diff --git a/doc/workflow/merge_requests/commit_compare.png b/doc/user/project/merge_requests/img/commit_compare.png
similarity index 100%
rename from doc/workflow/merge_requests/commit_compare.png
rename to doc/user/project/merge_requests/img/commit_compare.png
diff --git a/doc/user/project/merge_requests/img/merge_request_diff.png b/doc/user/project/merge_requests/img/merge_request_diff.png
new file mode 100644
index 0000000..06ee490
Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_request_diff.png differ
diff --git a/doc/workflow/merge_when_build_succeeds/enable.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_enable.png
similarity index 100%
rename from doc/workflow/merge_when_build_succeeds/enable.png
rename to doc/user/project/merge_requests/img/merge_when_build_succeeds_enable.png
diff --git a/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_msg.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_msg.png
new file mode 100644
index 0000000..6b9756b
Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_msg.png differ
diff --git a/doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_settings.png
similarity index 100%
rename from doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.png
rename to doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_settings.png
diff --git a/doc/workflow/merge_when_build_succeeds/status.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_status.png
similarity index 100%
rename from doc/workflow/merge_when_build_succeeds/status.png
rename to doc/user/project/merge_requests/img/merge_when_build_succeeds_status.png
diff --git a/doc/workflow/img/revert_changes_commit.png b/doc/user/project/merge_requests/img/revert_changes_commit.png
similarity index 100%
rename from doc/workflow/img/revert_changes_commit.png
rename to doc/user/project/merge_requests/img/revert_changes_commit.png
diff --git a/doc/workflow/img/revert_changes_commit_modal.png b/doc/user/project/merge_requests/img/revert_changes_commit_modal.png
similarity index 100%
rename from doc/workflow/img/revert_changes_commit_modal.png
rename to doc/user/project/merge_requests/img/revert_changes_commit_modal.png
diff --git a/doc/workflow/img/revert_changes_mr.png b/doc/user/project/merge_requests/img/revert_changes_mr.png
similarity index 100%
rename from doc/workflow/img/revert_changes_mr.png
rename to doc/user/project/merge_requests/img/revert_changes_mr.png
diff --git a/doc/workflow/img/revert_changes_mr_modal.png b/doc/user/project/merge_requests/img/revert_changes_mr_modal.png
similarity index 100%
rename from doc/workflow/img/revert_changes_mr_modal.png
rename to doc/user/project/merge_requests/img/revert_changes_mr_modal.png
diff --git a/doc/user/project/merge_requests/img/versions-compare.png b/doc/user/project/merge_requests/img/versions-compare.png
new file mode 100644
index 0000000..890cae7
Binary files /dev/null and b/doc/user/project/merge_requests/img/versions-compare.png differ
diff --git a/doc/user/project/merge_requests/img/versions-dropdown.png b/doc/user/project/merge_requests/img/versions-dropdown.png
new file mode 100644
index 0000000..9bab930
Binary files /dev/null and b/doc/user/project/merge_requests/img/versions-dropdown.png differ
diff --git a/doc/user/project/merge_requests/img/versions.png b/doc/user/project/merge_requests/img/versions.png
new file mode 100644
index 0000000..6c86f2c
Binary files /dev/null and b/doc/user/project/merge_requests/img/versions.png differ
diff --git a/doc/workflow/wip_merge_requests/blocked_accept_button.png b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
similarity index 100%
rename from doc/workflow/wip_merge_requests/blocked_accept_button.png
rename to doc/user/project/merge_requests/img/wip_blocked_accept_button.png
diff --git a/doc/workflow/wip_merge_requests/mark_as_wip.png b/doc/user/project/merge_requests/img/wip_mark_as_wip.png
similarity index 100%
rename from doc/workflow/wip_merge_requests/mark_as_wip.png
rename to doc/user/project/merge_requests/img/wip_mark_as_wip.png
diff --git a/doc/workflow/wip_merge_requests/unmark_as_wip.png b/doc/user/project/merge_requests/img/wip_unmark_as_wip.png
similarity index 100%
rename from doc/workflow/wip_merge_requests/unmark_as_wip.png
rename to doc/user/project/merge_requests/img/wip_unmark_as_wip.png
diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md
new file mode 100644
index 0000000..011f9cb
--- /dev/null
+++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md
@@ -0,0 +1,46 @@
+# Merge When Build Succeeds
+
+When reviewing a merge request that looks ready to merge but still has one or
+more CI builds running, you can set it to be merged automatically when all
+builds succeed. This way, you don't have to wait for the builds to finish and
+remember to merge the request manually.
+
+![Enable](img/merge_when_build_succeeds_enable.png)
+
+When you hit the "Merge When Build Succeeds" button, the status of the merge
+request will be updated to represent the impending merge. If you cannot wait
+for the build to succeed and want to merge immediately, this option is available
+in the dropdown menu on the right of the main button.
+
+Both team developers and the author of the merge request have the option to
+cancel the automatic merge if they find a reason why it shouldn't be merged
+after all.
+
+![Status](img/merge_when_build_succeeds_status.png)
+
+When the build succeeds, the merge request will automatically be merged. When
+the build fails, the author gets a chance to retry any failed builds, or to
+push new commits to fix the failure.
+
+When the builds are retried and succeed on the second try, the merge request
+will automatically be merged after all. When the merge request is updated with
+new commits, the automatic merge is automatically canceled to allow the new
+changes to be reviewed.
+
+## Only allow merge requests to be merged if the build succeeds
+
+> **Note:**
+You need to have builds configured to enable this feature.
+
+You can prevent merge requests from being merged if their build did not succeed.
+
+Navigate to your project's settings page, select the
+**Only allow merge requests to be merged if the build succeeds** check box and
+hit **Save** for the changes to take effect.
+
+![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png)
+
+From now on, every time the build fails you will not be able to merge the merge
+request from the UI, until you make the build pass.
+
+![Only allow merge if build succeeds msg](img/merge_when_build_succeeds_only_if_succeeds_msg.png)
diff --git a/doc/workflow/revert_changes.md b/doc/user/project/merge_requests/revert_changes.md
similarity index 100%
copy from doc/workflow/revert_changes.md
copy to doc/user/project/merge_requests/revert_changes.md
diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
new file mode 100644
index 0000000..2805fdf
--- /dev/null
+++ b/doc/user/project/merge_requests/versions.md
@@ -0,0 +1,32 @@
+# Merge requests versions
+
+> Will be [introduced][ce-5467] in GitLab 8.12.
+
+Every time you push to a branch that is tied to a merge request, a new version
+of merge request diff is created. When you visit a merge request that contains
+more than one pushes, you can select and compare the versions of those merge
+request diffs.
+
+![Merge Request Versions](img/versions.png)
+
+By default, the latest version of changes is shown. However, you
+can select an older one from version dropdown.
+
+![Merge Request Versions](img/versions-dropdown.png)
+
+You can also compare the merge request version with older one to see what is
+changed since then.
+
+![Merge Request Versions](img/versions-compare.png)
+
+Please note that comments are disabled while viewing outdated merge versions
+or comparing to versions other than base.
+
+---
+
+>**Note:**
+Merge request versions are based on push not on commit. So, if you pushed 5
+commits in a single push, it will be a single option in the dropdown. If you
+pushed 5 times, that will count for 5 options.
+
+[ce-5467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5467
diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
new file mode 100644
index 0000000..546c8bd
--- /dev/null
+++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
@@ -0,0 +1,17 @@
+# "Work In Progress" Merge Requests
+
+To prevent merge requests from accidentally being accepted before they're
+completely ready, GitLab blocks the "Accept" button for merge requests that
+have been marked a **Work In Progress**.
+
+![Blocked Accept Button](img/wip_blocked_accept_button.png)
+
+To mark a merge request a Work In Progress, simply start its title with `[WIP]`
+or `WIP:`.
+
+![Mark as WIP](img/wip_mark_as_wip.png)
+
+To allow a Work In Progress merge request to be accepted again when it's ready,
+simply remove the `WIP` prefix.
+
+![Unark as WIP](img/wip_unmark_as_wip.png)
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
new file mode 100644
index 0000000..e73f600
--- /dev/null
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -0,0 +1,289 @@
+# New CI build permissions model
+
+> Introduced in GitLab 8.12.
+
+GitLab 8.12 has a completely redesigned [build permissions] system. You can find
+all discussion and all our concerns when choosing the current approach in issue
+[#18994](https://gitlab.com/gitlab-org/gitlab-ce/issues/18994).
+
+---
+
+Builds permissions should be tightly integrated with the permissions of a user
+who is triggering a build.
+
+The reasons to do it like that are:
+
+- We already have a permissions system in place: group and project membership
+  of users.
+- We already fully know who is triggering a build (using `git push`, using the
+  web UI, executing triggers).
+- We already know what user is allowed to do.
+- We use the user permissions for builds that are triggered by the user.
+- It opens a lot of possibilities to further enforce user permissions, like
+  allowing only specific users to access runners or use secure variables and
+  environments.
+- It is simple and convenient that your build can access everything that you
+  as a user have access to.
+- Short living unique tokens are now used, granting access for time of the build
+  and maximizing security.
+
+With the new behavior, any build that is triggered by the user, is also marked
+with their permissions. When a user does a `git push` or changes files through
+the web UI, a new pipeline will be usually created. This pipeline will be marked
+as created be the pusher (local push or via the UI) and any build created in this
+pipeline will have the permissions of the pusher.
+
+This allows us to make it really easy to evaluate the access for all projects
+that have Git submodules or are using container images that the pusher would
+have access too. **The permission is granted only for time that build is running.
+The access is revoked after the build is finished.**
+
+## Types of users
+
+It is important to note that we have a few types of users:
+
+- **Administrators**: CI builds created by Administrators will not have access
+  to all GitLab projects, but only to projects and container images of projects
+  that the administrator is a member of.That means that if a project is either
+  public or internal users have access anyway, but if a project is private, the
+  Administrator will have to be a member of it in order to have access to it
+  via another project's build.
+
+- **External users**: CI builds created by [external users][ext] will have
+  access only to projects to which user has at least reporter access. This
+  rules out accessing all internal projects by default,
+
+This allows us to make the CI and permission system more trustworthy.
+Let's consider the following scenario:
+
+1. You are an employee of a company. Your company has a number of internal tools
+   hosted in private repositories and you have multiple CI builds that make use
+   of these repositories.
+
+2. You invite a new [external user][ext]. CI builds created by that user do not
+   have access to internal repositories, because the user also doesn't have the
+   access from within GitLab. You as an employee have to grant explicit access
+   for this user. This allows us to prevent from accidental data leakage.
+
+## Build token
+
+A unique build token is generated for each build and it allows the user to
+access all projects that would be normally accessible to the user creating that
+build.
+
+We try to make sure that this token doesn't leak by:
+
+1. Securing all API endpoints to not expose the build token.
+1. Masking the build token from build logs.
+1. Allowing to use the build token **only** when build is running.
+
+However, this brings a question about the Runners security. To make sure that
+this token doesn't leak, you should also make sure that you configure
+your Runners in the most possible secure way, by avoiding the following:
+
+1. Any usage of Docker's `privileged` mode is risky if the machines are re-used.
+1. Using the `shell` executor since builds run on the same machine.
+
+By using an insecure GitLab Runner configuration, you allow the rogue developers
+to steal the tokens of other builds.
+
+## Debugging problems
+
+With the new permission model in place, there may be times that your build will
+fail. This is most likely because your project tries to access other project's
+sources, and you don't have the appropriate permissions. In the build log look
+for information about 403 or forbidden access messages
+
+As an Administrator, you can verify that the user is a member of the group or
+project they're trying to have access to, and you can impersonate the user to
+retry the failing build in order to verify that everything is correct.
+
+## Build triggers
+
+[Build triggers][triggers] do not support the new permission model.
+They continue to use the old authentication mechanism where the CI build
+can access only its own sources. We plan to remove that limitation in one of
+the upcoming releases.
+
+## Before GitLab 8.12
+
+In versions before GitLab 8.12, all CI builds would use the CI Runner's token
+to checkout project sources.
+
+The project's Runner's token was a token that you could find under the
+project's **Settings > CI/CD Pipelines** and was limited to access only that
+project.
+It could be used for registering new specific Runners assigned to the project
+and to checkout project sources.
+It could also be used with the GitLab Container Registry for that project,
+allowing pulling and pushing Docker images from within the CI build.
+
+---
+
+GitLab would create a special checkout URL like:
+
+```
+https://gitlab-ci-token:<project-runners-token>/gitlab.com/gitlab-org/gitlab-ce.git
+```
+
+And then the users could also use it in their CI builds all Docker related
+commands to interact with GitLab Container Registry. For example:
+
+```
+docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
+```
+
+Using single token had multiple security implications:
+
+- The token would be readable to anyone who had developer access to a project
+  that could run CI builds, allowing the developer to register any specific
+  Runner for that project.
+- The token would allow to access only the project's sources, forbidding from
+  accessing any other projects.
+- The token was not expiring and was multi-purpose: used for checking out sources,
+  for registering specific runners and for accessing a project's container
+  registry with read-write permissions.
+
+All the above led to a new permission model for builds that was introduced
+with GitLab 8.12.
+
+## Making use of the new CI build permissions model
+
+With the new build permission model, there is now an easy way to access all
+dependent source code in a project. That way, we can:
+
+1. Access a project's Git submodules
+1. Access private container images
+1. Access project's and submodule LFS objects
+
+Let's see how that works with Git submodules and private Docker images hosted on
+the container registry.
+
+## Git submodules
+
+>
+It often happens that while working on one project, you need to use another
+project from within it; perhaps it’s a library that a third party developed or
+you’re developing a project separately and are using it in multiple parent
+projects.
+A common issue arises in these scenarios: you want to be able to treat the two
+projects as separate yet still be able to use one from within the other.
+>
+_Excerpt from the [Git website][git-scm] about submodules._
+
+If dealing with submodules, your project will probably have a file named
+`.gitmodules`. And this is how it usually looks like:
+
+```
+[submodule "tools"]
+	path = tools
+	url = git at gitlab.com/group/tools.git
+```
+
+> **Note:**
+If you are **not** using GitLab 8.12 or higher, you would need to work your way
+around this issue in order to access the sources of `gitlab.com/group/tools`
+(e.g., use [SSH keys](../ssh_keys/README.md)).
+>
+With GitLab 8.12 onward, your permissions are used to evaluate what a CI build
+can access. More information about how this system works can be found in the
+[Build permissions model](../../user/permissions.md#builds-permissions).
+
+To make use of the new changes, you have to update your `.gitmodules` file to
+use a relative URL.
+
+Let's consider the following example:
+
+1. Your project is located at `https://gitlab.com/secret-group/my-project`.
+1. To checkout your sources you usually use an SSH address like
+   `git at gitlab.com:secret-group/my-project.git`.
+1. Your project depends on `https://gitlab.com/group/tools`.
+1. You have the `.gitmodules` file with above content.
+
+Since Git allows the usage of relative URLs for your `.gitmodules` configuration,
+this easily allows you to use HTTP for cloning all your CI builds and SSH
+for all your local checkouts.
+
+For example, if you change the `url` of your `tools` dependency, from
+`git at gitlab.com/group/tools.git` to `../../group/tools.git`, this will instruct
+Git to automatically deduce the URL that should be used when cloning sources.
+Whether you use HTTP or SSH, Git will use that same channel and it will allow
+to make all your CI builds use HTTPS (because GitLab CI uses HTTPS for cloning
+your sources), and all your local clones will continue using SSH.
+
+Given the above explanation, your `.gitmodules` file should eventually look
+like this:
+
+```
+[submodule "tools"]
+	path = tools
+	url = ../../group/tools.git
+```
+
+However, you have to explicitly tell GitLab CI to clone your submodules as this
+is not done automatically. You can achieve that by adding a `before_script`
+section to your `.gitlab-ci.yml`:
+
+```
+before_script:
+  - git submodule update --init --recursive
+
+test:
+  script:
+    - run-my-tests
+```
+
+This will make GitLab CI initialize (fetch) and update (checkout) all your
+submodules recursively.
+
+In case your environment or your Docker image doesn't have Git installed,
+you have to either ask your Administrator or install the missing dependency
+yourself:
+
+```
+# Debian / Ubuntu
+before_script:
+  - apt-get update -y
+  - apt-get install -y git-core
+  - git submodule update --init --recursive
+
+# CentOS / RedHat
+before_script:
+  - yum install git
+  - git submodule update --init --recursive
+
+# Alpine
+before_script:
+  - apk add -U git
+  - git submodule update --init --recursive
+```
+
+### Container Registry
+
+With the update permission model we also extended the support for accessing
+Container Registries for private projects.
+
+> **Note:**
+As GitLab Runner 1.6 doesn't yet incorporate the introduced changes for
+permissions, this makes the `image:` directive to not work with private projects
+automatically. The manual configuration by an Administrator is required to use
+private images. We plan to remove that limitation in one of the upcoming releases.
+
+Your builds can access all container images that you would normally have access
+to. The only implication is that you can push to the Container Registry of the
+project for which the build is triggered.
+
+This is how an example usage can look like:
+
+```
+test:
+  script:
+    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
+    - docker pull $CI_REGISTRY/group/other-project:latest
+    - docker run $CI_REGISTRY/group/other-project:latest
+```
+
+[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
+[build permissions]: ../permissions.md#builds-permissions
+[ext]: ../permissions.md#external-users
+[triggers]: ../../ci/triggers/README.md
diff --git a/doc/workflow/img/web_editor_new_branch_dropdown.png b/doc/user/project/repository/img/web_editor_new_branch_dropdown.png
similarity index 100%
rename from doc/workflow/img/web_editor_new_branch_dropdown.png
rename to doc/user/project/repository/img/web_editor_new_branch_dropdown.png
diff --git a/doc/workflow/img/web_editor_new_branch_page.png b/doc/user/project/repository/img/web_editor_new_branch_page.png
similarity index 100%
rename from doc/workflow/img/web_editor_new_branch_page.png
rename to doc/user/project/repository/img/web_editor_new_branch_page.png
diff --git a/doc/workflow/img/web_editor_new_directory_dialog.png b/doc/user/project/repository/img/web_editor_new_directory_dialog.png
similarity index 100%
rename from doc/workflow/img/web_editor_new_directory_dialog.png
rename to doc/user/project/repository/img/web_editor_new_directory_dialog.png
diff --git a/doc/workflow/img/web_editor_new_directory_dropdown.png b/doc/user/project/repository/img/web_editor_new_directory_dropdown.png
similarity index 100%
rename from doc/workflow/img/web_editor_new_directory_dropdown.png
rename to doc/user/project/repository/img/web_editor_new_directory_dropdown.png
diff --git a/doc/workflow/img/web_editor_new_file_dropdown.png b/doc/user/project/repository/img/web_editor_new_file_dropdown.png
similarity index 100%
rename from doc/workflow/img/web_editor_new_file_dropdown.png
rename to doc/user/project/repository/img/web_editor_new_file_dropdown.png
diff --git a/doc/workflow/img/web_editor_new_file_editor.png b/doc/user/project/repository/img/web_editor_new_file_editor.png
similarity index 100%
rename from doc/workflow/img/web_editor_new_file_editor.png
rename to doc/user/project/repository/img/web_editor_new_file_editor.png
diff --git a/doc/workflow/img/web_editor_new_push_widget.png b/doc/user/project/repository/img/web_editor_new_push_widget.png
similarity index 100%
rename from doc/workflow/img/web_editor_new_push_widget.png
rename to doc/user/project/repository/img/web_editor_new_push_widget.png
diff --git a/doc/workflow/img/web_editor_new_tag_dropdown.png b/doc/user/project/repository/img/web_editor_new_tag_dropdown.png
similarity index 100%
rename from doc/workflow/img/web_editor_new_tag_dropdown.png
rename to doc/user/project/repository/img/web_editor_new_tag_dropdown.png
diff --git a/doc/workflow/img/web_editor_new_tag_page.png b/doc/user/project/repository/img/web_editor_new_tag_page.png
similarity index 100%
rename from doc/workflow/img/web_editor_new_tag_page.png
rename to doc/user/project/repository/img/web_editor_new_tag_page.png
diff --git a/doc/workflow/img/web_editor_start_new_merge_request.png b/doc/user/project/repository/img/web_editor_start_new_merge_request.png
similarity index 100%
rename from doc/workflow/img/web_editor_start_new_merge_request.png
rename to doc/user/project/repository/img/web_editor_start_new_merge_request.png
diff --git a/doc/user/project/repository/img/web_editor_template_dropdown_buttons.png b/doc/user/project/repository/img/web_editor_template_dropdown_buttons.png
new file mode 100644
index 0000000..4efc51c
Binary files /dev/null and b/doc/user/project/repository/img/web_editor_template_dropdown_buttons.png differ
diff --git a/doc/user/project/repository/img/web_editor_template_dropdown_first_file.png b/doc/user/project/repository/img/web_editor_template_dropdown_first_file.png
new file mode 100644
index 0000000..67190c5
Binary files /dev/null and b/doc/user/project/repository/img/web_editor_template_dropdown_first_file.png differ
diff --git a/doc/user/project/repository/img/web_editor_template_dropdown_mit_license.png b/doc/user/project/repository/img/web_editor_template_dropdown_mit_license.png
new file mode 100644
index 0000000..4771911
Binary files /dev/null and b/doc/user/project/repository/img/web_editor_template_dropdown_mit_license.png differ
diff --git a/doc/workflow/img/web_editor_upload_file_dialog.png b/doc/user/project/repository/img/web_editor_upload_file_dialog.png
similarity index 100%
rename from doc/workflow/img/web_editor_upload_file_dialog.png
rename to doc/user/project/repository/img/web_editor_upload_file_dialog.png
diff --git a/doc/workflow/img/web_editor_upload_file_dropdown.png b/doc/user/project/repository/img/web_editor_upload_file_dropdown.png
similarity index 100%
rename from doc/workflow/img/web_editor_upload_file_dropdown.png
rename to doc/user/project/repository/img/web_editor_upload_file_dropdown.png
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
new file mode 100644
index 0000000..993c6bf
--- /dev/null
+++ b/doc/user/project/repository/web_editor.md
@@ -0,0 +1,175 @@
+# GitLab Web Editor
+
+Sometimes it's easier to make quick changes directly from the GitLab interface
+than to clone the project and use the Git command line tool. In this feature
+highlight we look at how you can create a new file, directory, branch or
+tag from the file browser. All of these actions are available from a single
+dropdown menu.
+
+## Create a file
+
+From a project's files page, click the '+' button to the right of the branch selector.
+Choose **New file** from the dropdown.
+
+![New file dropdown menu](img/web_editor_new_file_dropdown.png)
+
+---
+
+Enter a file name in the **File name** box. Then, add file content in the editor
+area. Add a descriptive commit message and choose a branch. The branch field
+will default to the branch you were viewing in the file browser. If you enter
+a new branch name, a checkbox will appear allowing you to start a new merge
+request after you commit the changes.
+
+When you are satisfied with your new file, click **Commit Changes** at the bottom.
+
+![Create file editor](img/web_editor_new_file_editor.png)
+
+### Template dropdowns
+
+When starting a new project, there are some common files which the new project
+might need too. Therefore a message will be displayed by GitLab to make this
+easy for you.
+
+![First file for your project](img/web_editor_template_dropdown_first_file.png)
+
+When clicking on either `LICENSE` or `.gitignore`, a dropdown will be displayed
+to provide you with a template which might be suitable for your project.
+
+![MIT license selected](img/web_editor_template_dropdown_mit_license.png)
+
+The license, changelog, contribution guide, or `.gitlab-ci.yml` file could also
+be added through a button on the project page. In the example below the license
+has already been created, which creates a link to the license itself.
+
+![New file button](img/web_editor_template_dropdown_buttons.png)
+
+>**Note:**
+The **Set up CI** button will not appear on an empty repository. You have to at
+least add a file in order for the button to show up.
+
+## Upload a file
+
+The ability to create a file is great when the content is text. However, this
+doesn't work well for binary data such as images, PDFs or other file types. In
+this case you need to upload a file.
+
+From a project's files page, click the '+' button to the right of the branch
+selector. Choose **Upload file** from the dropdown.
+
+![Upload file dropdown menu](img/web_editor_upload_file_dropdown.png)
+
+---
+
+Once the upload dialog pops up there are two ways to upload your file. Either
+drag and drop a file on the pop up or use the **click to upload** link. A file
+preview will appear once you have selected a file to upload.
+
+Enter a commit message, choose a branch, and click **Upload file** when you are
+ready.
+
+![Upload file dialog](img/web_editor_upload_file_dialog.png)
+
+## Create a directory
+
+To keep files in the repository organized it is often helpful to create a new
+directory.
+
+From a project's files page, click the '+' button to the right of the branch selector.
+Choose **New directory** from the dropdown.
+
+![New directory dropdown](img/web_editor_new_directory_dropdown.png)
+
+---
+
+In the new directory dialog enter a directory name, a commit message and choose
+the target branch. Click **Create directory** to finish.
+
+![New directory dialog](img/web_editor_new_directory_dialog.png)
+
+## Create a new branch
+
+There are multiple ways to create a branch from GitLab's web interface.
+
+### Create a new branch from an issue
+
+> [Introduced][ce-2808] in GitLab 8.6.
+
+In case your development workflow dictates to have an issue for every merge
+request, you can quickly create a branch right on the issue page which will be
+tied with the issue itself. You can see a **New Branch** button after the issue
+description, unless there is already a branch with the same name or a referenced
+merge request.
+
+![New Branch Button](img/new_branch_from_issue.png)
+
+Once you click it, a new branch will be created that diverges from the default
+branch of your project, by default `master`. The branch name will be based on
+the title of the issue and as suffix it will have its ID. Thus, the example
+screenshot above will yield a branch named
+`2-et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum`.
+
+After the branch is created, you can edit files in the repository to fix
+the issue. When a merge request is created based on the newly created branch,
+the description field will automatically display the [issue closing pattern]
+`Closes #ID`, where `ID` the ID of the issue. This will close the issue once the
+merge request is merged.
+
+### Create a new branch from a project's dashboard
+
+If you want to make changes to several files before creating a new merge
+request, you can create a new branch up front. From a project's files page,
+choose **New branch** from the dropdown.
+
+![New branch dropdown](img/web_editor_new_branch_dropdown.png)
+
+---
+
+Enter a new **Branch name**. Optionally, change the **Create from** field
+to choose which branch, tag or commit SHA this new branch will originate from.
+This field will autocomplete if you start typing an existing branch or tag.
+Click **Create branch** and you will be returned to the file browser on this new
+branch.
+
+![New branch page](img/web_editor_new_branch_page.png)
+
+---
+
+You can now make changes to any files, as needed. When you're ready to merge
+the changes back to master you can use the widget at the top of the screen.
+This widget only appears for a period of time after you create the branch or
+modify files.
+
+![New push widget](img/web_editor_new_push_widget.png)
+
+## Create a new tag
+
+Tags are useful for marking major milestones such as production releases,
+release candidates, and more. You can create a tag from a branch or a commit
+SHA. From a project's files page, choose **New tag** from the dropdown.
+
+![New tag dropdown](img/web_editor_new_tag_dropdown.png)
+
+---
+
+Give the tag a name such as `v1.0.0`. Choose the branch or SHA from which you
+would like to create this new tag. You can optionally add a message and
+release notes. The release notes section supports markdown format and you can
+also upload an attachment. Click **Create tag** and you will be taken to the tag
+list page.
+
+![New tag page](img/web_editor_new_tag_page.png)
+
+## Tips
+
+When creating or uploading a new file, or creating a new directory, you can
+trigger a new merge request rather than committing directly to master. Enter
+a new branch name in the **Target branch** field. You will notice a checkbox
+appear that is labeled **Start a new merge request with these changes**. After
+you commit the changes you will be taken to a new merge request form.
+
+![Start a new merge request with these changes](img/web_editor_start_new_merge_request.png)
+
+![New file button](basicsimages/file_button.png)
+[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808
+[issue closing pattern]: ../user/project/issues/automatic_issue_closing.md
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 08ff89c..445c0ee 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -3,8 +3,8 @@
 >**Notes:**
 >
 >  - [Introduced][ce-3050] in GitLab 8.9.
->  - Importing will not be possible if the import instance version is lower
->    than that of the exporter.
+>  - Importing will not be possible if the import instance version differs from
+>    that of the exporter.
 >  - For existing installations, the project import option has to be enabled in
 >    application settings (`/admin/application_settings`) under 'Import sources'.
 >    You will have to be an administrator to enable and use the import functionality.
@@ -17,6 +17,20 @@
 Existing projects running on any GitLab instance or GitLab.com can be exported
 with all their related data and be moved into a new GitLab instance.
 
+## Version history
+
+| GitLab version | Import/Export version |
+| -------- | -------- |
+| 8.12.0 to current  | 0.1.4    |
+| 8.10.3   | 0.1.3    |
+| 8.10.0   | 0.1.2    |
+| 8.9.5    | 0.1.1    |
+| 8.9.0    | 0.1.0    |
+ 
+ > The table reflects what GitLab version we updated the Import/Export version at.
+ > For instance, 8.10.3 and 8.11 will have the same Import/Export version (0.1.3)
+ > and the exports between them will be compatible.
+
 ## Exported contents
 
 The following items will be exported:
diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md
index 11e1574..1792a0c 100644
--- a/doc/user/project/slash_commands.md
+++ b/doc/user/project/slash_commands.md
@@ -26,5 +26,5 @@ do.
 | `/done`                    | Mark todo as done |
 | `/subscribe`               | Subscribe |
 | `/unsubscribe`             | Unsubscribe |
-| `/due <in 2 days or this Friday or December 31st>` | Set due date |
+| <code>/due <in 2 days | this Friday | December 31st></code> | Set due date |
 | `/remove_due_date`          | Remove due date |
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 0cf5644..2d9bfbc 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -1,7 +1,8 @@
 # Workflow
 
-- [Authorization for merge requests](authorization_for_merge_requests.md)
+- [Automatic issue closing](../user/project/issues/automatic_issue_closing.md)
 - [Change your time zone](timezone.md)
+- [Cycle Analytics](../user/project/cycle_analytics.md)
 - [Description templates](../user/project/description_templates.md)
 - [Feature branch workflow](workflow.md)
 - [GitLab Flow](gitlab_flow.md)
@@ -18,14 +19,18 @@
 - [Slash commands](../user/project/slash_commands.md)
 - [Sharing a project with a group](share_with_group.md)
 - [Share projects with other groups](share_projects_with_other_groups.md)
-- [Web Editor](web_editor.md)
+- [Web Editor](../user/project/repository/web_editor.md)
 - [Releases](releases.md)
 - [Milestones](milestones.md)
-- [Merge Requests](merge_requests.md)
-- [Revert changes](revert_changes.md)
-- [Cherry-pick changes](cherry_pick_changes.md)
-- ["Work In Progress" Merge Requests](wip_merge_requests.md)
-- [Merge When Build Succeeds](merge_when_build_succeeds.md)
+- [Merge Requests](../user/project/merge_requests.md)
+  - [Authorization for merge requests](../user/project/merge_requests/authorization_for_merge_requests.md)
+  - [Cherry-pick changes](../user/project/merge_requests/cherry_pick_changes.md)
+  - [Merge when build succeeds](../user/project/merge_requests/merge_when_build_succeeds.md)
+  - [Resolve discussion comments in merge requests reviews](../user/project/merge_requests/merge_request_discussion_resolution.md)
+  - [Resolve merge conflicts in the UI](../user/project/merge_requests/resolve_conflicts.md)
+  - [Revert changes in the UI](../user/project/merge_requests/revert_changes.md)
+  - [Merge requests versions](../user/project/merge_requests/versions.md)
+  - ["Work In Progress" merge requests](../user/project/merge_requests/work_in_progress_merge_requests.md)
 - [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
 - [Importing from SVN, GitHub, BitBucket, etc](importing/README.md)
 - [Todos](todos.md)
diff --git a/doc/workflow/authorization_for_merge_requests.md b/doc/workflow/authorization_for_merge_requests.md
index d1d6d94..7bf80a3 100644
--- a/doc/workflow/authorization_for_merge_requests.md
+++ b/doc/workflow/authorization_for_merge_requests.md
@@ -1,40 +1 @@
-# Authorization for Merge requests
-
-There are two main ways to have a merge request flow with GitLab: working with protected branches in a single repository, or working with forks of an authoritative project.
-
-## Protected branch flow
-
-With the protected branch flow everybody works within the same GitLab project.
-
-The project maintainers get Master access and the regular developers get Developer access.
-
-The maintainers mark the authoritative branches as 'Protected'.
-
-The developers push feature branches to the project and create merge requests to have their feature branches reviewed and merged into one of the protected branches.
-
-Only users with Master access can merge changes into a protected branch.
-
-### Advantages
-
-- fewer projects means less clutter
-- developers need to consider only one remote repository
-
-### Disadvantages
-
-- manual setup of protected branch required for each new project
-
-## Forking workflow
-
-With the forking workflow the maintainers get Master access and the regular developers get Reporter access to the authoritative repository, which prohibits them from pushing any changes to it.
-
-Developers create forks of the authoritative project and push their feature branches to their own forks.
-
-To get their changes into master they need to create a merge request across forks.
-
-### Advantages
-
-- in an appropriately configured GitLab group, new projects automatically get the required access restrictions for regular developers: fewer manual steps to configure authorization for new projects
-
-### Disadvantages
-
-- the project need to keep their forks up to date, which requires more advanced Git skills (managing multiple remotes)
+This document was moved to [user/project/merge_requests/authorization_for_merge_requests](../user/project/merge_requests/authorization_for_merge_requests.md)
diff --git a/doc/workflow/cherry_pick_changes.md b/doc/workflow/cherry_pick_changes.md
index 64b94d8..663ffd3 100644
--- a/doc/workflow/cherry_pick_changes.md
+++ b/doc/workflow/cherry_pick_changes.md
@@ -1,52 +1 @@
-# Cherry-pick changes
-
-> [Introduced][ce-3514] in GitLab 8.7.
-
----
-
-GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick]
-with introducing a **Cherry-pick** button in Merge Requests and commit details.
-
-## Cherry-picking a Merge Request
-
-After the Merge Request has been merged, a **Cherry-pick** button will be available
-to cherry-pick the changes introduced by that Merge Request:
-
-![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png)
-
----
-
-You can cherry-pick the changes directly into the selected branch or you can opt to
-create a new Merge Request with the cherry-pick changes:
-
-![Cherry-pick Merge Request modal](img/cherry_pick_changes_mr_modal.png)
-
-## Cherry-picking a Commit
-
-You can cherry-pick a Commit from the Commit details page:
-
-![Cherry-pick commit](img/cherry_pick_changes_commit.png)
-
----
-
-Similar to cherry-picking a Merge Request, you can opt to cherry-pick the changes
-directly into the target branch or create a new Merge Request to cherry-pick the
-changes:
-
-![Cherry-pick commit modal](img/cherry_pick_changes_commit_modal.png)
-
----
-
-Please note that when cherry-picking merge commits, the mainline will always be the
-first parent. If you want to use a different mainline then you need to do that
-from the command line.
-
-Here is a quick example to cherry-pick a merge commit using the second parent as the
-mainline:
-
-```bash
-git cherry-pick -m 2 7a39eb0
-```
-
-[ce-3514]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 "Cherry-pick button Merge Request"
-[git-cherry-pick]: https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation"
+This document was moved to [user/project/merge_requests/cherry_pick_changes](../user/project/merge_requests/cherry_pick_changes.md).
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 2b2f140..7c0eb90 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -89,7 +89,7 @@ In this case the master branch is deployed on staging. When someone wants to dep
 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 master is good to go (it should be if you are 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](https://teatro.io/).
 
@@ -115,7 +115,7 @@ In this flow it is not common to have a production branch (or git flow master br
 
 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.
+Tools such as GitLab and others 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.
diff --git a/doc/workflow/importing/img/import_projects_from_github_importer.png b/doc/workflow/importing/img/import_projects_from_github_importer.png
index b6ed8dd..eadd33c 100644
Binary files a/doc/workflow/importing/img/import_projects_from_github_importer.png and b/doc/workflow/importing/img/import_projects_from_github_importer.png differ
diff --git a/doc/workflow/importing/img/import_projects_from_github_new_project_page.png b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png
index c8f35a5..6e91c43 100644
Binary files a/doc/workflow/importing/img/import_projects_from_github_new_project_page.png and b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png differ
diff --git a/doc/workflow/importing/img/import_projects_from_github_select_auth_method.png b/doc/workflow/importing/img/import_projects_from_github_select_auth_method.png
new file mode 100644
index 0000000..c11863a
Binary files /dev/null and b/doc/workflow/importing/img/import_projects_from_github_select_auth_method.png differ
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index 306caab..c36dfdb 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -1,53 +1,118 @@
 # Import your project from GitHub to GitLab
 
+Import your projects from GitHub to GitLab with minimal effort.
+
+## Overview
+
 >**Note:**
-In order to enable the GitHub import setting, you may also want to
-enable the [GitHub integration][gh-import] in your GitLab instance. This
-configuration is optional, you will be able import your GitHub repositories
-with a Personal Access Token.
+If you are an administrator you can enable the [GitHub integration][gh-import]
+in your GitLab instance sitewide. This configuration is optional, users will be
+able import their GitHub repositories with a [personal access token][gh-token].
 
-At its current state, GitHub importer can import:
+- At its current state, GitHub importer can import:
+  - the repository description (GitLab 7.7+)
+  - the Git repository data (GitLab 7.7+)
+  - the issues (GitLab 7.7+)
+  - the pull requests (GitLab 8.4+)
+  - the wiki pages (GitLab 8.4+)
+  - the milestones (GitLab 8.7+)
+  - the labels (GitLab 8.7+)
+  - the release note descriptions (GitLab 8.12+)
+- References to pull requests and issues are preserved (GitLab 8.7+)
+- Repository public access is retained. If a repository is private in GitHub
+  it will be created as private in GitLab as well.
 
-- the repository description (introduced in GitLab 7.7)
-- the git repository data (introduced in GitLab 7.7)
-- the issues (introduced in GitLab 7.7)
-- the pull requests (introduced in GitLab 8.4)
-- the wiki pages (introduced in GitLab 8.4)
-- the milestones (introduced in GitLab 8.7)
-- the labels (introduced in GitLab 8.7)
+## How it works
 
-With GitLab 8.7+, references to pull requests and issues are preserved.
+When issues/pull requests are being imported, the GitHub importer tries to find
+the GitHub author/assignee in GitLab's database using the GitHub ID. For this
+to work, the GitHub author/assignee should have signed in beforehand in GitLab
+and [**associated their GitHub account**][social sign-in]. If the user is not
+found in GitLab's database, the project creator (most of the times the current
+user that started the import process) is set as the author, but a reference on
+the issue about the original GitHub author is kept.
 
-The importer page is visible when you [create a new project][new-project].
-Click on the **GitHub** link and, if you are logged in via the GitHub
-integration, you will be redirected to GitHub for permission to access your
-projects. After accepting, you'll be automatically redirected to the importer.
+The importer will create any new namespaces (groups) if they don't exist or in
+the case the namespace is taken, the repository will be imported under the user's
+namespace that started the import process.
 
-If you are not using the GitHub integration, you can still perform a one-off
-authorization with GitHub to access your projects.
+## Importing your GitHub repositories
 
-Alternatively, you can also enter a GitHub Personal Access Token. Once you enter
-your token, you'll be taken to the importer.
+The importer page is visible when you create a new project.
 
 ![New project page on GitLab](img/import_projects_from_github_new_project_page.png)
 
----
+Click on the **GitHub** link and the import authorization process will start.
+There are two ways to authorize access to your GitHub repositories:
+
+1. [Using the GitHub integration][gh-integration] (if it's enabled by your
+   GitLab administrator). This is the preferred way as it's possible to
+   preserve the GitHub authors/assignees. Read more in the [How it works](#how-it-works)
+   section.
+1. [Using a personal access token][gh-token] provided by GitHub.
+
+![Select authentication method](img/import_projects_from_github_select_auth_method.png)
+
+### Authorize access to your repositories using the GitHub integration
+
+If the [GitHub integration][gh-import] is enabled by your GitLab administrator,
+you can use it instead of the personal access token.
+
+1. First you may want to connect your GitHub account to GitLab in order for
+   the username mapping to be correct. Follow the [social sign-in] documentation
+   on how to do so.
+1. Once you connect GitHub, click the **List your GitHub repositories** button
+   and you will be redirected to GitHub for permission to access your projects.
+1. After accepting, you'll be automatically redirected to the importer.
+
+You can now go on and [select which repositories to import](#select-which-repositories-to-import).
+
+### Authorize access to your repositories using a personal access token
+
+>**Note:**
+For a proper author/assignee mapping for issues and pull requests, the
+[GitHub integration][gh-integration] should be used instead of the
+[personal access token][gh-token]. If the GitHub integration is enabled by your
+GitLab administrator, it should be the preferred method to import your repositories.
+Read more in the [How it works](#how-it-works) section.
+
+If you are not using the GitHub integration, you can still perform a one-off
+authorization with GitHub to grant GitLab access your repositories:
+
+1. Go to <https://github.com/settings/tokens/new>.
+1. Enter a token description.
+1. Check the `repo` scope.
+1. Click **Generate token**.
+1. Copy the token hash.
+1. Go back to GitLab and provide the token to the GitHub importer.
+1. Hit the **List your GitHub repositories** button and wait while GitLab reads
+   your repositories' information. Once done, you'll be taken to the importer
+   page to select the repositories to import.
+
+### Select which repositories to import
+
+After you've authorized access to your GitHub repositories, you will be
+redirected to the GitHub importer page.
+
+From there, you can see the import statuses of your GitHub repositories.
+
+- Those that are being imported will show a _started_ status,
+- those already successfully imported will be green with a _done_ status,
+- whereas those that are not yet imported will have an **Import** button on the
+  right side of the table.
 
-While at the GitHub importer page, you can see the import statuses of your
-GitHub projects. Those that are being imported will show a _started_ status,
-those already imported will be green, whereas those that are not yet imported
-have an **Import** button on the right side of the table. If you want, you can
-import all your GitHub projects in one go by hitting **Import all projects**
-in the upper left corner.
+If you want, you can import all your GitHub projects in one go by hitting
+**Import all projects** in the upper left corner.
 
 ![GitHub importer page](img/import_projects_from_github_importer.png)
 
 ---
 
-The importer will create any new namespaces if they don't exist or in the
-case the namespace is taken, the project will be imported on the user's
-namespace.
+You can also choose a different name for the project and a different namespace,
+if you have the privileges to do so.
 
 [gh-import]: ../../integration/github.md "GitHub integration"
-[ee-gh]: http://docs.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
 [new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab"
+[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration
+[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token
+[social sign-in]: ../../profile/account/social_sign_in.md
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 9dc1e9b..b3c73e9 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -45,5 +45,5 @@ In `config/gitlab.yml`:
 * Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
   is not supported
 * Currently, removing LFS objects from GitLab Git LFS storage is not supported
-* LFS authentications via SSH is not supported for the time being
-* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2.
+* LFS authentications via SSH was added with GitLab 8.12
+* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 9fe065f..1a4f213 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -35,6 +35,10 @@ Documentation for GitLab instance administrators is under [LFS administration do
   credentials store is recommended
 * Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
   to add the URL to Git config manually (see #troubleshooting)
+  
+>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
+ still goes over HTTP, but now the SSH client passes the correct credentials
+ to the Git LFS client, so no action is required by the user.
 
 ## Using Git LFS
 
@@ -132,6 +136,10 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs"
 
 ### Credentials are always required when pushing an object
 
+>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
+ still goes over HTTP, but now the SSH client passes the correct credentials
+ to the Git LFS client, so no action is required by the user.
+
 Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing
 the LFS object on every push for every object, user HTTPS credentials are required.
 
diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md
index d2ec56e..a68bb8b 100644
--- a/doc/workflow/merge_requests.md
+++ b/doc/workflow/merge_requests.md
@@ -1,63 +1 @@
-# Merge Requests
-
-Merge requests allow you to exchange changes you made to source code
-
-## Only allow merge requests to be merged if the build succeeds
-
-You can prevent merge requests from being merged if their build did not succeed
-in the project settings page.
-
-![only_allow_merge_if_build_succeeds](merge_requests/only_allow_merge_if_build_succeeds.png)
-
-Navigate to project settings page and select the `Only allow merge requests to be merged if the build succeeds` check box.
-
-Please note that you need to have builds configured to enable this feature.
-
-## Checkout merge requests locally
-
-Locate the section for your GitLab remote in the `.git/config` file. It looks like this:
-
-```
-[remote "origin"]
-  url = https://gitlab.com/gitlab-org/gitlab-ce.git
-  fetch = +refs/heads/*:refs/remotes/origin/*
-```
-
-Now add the line `fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*` to this section.
-
-It should look like this:
-
-```
-[remote "origin"]
-  url = https://gitlab.com/gitlab-org/gitlab-ce.git
-  fetch = +refs/heads/*:refs/remotes/origin/*
-  fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
-```
-
-Now you can fetch all the merge requests requests:
-
-```
-$ git fetch origin
-From https://gitlab.com/gitlab-org/gitlab-ce.git
- * [new ref]         refs/merge-requests/1/head -> origin/merge-requests/1
- * [new ref]         refs/merge-requests/2/head -> origin/merge-requests/2
-...
-```
-
-To check out a particular merge request:
-
-```
-$ git checkout origin/merge-requests/1
-```
-
-## Ignore whitespace changes in Merge Request diff view
-
-![MR diff](merge_requests/merge_request_diff.png)
-
-If you click the "Hide whitespace changes" button, you can see the diff without whitespace changes.
-
-![MR diff without whitespace](merge_requests/merge_request_diff_without_whitespace.png)
-
-It is also working on commits compare view.
-
-![Commit Compare](merge_requests/commit_compare.png)
+This document was moved to [user/project/merge_requests](../user/project/merge_requests.md).
diff --git a/doc/workflow/merge_requests/merge_request_diff.png b/doc/workflow/merge_requests/merge_request_diff.png
deleted file mode 100644
index 3ebbfb7..0000000
Binary files a/doc/workflow/merge_requests/merge_request_diff.png and /dev/null differ
diff --git a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png b/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png
deleted file mode 100644
index a0db535..0000000
Binary files a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png and /dev/null differ
diff --git a/doc/workflow/merge_when_build_succeeds.md b/doc/workflow/merge_when_build_succeeds.md
index 75e1fdf..95afd12 100644
--- a/doc/workflow/merge_when_build_succeeds.md
+++ b/doc/workflow/merge_when_build_succeeds.md
@@ -1,15 +1 @@
-# Merge When Build Succeeds
-
-When reviewing a merge request that looks ready to merge but still has one or more CI builds running, you can set it to be merged automatically when all builds succeed. This way, you don't have to wait for the builds to finish and remember to merge the request manually.
-
-![Enable](merge_when_build_succeeds/enable.png)
-
-When you hit the "Merge When Build Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait for the build to succeed and want to merge immediately, this option is available in the dropdown menu on the right of the main button.
-
-Both team developers and the author of the merge request have the option to cancel the automatic merge if they find a reason why it shouldn't be merged after all.
-
-![Status](merge_when_build_succeeds/status.png)
-
-When the build succeeds, the merge request will automatically be merged. When the build fails, the author gets a chance to retry any failed builds, or to push new commits to fix the failure.
-
-When the builds are retried and succeed on the second try, the merge request will automatically be merged after all. When the merge request is updated with new commits, the automatic merge is automatically canceled to allow the new changes to be reviewed.
+This document was moved to [user/project/merge_requests/merge_when_build_succeeds](../user/project/merge_requests/merge_when_build_succeeds.md).
diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md
index a523b3f..f19e7df 100644
--- a/doc/workflow/project_features.md
+++ b/doc/workflow/project_features.md
@@ -32,4 +32,12 @@ Snippets are little bits of code or text.
 
 This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control.
 
-For example, a specific config file that is used by > the team that is only valid for the people that work on the code.
+For example, a specific config file that is used by the team that is only valid for the people that work on the code.
+
+## Git LFS
+
+>**Note:** Project-specific LFS setting was added on 8.12 and is available only to admins.
+
+Git Large File Storage allows you to easily manage large binary files with Git.
+With this setting admins can better control which projects are allowed to use 
+LFS. 
diff --git a/doc/workflow/revert_changes.md b/doc/workflow/revert_changes.md
index 5ead9f4..cf12922 100644
--- a/doc/workflow/revert_changes.md
+++ b/doc/workflow/revert_changes.md
@@ -1,64 +1 @@
-# Reverting changes
-
-> [Introduced][ce-1990] in GitLab 8.5.
-
----
-
-GitLab implements Git's powerful feature to [revert any commit][git-revert]
-with introducing a **Revert** button in Merge Requests and commit details.
-
-## Reverting a Merge Request
-
-_**Note:** The **Revert** button will only be available for Merge Requests
-created since GitLab 8.5. However, you can still revert a Merge Request
-by reverting the merge commit from the list of Commits page._
-
-After the Merge Request has been merged, a **Revert** button will be available
-to revert the changes introduced by that Merge Request:
-
-![Revert Merge Request](img/revert_changes_mr.png)
-
----
-
-You can revert the changes directly into the selected branch or you can opt to
-create a new Merge Request with the revert changes:
-
-![Revert Merge Request modal](img/revert_changes_mr_modal.png)
-
----
-
-After the Merge Request has been reverted, the **Revert** button will not be
-available anymore.
-
-## Reverting a Commit
-
-You can revert a Commit from the Commit details page:
-
-![Revert commit](img/revert_changes_commit.png)
-
----
-
-Similar to reverting a Merge Request, you can opt to revert the changes
-directly into the target branch or create a new Merge Request to revert the
-changes:
-
-![Revert commit modal](img/revert_changes_commit_modal.png)
-
----
-
-After the Commit has been reverted, the **Revert** button will not be available
-anymore.
-
-Please note that when reverting merge commits, the mainline will always be the
-first parent. If you want to use a different mainline then you need to do that
-from the command line.
-
-Here is a quick example to revert a merge commit using the second parent as the
-mainline:
-
-```bash
-git revert -m 2 7a39eb0
-```
-
-[ce-1990]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1990 "Revert button Merge Request"
-[git-revert]: https://git-scm.com/docs/git-revert "Git revert documentation"
+This document was moved to [user/project/merge_requests/revert_changes](../user/project/merge_requests/revert_changes.md).
diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md
index ee8e786..595c7da 100644
--- a/doc/workflow/web_editor.md
+++ b/doc/workflow/web_editor.md
@@ -1,151 +1 @@
-# GitLab Web Editor
-
-Sometimes it's easier to make quick changes directly from the GitLab interface
-than to clone the project and use the Git command line tool. In this feature
-highlight we look at how you can create a new file, directory, branch or
-tag from the file browser. All of these actions are available from a single
-dropdown menu.
-
-## Create a file
-
-From a project's files page, click the '+' button to the right of the branch selector.
-Choose **New file** from the dropdown.
-
-![New file dropdown menu](img/web_editor_new_file_dropdown.png)
-
----
-
-Enter a file name in the **File name** box. Then, add file content in the editor
-area. Add a descriptive commit message and choose a branch. The branch field
-will default to the branch you were viewing in the file browser. If you enter
-a new branch name, a checkbox will appear allowing you to start a new merge
-request after you commit the changes.
-
-When you are satisfied with your new file, click **Commit Changes** at the bottom.
-
-![Create file editor](img/web_editor_new_file_editor.png)
-
-## Upload a file
-
-The ability to create a file is great when the content is text. However, this
-doesn't work well for binary data such as images, PDFs or other file types. In
-this case you need to upload a file.
-
-From a project's files page, click the '+' button to the right of the branch
-selector. Choose **Upload file** from the dropdown.
-
-![Upload file dropdown menu](img/web_editor_upload_file_dropdown.png)
-
----
-
-Once the upload dialog pops up there are two ways to upload your file. Either
-drag and drop a file on the pop up or use the **click to upload** link. A file
-preview will appear once you have selected a file to upload.
-
-Enter a commit message, choose a branch, and click **Upload file** when you are
-ready.
-
-![Upload file dialog](img/web_editor_upload_file_dialog.png)
-
-## Create a directory
-
-To keep files in the repository organized it is often helpful to create a new
-directory.
-
-From a project's files page, click the '+' button to the right of the branch selector.
-Choose **New directory** from the dropdown.
-
-![New directory dropdown](img/web_editor_new_directory_dropdown.png)
-
----
-
-In the new directory dialog enter a directory name, a commit message and choose
-the target branch. Click **Create directory** to finish.
-
-![New directory dialog](img/web_editor_new_directory_dialog.png)
-
-## Create a new branch
-
-There are multiple ways to create a branch from GitLab's web interface.
-
-### Create a new branch from an issue
-
-> [Introduced][ce-2808] in GitLab 8.6.
-
-In case your development workflow dictates to have an issue for every merge
-request, you can quickly create a branch right on the issue page which will be
-tied with the issue itself. You can see a **New Branch** button after the issue
-description, unless there is already a branch with the same name or a referenced
-merge request.
-
-![New Branch Button](img/new_branch_from_issue.png)
-
-Once you click it, a new branch will be created that diverges from the default
-branch of your project, by default `master`. The branch name will be based on
-the title of the issue and as suffix it will have its ID. Thus, the example
-screenshot above will yield a branch named
-`2-et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum`.
-
-After the branch is created, you can edit files in the repository to fix
-the issue. When a merge request is created based on the newly created branch,
-the description field will automatically display the [issue closing pattern]
-`Closes #ID`, where `ID` the ID of the issue. This will close the issue once the
-merge request is merged.
-
-### Create a new branch from a project's dashboard
-
-If you want to make changes to several files before creating a new merge
-request, you can create a new branch up front. From a project's files page,
-choose **New branch** from the dropdown.
-
-![New branch dropdown](img/web_editor_new_branch_dropdown.png)
-
----
-
-Enter a new **Branch name**. Optionally, change the **Create from** field
-to choose which branch, tag or commit SHA this new branch will originate from.
-This field will autocomplete if you start typing an existing branch or tag.
-Click **Create branch** and you will be returned to the file browser on this new
-branch.
-
-![New branch page](img/web_editor_new_branch_page.png)
-
----
-
-You can now make changes to any files, as needed. When you're ready to merge
-the changes back to master you can use the widget at the top of the screen.
-This widget only appears for a period of time after you create the branch or
-modify files.
-
-![New push widget](img/web_editor_new_push_widget.png)
-
-## Create a new tag
-
-Tags are useful for marking major milestones such as production releases,
-release candidates, and more. You can create a tag from a branch or a commit
-SHA. From a project's files page, choose **New tag** from the dropdown.
-
-![New tag dropdown](img/web_editor_new_tag_dropdown.png)
-
----
-
-Give the tag a name such as `v1.0.0`. Choose the branch or SHA from which you
-would like to create this new tag. You can optionally add a message and
-release notes. The release notes section supports markdown format and you can
-also upload an attachment. Click **Create tag** and you will be taken to the tag
-list page.
-
-![New tag page](img/web_editor_new_tag_page.png)
-
-## Tips
-
-When creating or uploading a new file, or creating a new directory, you can
-trigger a new merge request rather than committing directly to master. Enter
-a new branch name in the **Target branch** field. You will notice a checkbox
-appear that is labeled **Start a new merge request with these changes**. After
-you commit the changes you will be taken to a new merge request form.
-
-![Start a new merge request with these changes](img/web_editor_start_new_merge_request.png)
-
-[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808
-[issue closing pattern]: ../customization/issue_closing.md
+This document was moved to [user/project/repository/web_editor](../user/project/repository/web_editor.md).
diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md
index 46035a5..abb8002 100644
--- a/doc/workflow/wip_merge_requests.md
+++ b/doc/workflow/wip_merge_requests.md
@@ -1,13 +1 @@
-# "Work In Progress" Merge Requests
-
-To prevent merge requests from accidentally being accepted before they're completely ready, GitLab blocks the "Accept" button for merge requests that have been marked a **Work In Progress**.
-
-![Blocked Accept Button](wip_merge_requests/blocked_accept_button.png)
-
-To mark a merge request a Work In Progress, simply start its title with `[WIP]` or `WIP:`.
-
-![Mark as WIP](wip_merge_requests/mark_as_wip.png)
-
-To allow a Work In Progress merge request to be accepted again when it's ready, simply remove the `WIP` prefix.
-
-![Unark as WIP](wip_merge_requests/unmark_as_wip.png)
+This document was moved to [user/project/merge_requests/work_in_progress_merge_requests](../user/project/merge_requests/work_in_progress_merge_requests.md).
diff --git a/features/dashboard/todos.feature b/features/dashboard/todos.feature
index 42f5d6d..0b23bbb 100644
--- a/features/dashboard/todos.feature
+++ b/features/dashboard/todos.feature
@@ -23,26 +23,6 @@ Feature: Dashboard Todos
     Then I should see all todos marked as done
 
   @javascript
-    Scenario: I filter by project
-      Given I filter by "Enterprise"
-      Then I should not see todos
-
-  @javascript
-    Scenario: I filter by author
-      Given I filter by "John Doe"
-      Then I should not see todos related to "Mary Jane" in the list
-
-  @javascript
-    Scenario: I filter by type
-      Given I filter by "Issue"
-      Then I should not see todos related to "Merge Requests" in the list
-
-  @javascript
-    Scenario: I filter by action
-      Given I filter by "Mentioned"
-      Then I should not see todos related to "Assignments" in the list
-
-  @javascript
     Scenario: I click on a todo row
       Given I click on the todo
       Then I should be directed to the corresponding page
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 6bac601..5aa592e 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -24,7 +24,7 @@ Feature: Project Merge Requests
   Scenario: I should see target branch when it is different from default
     Given project "Shop" have "Bug NS-06" open merge request
     When I visit project "Shop" merge requests page
-    Then I should see "other_branch" branch
+    Then I should see "feature_conflict" branch
 
   Scenario: I should not see the numbers of diverged commits if the branch is rebased on the target
     Given project "Shop" have "Bug NS-07" open merge request with rebased branch
@@ -89,7 +89,7 @@ Feature: Project Merge Requests
     Then The list should be sorted by "Oldest updated"
 
   @javascript
-  Scenario: Visiting Merge Requests from a differente Project after sorting
+  Scenario: Visiting Merge Requests from a different Project after sorting
     Given I visit project "Shop" merge requests page
     And I sort the list by "Oldest updated"
     And I visit dashboard merge requests page
diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb
index 03f87df..11dc7f5 100644
--- a/features/steps/admin/settings.rb
+++ b/features/steps/admin/settings.rb
@@ -33,6 +33,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
     page.check('Issue')
     page.check('Merge request')
     page.check('Build')
+    page.check('Pipeline')
     click_on 'Save'
   end
 
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
index f0d8d49..2f0941e 100644
--- a/features/steps/dashboard/new_project.rb
+++ b/features/steps/dashboard/new_project.rb
@@ -18,7 +18,6 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
     expect(page).to have_link('GitHub')
     expect(page).to have_link('Bitbucket')
     expect(page).to have_link('GitLab.com')
-    expect(page).to have_link('Gitorious.org')
     expect(page).to have_link('Google Code')
     expect(page).to have_link('Repo by URL')
   end
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index 60152d3..344b6fd 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -3,7 +3,6 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
   include SharedPaths
   include SharedProject
   include SharedUser
-  include Select2Helper
 
   step '"John Doe" is a developer of project "Shop"' do
     project.team << [john_doe, :developer]
@@ -54,7 +53,8 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
     page.within('.todos-pending-count') { expect(page).to have_content '0' }
     expect(page).to have_content 'To do 0'
     expect(page).to have_content 'Done 4'
-    expect(page).not_to have_link project.name_with_namespace
+    expect(page).to have_content "You're all done!"
+    expect('.prepend-top-default').not_to have_link project.name_with_namespace
     should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
     should_not_see_todo "John Doe mentioned you on issue #{issue.to_reference}"
     should_not_see_todo "John Doe assigned you issue #{issue.to_reference}"
@@ -79,19 +79,31 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
   end
 
   step 'I filter by "Enterprise"' do
-    select2(enterprise.id, from: "#project_id")
+    click_button 'Project'
+    page.within '.dropdown-menu-project' do
+      click_link enterprise.name_with_namespace
+    end
   end
 
   step 'I filter by "John Doe"' do
-    select2(john_doe.id, from: "#author_id")
+    click_button 'Author'
+    page.within '.dropdown-menu-author' do
+      click_link john_doe.username
+    end
   end
 
   step 'I filter by "Issue"' do
-    select2('Issue', from: "#type")
+    click_button 'Type'
+    page.within '.dropdown-menu-type' do
+      click_link 'Issue'
+    end
   end
 
   step 'I filter by "Mentioned"' do
-    select2("#{Todo::MENTIONED}", from: '#action_id')
+    click_button 'Action'
+    page.within '.dropdown-menu-action' do
+      click_link 'Mentioned'
+    end
   end
 
   step 'I should not see todos' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 1498f89..cbe5738 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -48,7 +48,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
     page.within '.awards' do
       expect(page).to have_selector '.js-emoji-btn'
       expect(page.find('.js-emoji-btn.active .js-counter')).to have_content '1'
-      expect(page).to have_css(".js-emoji-btn.active[data-original-title='me']")
+      expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
     end
   end
 
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index e21f76d..ed72416 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -356,6 +356,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
   end
 
   def filter_issue(text)
-    fill_in 'issue_search', with: text
+    fill_in 'issuable_search', with: text
   end
 end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 9778ff4..4a67cf0 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -31,7 +31,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I click link "Closed"' do
-    click_link "Closed"
+    page.within('.issues-state-filters') do
+      click_link "Closed"
+    end
   end
 
   step 'I should see merge request "Wiki Feature"' do
@@ -58,8 +60,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
     expect(find('.merge-request-info')).not_to have_content "master"
   end
 
-  step 'I should see "other_branch" branch' do
-    expect(page).to have_content "other_branch"
+  step 'I should see "feature_conflict" branch' do
+    expect(page).to have_content "feature_conflict"
   end
 
   step 'I should see "Bug NS-04" in merge requests' do
@@ -124,7 +126,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
            source_project: project,
            target_project: project,
            source_branch: 'fix',
-           target_branch: 'other_branch',
+           target_branch: 'feature_conflict',
            author: project.users.first,
            description: "# Description header"
           )
@@ -491,7 +493,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I fill in merge request search with "Fe"' do
-    fill_in 'issue_search', with: "Fe"
+    fill_in 'issuable_search', with: "Fe"
   end
 
   step 'I click the "Target branch" dropdown' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 76fefee..975c879 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -5,7 +5,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
 
   step 'change project settings' do
     fill_in 'project_name_edit', with: 'NewName'
-    uncheck 'project_issues_enabled'
+    select 'Disabled', from: 'project_project_feature_attributes_issues_access_level'
   end
 
   step 'I save project' do
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index aa666a9..df9845b 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -179,7 +179,7 @@ module SharedIssuable
     project = Project.find_by(name: from_project_name)
 
     expect(page).to have_content(user_name)
-    expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}")
+    expect(page).to have_content("Mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}")
   end
 
   def expect_sidebar_content(content)
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 0b49208..afbd8ef 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -15,7 +15,7 @@ module SharedProject
   # Create a specific project called "Shop"
   step 'I own project "Shop"' do
     @project = Project.find_by(name: "Shop")
-    @project ||= create(:project, name: "Shop", namespace: @user.namespace, snippets_enabled: true)
+    @project ||= create(:project, name: "Shop", namespace: @user.namespace)
     @project.team << [@user, :master]
   end
 
@@ -41,6 +41,8 @@ module SharedProject
   step 'I own project "Forum"' do
     @project = Project.find_by(name: "Forum")
     @project ||= create(:project, name: "Forum", namespace: @user.namespace, path: 'forum_project')
+    @project.build_project_feature
+    @project.project_feature.save
     @project.team << [@user, :master]
   end
 
@@ -95,7 +97,7 @@ module SharedProject
   step 'I should see project settings' do
     expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
     expect(page).to have_content("Project name")
-    expect(page).to have_content("Features")
+    expect(page).to have_content("Feature Visibility")
   end
 
   def current_project
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index d02b469..29a97cc 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -20,7 +20,7 @@ module API
 
           access_requesters = paginate(source.requesters.includes(:user))
 
-          present access_requesters.map(&:user), with: Entities::AccessRequester, access_requesters: access_requesters
+          present access_requesters.map(&:user), with: Entities::AccessRequester, source: source
         end
 
         # Request access to the group/project
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 6b8bfbb..74ca472 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -18,27 +18,20 @@ module API
     end
 
     rescue_from :all do |exception|
-      # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
-      # why is this not wrapped in something reusable?
-      trace = exception.backtrace
-
-      message = "\n#{exception.class} (#{exception.message}):\n"
-      message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
-      message << "  " << trace.join("\n  ")
-
-      API.logger.add Logger::FATAL, message
-      rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500)
+      handle_api_exception(exception)
     end
 
     format :json
     content_type :txt, "text/plain"
 
     # Ensure the namespace is right, otherwise we might load Grape::API::Helpers
+    helpers ::SentryHelper
     helpers ::API::Helpers
 
     mount ::API::AccessRequests
     mount ::API::AwardEmoji
     mount ::API::Branches
+    mount ::API::BroadcastMessages
     mount ::API::Builds
     mount ::API::CommitStatuses
     mount ::API::Commits
@@ -52,11 +45,13 @@ module API
     mount ::API::Keys
     mount ::API::Labels
     mount ::API::LicenseTemplates
+    mount ::API::Lint
     mount ::API::Members
     mount ::API::MergeRequests
     mount ::API::Milestones
     mount ::API::Namespaces
     mount ::API::Notes
+    mount ::API::NotificationSettings
     mount ::API::Pipelines
     mount ::API::ProjectHooks
     mount ::API::ProjectSnippets
@@ -75,5 +70,6 @@ module API
     mount ::API::Triggers
     mount ::API::Users
     mount ::API::Variables
+    mount ::API::MergeRequestDiffs
   end
 end
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 7e67edb..8cc7a26 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -33,46 +33,29 @@ module API
       #
       # If the token is revoked, then it raises RevokedError.
       #
-      # If the token is not found (nil), then it raises TokenNotFoundError.
+      # If the token is not found (nil), then it returns nil
       #
       # Arguments:
       #
       #   scopes: (optional) scopes required for this guard.
       #           Defaults to empty array.
       #
-      def doorkeeper_guard!(scopes: [])
-        if (access_token = find_access_token).nil?
-          raise TokenNotFoundError
-
-        else
-          case validate_access_token(access_token, scopes)
-          when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
-            raise InsufficientScopeError.new(scopes)
-          when Oauth2::AccessTokenValidationService::EXPIRED
-            raise ExpiredError
-          when Oauth2::AccessTokenValidationService::REVOKED
-            raise RevokedError
-          when Oauth2::AccessTokenValidationService::VALID
-            @current_user = User.find(access_token.resource_owner_id)
-          end
-        end
-      end
-
       def doorkeeper_guard(scopes: [])
-        if access_token = find_access_token
-          case validate_access_token(access_token, scopes)
-          when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
-            raise InsufficientScopeError.new(scopes)
+        access_token = find_access_token
+        return nil unless access_token
+
+        case validate_access_token(access_token, scopes)
+        when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
+          raise InsufficientScopeError.new(scopes)
 
-          when Oauth2::AccessTokenValidationService::EXPIRED
-            raise ExpiredError
+        when Oauth2::AccessTokenValidationService::EXPIRED
+          raise ExpiredError
 
-          when Oauth2::AccessTokenValidationService::REVOKED
-            raise RevokedError
+        when Oauth2::AccessTokenValidationService::REVOKED
+          raise RevokedError
 
-          when Oauth2::AccessTokenValidationService::VALID
-            @current_user = User.find(access_token.resource_owner_id)
-          end
+        when Oauth2::AccessTokenValidationService::VALID
+          @current_user = User.find(access_token.resource_owner_id)
         end
       end
 
@@ -96,19 +79,6 @@ module API
     end
 
     module ClassMethods
-      # Installs the doorkeeper guard on the whole Grape API endpoint.
-      #
-      # Arguments:
-      #
-      #   scopes: (optional) scopes required for this guard.
-      #           Defaults to empty array.
-      #
-      def guard_all!(scopes: [])
-        before do
-          guard! scopes: scopes
-        end
-      end
-
       private
 
       def install_error_responders(base)
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 2efe7e3..2461a78 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -1,12 +1,12 @@
 module API
   class AwardEmoji < Grape::API
     before { authenticate! }
-    AWARDABLES = [Issue, MergeRequest]
+    AWARDABLES = %w[issue merge_request snippet]
 
     resource :projects do
       AWARDABLES.each do |awardable_type|
-        awardable_string = awardable_type.to_s.underscore.pluralize
-        awardable_id_string = "#{awardable_type.to_s.underscore}_id"
+        awardable_string = awardable_type.pluralize
+        awardable_id_string = "#{awardable_type}_id"
 
         [ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
           ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"
@@ -54,7 +54,7 @@ module API
           post endpoint do
             required_attributes! [:name]
 
-            not_found!('Award Emoji') unless can_read_awardable?
+            not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable?
 
             award = awardable.create_award_emoji(params[:name], current_user)
 
@@ -87,27 +87,36 @@ module API
 
     helpers do
       def can_read_awardable?
-        ability = "read_#{awardable.class.to_s.underscore}".to_sym
+        can?(current_user, read_ability(awardable), awardable)
+      end
 
-        can?(current_user, ability, awardable)
+      def can_award_awardable?
+        awardable.user_can_award?(current_user, params[:name])
       end
 
       def awardable
         @awardable ||=
           begin
             if params.include?(:note_id)
-              noteable.notes.find(params[:note_id])
+              note_id = params.delete(:note_id)
+
+              awardable.notes.find(note_id)
+            elsif params.include?(:issue_id)
+              user_project.issues.find(params[:issue_id])
+            elsif params.include?(:merge_request_id)
+              user_project.merge_requests.find(params[:merge_request_id])
             else
-              noteable
+              user_project.snippets.find(params[:snippet_id])
             end
           end
       end
 
-      def noteable
-        if params.include?(:issue_id)
-          user_project.issues.find(params[:issue_id])
+      def read_ability(awardable)
+        case awardable
+        when Note
+          read_ability(awardable.noteable)
         else
-          user_project.merge_requests.find(params[:merge_request_id])
+          :"read_#{awardable.class.to_s.underscore}"
         end
       end
     end
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
new file mode 100644
index 0000000..fb2a414
--- /dev/null
+++ b/lib/api/broadcast_messages.rb
@@ -0,0 +1,99 @@
+module API
+  class BroadcastMessages < Grape::API
+    before { authenticate! }
+    before { authenticated_as_admin! }
+
+    resource :broadcast_messages do
+      helpers do
+        def find_message
+          BroadcastMessage.find(params[:id])
+        end
+      end
+
+      desc 'Get all broadcast messages' do
+        detail 'This feature was introduced in GitLab 8.12.'
+        success Entities::BroadcastMessage
+      end
+      params do
+        optional :page,     type: Integer, desc: 'Current page number'
+        optional :per_page, type: Integer, desc: 'Number of messages per page'
+      end
+      get do
+        messages = BroadcastMessage.all
+
+        present paginate(messages), with: Entities::BroadcastMessage
+      end
+
+      desc 'Create a broadcast message' do
+        detail 'This feature was introduced in GitLab 8.12.'
+        success Entities::BroadcastMessage
+      end
+      params do
+        requires :message,   type: String,   desc: 'Message to display'
+        optional :starts_at, type: DateTime, desc: 'Starting time', default: -> { Time.zone.now }
+        optional :ends_at,   type: DateTime, desc: 'Ending time',   default: -> { 1.hour.from_now }
+        optional :color,     type: String,   desc: 'Background color'
+        optional :font,      type: String,   desc: 'Foreground color'
+      end
+      post do
+        create_params = declared(params, include_missing: false).to_h
+        message = BroadcastMessage.create(create_params)
+
+        if message.persisted?
+          present message, with: Entities::BroadcastMessage
+        else
+          render_validation_error!(message)
+        end
+      end
+
+      desc 'Get a specific broadcast message' do
+        detail 'This feature was introduced in GitLab 8.12.'
+        success Entities::BroadcastMessage
+      end
+      params do
+        requires :id, type: Integer, desc: 'Broadcast message ID'
+      end
+      get ':id' do
+        message = find_message
+
+        present message, with: Entities::BroadcastMessage
+      end
+
+      desc 'Update a broadcast message' do
+        detail 'This feature was introduced in GitLab 8.12.'
+        success Entities::BroadcastMessage
+      end
+      params do
+        requires :id,        type: Integer,  desc: 'Broadcast message ID'
+        optional :message,   type: String,   desc: 'Message to display'
+        optional :starts_at, type: DateTime, desc: 'Starting time'
+        optional :ends_at,   type: DateTime, desc: 'Ending time'
+        optional :color,     type: String,   desc: 'Background color'
+        optional :font,      type: String,   desc: 'Foreground color'
+      end
+      put ':id' do
+        message = find_message
+        update_params = declared(params, include_missing: false).to_h
+
+        if message.update(update_params)
+          present message, with: Entities::BroadcastMessage
+        else
+          render_validation_error!(message)
+        end
+      end
+
+      desc 'Delete a broadcast message' do
+        detail 'This feature was introduced in GitLab 8.12.'
+        success Entities::BroadcastMessage
+      end
+      params do
+        requires :id, type: Integer, desc: 'Broadcast message ID'
+      end
+      delete ':id' do
+        message = find_message
+
+        present message.destroy, with: Entities::BroadcastMessage
+      end
+    end
+  end
+end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 4df6ca8..dfbdd59 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -37,7 +37,7 @@ module API
       #   id (required) - The ID of a project
       #   sha (required) - The commit hash
       #   ref (optional) - The ref
-      #   state (required) - The state of the status. Can be: pending, running, success, error or failure
+      #   state (required) - The state of the status. Can be: pending, running, success, failed or canceled
       #   target_url (optional) - The target URL to associate with this status
       #   description (optional) - A short description of the status
       #   name or context (optional) - A string label to differentiate this status from the status of other systems. Default: "default"
@@ -46,7 +46,7 @@ module API
       post ':id/statuses/:sha' do
         authorize! :create_commit_status, user_project
         required_attributes! [:state]
-        attrs = attributes_for_keys [:ref, :target_url, :description, :context, :name]
+        attrs = attributes_for_keys [:target_url, :description]
         commit = @project.commit(params[:sha])
         not_found! 'Commit' unless commit
 
@@ -58,36 +58,38 @@ module API
         # the first found branch on that commit
 
         ref = params[:ref]
-        unless ref
-          branches = @project.repository.branch_names_contains(commit.sha)
-          not_found! 'References for commit' if branches.none?
-          ref = branches.first
-        end
+        ref ||= @project.repository.branch_names_contains(commit.sha).first
+        not_found! 'References for commit' unless ref
 
-        pipeline = @project.ensure_pipeline(commit.sha, ref, current_user)
+        name = params[:name] || params[:context] || 'default'
 
-        name = params[:name] || params[:context]
-        status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
-        status ||= GenericCommitStatus.new(project: @project, pipeline: pipeline, user: current_user)
-        status.update(attrs)
+        pipeline = @project.ensure_pipeline(ref, commit.sha, current_user)
 
-        case params[:state].to_s
-        when 'running'
-          status.run
-        when 'success'
-          status.success
-        when 'failed'
-          status.drop
-        when 'canceled'
-          status.cancel
-        else
-          status.status = params[:state].to_s
-        end
+        status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
+          project: @project, pipeline: pipeline,
+          user: current_user, name: name, ref: ref)
+        status.attributes = attrs
+
+        begin
+          case params[:state].to_s
+          when 'pending'
+            status.enqueue!
+          when 'running'
+            status.enqueue
+            status.run!
+          when 'success'
+            status.success!
+          when 'failed'
+            status.drop!
+          when 'canceled'
+            status.cancel!
+          else
+            render_api_error!('invalid state', 400)
+          end
 
-        if status.save
           present status, with: Entities::CommitStatus
-        else
-          render_validation_error!(status)
+        rescue StateMachines::InvalidTransition => e
+          render_api_error!(e.message, 400)
         end
       end
     end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 54ce2dc..92a6f29 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -49,7 +49,7 @@ module API
     class ProjectHook < Hook
       expose :project_id, :push_events
       expose :issues_events, :merge_requests_events, :tag_push_events
-      expose :note_events, :build_events, :pipeline_events
+      expose :note_events, :build_events, :pipeline_events, :wiki_page_events
       expose :enable_ssl_verification
     end
 
@@ -76,44 +76,57 @@ module API
       expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
       expose :name, :name_with_namespace
       expose :path, :path_with_namespace
-      expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :container_registry_enabled
+      expose :container_registry_enabled
+
+      # Expose old field names with the new permissions methods to keep API compatible
+      expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:user]) }
+      expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:user]) }
+      expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:user]) }
+      expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:user]) }
+      expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) }
+
       expose :created_at, :last_activity_at
       expose :shared_runners_enabled
+      expose :lfs_enabled?, as: :lfs_enabled
       expose :creator_id
       expose :namespace
       expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
       expose :avatar_url
       expose :star_count, :forks_count
-      expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? }
+      expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:user]) && project.default_issues_tracker? }
       expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
       expose :public_builds
       expose :shared_with_groups do |project, options|
         SharedGroup.represent(project.project_group_links.all, options)
       end
+      expose :only_allow_merge_if_build_succeeds
+      expose :request_access_enabled
     end
 
     class Member < UserBasic
       expose :access_level do |user, options|
-        member = options[:member] || options[:members].find { |m| m.user_id == user.id }
+        member = options[:member] || options[:source].members.find_by(user_id: user.id)
         member.access_level
       end
       expose :expires_at do |user, options|
-        member = options[:member] || options[:members].find { |m| m.user_id == user.id }
+        member = options[:member] || options[:source].members.find_by(user_id: user.id)
         member.expires_at
       end
     end
 
     class AccessRequester < UserBasic
       expose :requested_at do |user, options|
-        access_requester = options[:access_requester] || options[:access_requesters].find { |m| m.user_id == user.id }
+        access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id)
         access_requester.requested_at
       end
     end
 
     class Group < Grape::Entity
       expose :id, :name, :path, :description, :visibility_level
+      expose :lfs_enabled?, as: :lfs_enabled
       expose :avatar_url
       expose :web_url
+      expose :request_access_enabled
     end
 
     class GroupDetail < Group
@@ -177,6 +190,10 @@ module API
 
       # TODO (rspeicher): Deprecated; remove in 9.0
       expose(:expires_at) { |snippet| nil }
+
+      expose :web_url do |snippet, options|
+        Gitlab::UrlBuilder.build(snippet)
+      end
     end
 
     class ProjectEntity < Grape::Entity
@@ -206,6 +223,11 @@ module API
       expose :user_notes_count
       expose :upvotes, :downvotes
       expose :due_date
+      expose :confidential
+
+      expose :web_url do |issue, options|
+        Gitlab::UrlBuilder.build(issue)
+      end
     end
 
     class ExternalIssue < Grape::Entity
@@ -223,12 +245,18 @@ module API
       expose :milestone, using: Entities::Milestone
       expose :merge_when_build_succeeds
       expose :merge_status
+      expose :diff_head_sha, as: :sha
+      expose :merge_commit_sha
       expose :subscribed do |merge_request, options|
         merge_request.subscribed?(options[:current_user])
       end
       expose :user_notes_count
       expose :should_remove_source_branch?, as: :should_remove_source_branch
       expose :force_remove_source_branch?, as: :force_remove_source_branch
+
+      expose :web_url do |merge_request, options|
+        Gitlab::UrlBuilder.build(merge_request)
+      end
     end
 
     class MergeRequestChanges < MergeRequest
@@ -237,6 +265,19 @@ module API
       end
     end
 
+    class MergeRequestDiff < Grape::Entity
+      expose :id, :head_commit_sha, :base_commit_sha, :start_commit_sha,
+        :created_at, :merge_request_id, :state, :real_size
+    end
+
+    class MergeRequestDiffFull < MergeRequestDiff
+      expose :commits, using: Entities::RepoCommit
+
+      expose :diffs, using: Entities::RepoDiff do |compare, _|
+        compare.raw_diffs(all_diffs: true).to_a
+      end
+    end
+
     class SSHKey < Grape::Entity
       expose :id, :title, :key, :created_at
     end
@@ -338,7 +379,7 @@ module API
       expose :access_level
       expose :notification_level do |member, options|
         if member.notification_setting
-          NotificationSetting.levels[member.notification_setting.level]
+          ::NotificationSetting.levels[member.notification_setting.level]
         end
       end
     end
@@ -349,6 +390,21 @@ module API
     class GroupAccess < MemberAccess
     end
 
+    class NotificationSetting < Grape::Entity
+      expose :level
+      expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do
+        ::NotificationSetting::EMAIL_EVENTS.each do |event|
+          expose event
+        end
+      end
+    end
+
+    class GlobalNotificationSetting < NotificationSetting
+      expose :notification_email do |notification_setting, options|
+        notification_setting.user.notification_email
+      end
+    end
+
     class ProjectService < Grape::Entity
       expose :id, :title, :created_at, :updated_at, :active
       expose :push_events, :issues_events, :merge_requests_events
@@ -514,13 +570,12 @@ module API
       expose :duration
     end
 
-    class Environment < Grape::Entity
+    class EnvironmentBasic < Grape::Entity
       expose :id, :name, :external_url
-      expose :project, using: Entities::Project
     end
 
-    class EnvironmentBasic < Grape::Entity
-      expose :id, :name, :external_url
+    class Environment < EnvironmentBasic
+      expose :project, using: Entities::Project
     end
 
     class Deployment < Grape::Entity
@@ -549,5 +604,10 @@ module API
     class Template < Grape::Entity
       expose :name, :content
     end
+
+    class BroadcastMessage < Grape::Entity
+      expose :id, :message, :starts_at, :ends_at, :color, :font
+      expose :active?, as: :active
+    end
   end
 end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index c1d86f3..96510e6 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -11,14 +11,16 @@ module API
           target_branch: attrs[:branch_name],
           commit_message: attrs[:commit_message],
           file_content: attrs[:content],
-          file_content_encoding: attrs[:encoding]
+          file_content_encoding: attrs[:encoding],
+          author_email: attrs[:author_email],
+          author_name: attrs[:author_name]
         }
       end
 
       def commit_response(attrs)
         {
           file_path: attrs[:file_path],
-          branch_name: attrs[:branch_name],
+          branch_name: attrs[:branch_name]
         }
       end
     end
@@ -96,7 +98,7 @@ module API
         authorize! :push_code, user_project
 
         required_attributes! [:file_path, :branch_name, :content, :commit_message]
-        attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
+        attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name]
         result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute
 
         if result[:status] == :success
@@ -122,7 +124,7 @@ module API
         authorize! :push_code, user_project
 
         required_attributes! [:file_path, :branch_name, :content, :commit_message]
-        attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
+        attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name]
         result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute
 
         if result[:status] == :success
@@ -149,7 +151,7 @@ module API
         authorize! :push_code, user_project
 
         required_attributes! [:file_path, :branch_name, :commit_message]
-        attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
+        attrs = attributes_for_keys [:file_path, :branch_name, :commit_message, :author_email, :author_name]
         result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute
 
         if result[:status] == :success
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 9d8b8d7..953fa47 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -23,17 +23,19 @@ module API
       # Create group. Available only for users who can create groups.
       #
       # Parameters:
-      #   name (required)             - The name of the group
-      #   path (required)             - The path of the group
-      #   description (optional)      - The description of the group
-      #   visibility_level (optional) - The visibility level of the group
+      #   name (required)                   - The name of the group
+      #   path (required)                   - The path of the group
+      #   description (optional)            - The description of the group
+      #   visibility_level (optional)       - The visibility level of the group
+      #   lfs_enabled (optional)            - Enable/disable LFS for the projects in this group
+      #   request_access_enabled (optional) - Allow users to request member access
       # Example Request:
       #   POST /groups
       post do
-        authorize! :create_group, current_user
+        authorize! :create_group
         required_attributes! [:name, :path]
 
-        attrs = attributes_for_keys [:name, :path, :description, :visibility_level]
+        attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled]
         @group = Group.new(attrs)
 
         if @group.save
@@ -47,17 +49,19 @@ module API
       # Update group. Available only for users who can administrate groups.
       #
       # Parameters:
-      #   id (required)               - The ID of a group
-      #   path (optional)             - The path of the group
-      #   description (optional)      - The description of the group
-      #   visibility_level (optional) - The visibility level of the group
+      #   id (required)                     - The ID of a group
+      #   path (optional)                   - The path of the group
+      #   description (optional)            - The description of the group
+      #   visibility_level (optional)       - The visibility level of the group
+      #   lfs_enabled (optional)            - Enable/disable LFS for the projects in this group
+      #   request_access_enabled (optional) - Allow users to request member access
       # Example Request:
       #   PUT /groups/:id
       put ':id' do
         group = find_group(params[:id])
         authorize! :admin_group, group
 
-        attrs = attributes_for_keys [:name, :path, :description, :visibility_level]
+        attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled]
 
         if ::Groups::UpdateService.new(group, current_user, attrs).execute
           present group, with: Entities::GroupDetail
@@ -97,7 +101,7 @@ module API
         group = find_group(params[:id])
         projects = GroupProjectsFinder.new(group).execute(current_user)
         projects = paginate projects
-        present projects, with: Entities::Project
+        present projects, with: Entities::Project, user: current_user
       end
 
       # Transfer a project to the Group namespace
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index d0469d6..714d4ea 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -12,13 +12,30 @@ module API
       nil
     end
 
+    def private_token
+      params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]
+    end
+
+    def warden
+      env['warden']
+    end
+
+    # Check the Rails session for valid authentication details
+    def find_user_from_warden
+      warden ? warden.authenticate : nil
+    end
+
     def find_user_by_private_token
-      token_string = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
-      User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
+      token = private_token
+      return nil unless token.present?
+
+      User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
     end
 
     def current_user
-      @current_user ||= (find_user_by_private_token || doorkeeper_guard)
+      @current_user ||= find_user_by_private_token
+      @current_user ||= doorkeeper_guard
+      @current_user ||= find_user_from_warden
 
       unless @current_user && Gitlab::UserAccess.new(@current_user).allowed?
         return nil
@@ -129,7 +146,7 @@ module API
       forbidden! unless current_user.is_admin?
     end
 
-    def authorize!(action, subject)
+    def authorize!(action, subject = nil)
       forbidden! unless can?(current_user, action, subject)
     end
 
@@ -148,7 +165,7 @@ module API
     end
 
     def can?(object, action, subject)
-      abilities.allowed?(object, action, subject)
+      Ability.allowed?(object, action, subject)
     end
 
     # Checks the occurrences of required attributes, each attribute must be present in the params hash
@@ -269,6 +286,10 @@ module API
       render_api_error!('304 Not Modified', 304)
     end
 
+    def no_content!
+      render_api_error!('204 No Content', 204)
+    end
+
     def render_validation_error!(model)
       if model.errors.any?
         render_api_error!(model.errors.messages || '400 Bad Request', 400)
@@ -279,6 +300,24 @@ module API
       error!({ 'message' => message }, status)
     end
 
+    def handle_api_exception(exception)
+      if sentry_enabled? && report_exception?(exception)
+        define_params_for_grape_middleware
+        sentry_context
+        Raven.capture_exception(exception)
+      end
+
+      # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
+      trace = exception.backtrace
+
+      message = "\n#{exception.class} (#{exception.message}):\n"
+      message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+      message << "  " << trace.join("\n  ")
+
+      API.logger.add Logger::FATAL, message
+      rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500)
+    end
+
     # Projects helpers
 
     def filter_projects(projects)
@@ -390,14 +429,6 @@ module API
       links.join(', ')
     end
 
-    def abilities
-      @abilities ||= begin
-                       abilities = Six.new
-                       abilities << Ability
-                       abilities
-                     end
-    end
-
     def secret_token
       File.read(Gitlab.config.gitlab_shell.secret_file).chomp
     end
@@ -419,5 +450,19 @@ module API
         Entities::Issue
       end
     end
+
+    # The Grape Error Middleware only has access to env but no params. We workaround this by
+    # defining a method that returns the right value.
+    def define_params_for_grape_middleware
+      self.define_singleton_method(:params) { Rack::Request.new(env).params.symbolize_keys }
+    end
+
+    # We could get a Grape or a standard Ruby exception. We should only report anything that
+    # is clearly an error.
+    def report_exception?(exception)
+      return true unless exception.respond_to?(:status)
+
+      exception.status == 500
+    end
   end
 end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index d8e9ac4..090d045 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -35,6 +35,14 @@ module API
             Project.find_with_namespace(project_path)
           end
         end
+
+        def ssh_authentication_abilities
+          [
+            :read_project,
+            :download_code,
+            :push_code
+          ]
+        end
       end
 
       post "/allowed" do
@@ -51,9 +59,9 @@ module API
 
         access =
           if wiki?
-            Gitlab::GitAccessWiki.new(actor, project, protocol)
+            Gitlab::GitAccessWiki.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
           else
-            Gitlab::GitAccess.new(actor, project, protocol)
+            Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
           end
 
         access_status = access.check(params[:action], params[:changes])
@@ -74,6 +82,19 @@ module API
         response
       end
 
+      post "/lfs_authenticate" do
+        status 200
+
+        key = Key.find(params[:key_id])
+        token_handler = Gitlab::LfsToken.new(key)
+
+        {
+          username: token_handler.actor_name,
+          lfs_token: token_handler.generate,
+          repository_http_path: project.http_url_to_repo
+        }
+      end
+
       get "/merge_request_urls" do
         ::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
       end
@@ -101,6 +122,35 @@ module API
           {}
         end
       end
+
+      post '/two_factor_recovery_codes' do
+        status 200
+
+        key = Key.find_by(id: params[:key_id])
+
+        unless key
+          return { 'success' => false, 'message' => 'Could not find the given key' }
+        end
+
+        if key.is_a?(DeployKey)
+          return { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' }
+        end
+
+        user = key.user
+
+        unless user
+          return { success: false, message: 'Could not find a user for the given key' }
+        end
+
+        unless user.two_factor_enabled?
+          return { success: false, message: 'Two-factor authentication is not enabled for this user' }
+        end
+
+        codes = user.generate_otp_backup_codes!
+        user.save!
+
+        { success: true, recovery_codes: codes }
+      end
     end
   end
 end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 077258f..c9689e6 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -41,7 +41,8 @@ module API
         issues = current_user.issues.inc_notes_with_associations
         issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
         issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
-        issues.reorder(issuable_order_by => issuable_sort)
+        issues = issues.reorder(issuable_order_by => issuable_sort)
+
         present paginate(issues), with: Entities::Issue, current_user: current_user
       end
     end
@@ -73,7 +74,11 @@ module API
         params[:group_id] = group.id
         params[:milestone_title] = params.delete(:milestone)
         params[:label_name] = params.delete(:labels)
-        params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort]
+
+        if params[:order_by] || params[:sort]
+          # The Sortable concern takes 'created_desc', not 'created_at_desc' (for example)
+          params[:sort] = "#{issuable_order_by.sub('_at', '')}_#{issuable_sort}"
+        end
 
         issues = IssuesFinder.new(current_user, params).execute
 
@@ -113,7 +118,8 @@ module API
           issues = filter_issues_milestone(issues, params[:milestone])
         end
 
-        issues.reorder(issuable_order_by => issuable_sort)
+        issues = issues.reorder(issuable_order_by => issuable_sort)
+
         present paginate(issues), with: Entities::Issue, current_user: current_user
       end
 
@@ -140,12 +146,13 @@ module API
       #   labels (optional)       - The labels of an issue
       #   created_at (optional)   - Date time string, ISO 8601 formatted
       #   due_date (optional)     - Date time string in the format YEAR-MONTH-DAY
+      #   confidential (optional) - Boolean parameter if the issue should be confidential
       # Example Request:
       #   POST /projects/:id/issues
       post ':id/issues' do
         required_attributes! [:title]
 
-        keys = [:title, :description, :assignee_id, :milestone_id, :due_date]
+        keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential]
         keys << :created_at if current_user.admin? || user_project.owner == current_user
         attrs = attributes_for_keys(keys)
 
@@ -154,21 +161,19 @@ module API
           render_api_error!({ labels: errors }, 400)
         end
 
-        project = user_project
+        attrs[:labels] = params[:labels] if params[:labels]
+
+        # Convert and filter out invalid confidential flags
+        attrs['confidential'] = to_boolean(attrs['confidential'])
+        attrs.delete('confidential') if attrs['confidential'].nil?
 
-        issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute
+        issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute
 
         if issue.spam?
           render_api_error!({ error: 'Spam detected' }, 400)
         end
 
         if issue.valid?
-          # Find or create labels and attach to issue. Labels are valid because
-          # we already checked its name, so there can't be an error here
-          if params[:labels].present?
-            issue.add_labels_by_names(params[:labels].split(','))
-          end
-
           present issue, with: Entities::Issue, current_user: current_user
         else
           render_validation_error!(issue)
@@ -188,12 +193,13 @@ module API
       #   state_event (optional) - The state event of an issue (close|reopen)
       #   updated_at (optional) - Date time string, ISO 8601 formatted
       #   due_date (optional)     - Date time string in the format YEAR-MONTH-DAY
+      #   confidential (optional) - Boolean parameter if the issue should be confidential
       # Example Request:
       #   PUT /projects/:id/issues/:issue_id
       put ':id/issues/:issue_id' do
         issue = user_project.issues.find(params[:issue_id])
         authorize! :update_issue, issue
-        keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date]
+        keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date, :confidential]
         keys << :updated_at if current_user.admin? || user_project.owner == current_user
         attrs = attributes_for_keys(keys)
 
@@ -202,17 +208,15 @@ module API
           render_api_error!({ labels: errors }, 400)
         end
 
+        attrs[:labels] = params[:labels] if params[:labels]
+
+        # Convert and filter out invalid confidential flags
+        attrs['confidential'] = to_boolean(attrs['confidential'])
+        attrs.delete('confidential') if attrs['confidential'].nil?
+
         issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
 
         if issue.valid?
-          # Find or create labels and attach to issue. Labels are valid because
-          # we already checked its name, so there can't be an error here
-          if params[:labels] && can?(current_user, :admin_issue, user_project)
-            issue.remove_labels
-            # Create and add labels to the new created issue
-            issue.add_labels_by_names(params[:labels].split(','))
-          end
-
           present issue, with: Entities::Issue, current_user: current_user
         else
           render_validation_error!(issue)
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
new file mode 100644
index 0000000..ae43a4a
--- /dev/null
+++ b/lib/api/lint.rb
@@ -0,0 +1,21 @@
+module API
+  class Lint < Grape::API
+    namespace :ci do
+      desc 'Validation of .gitlab-ci.yml content'
+      params do
+        requires :content, type: String, desc: 'Content of .gitlab-ci.yml'
+      end
+      post '/lint' do
+        error = Ci::GitlabCiYamlProcessor.validation_message(params[:content])
+
+        status 200
+
+        if error.blank?
+          { status: 'valid', errors: [] }
+        else
+          { status: 'invalid', errors: [error] }
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 94c1671..37f0a65 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -18,11 +18,11 @@ module API
         get ":id/members" do
           source = find_source(source_type, params[:id])
 
-          members = source.members.includes(:user)
-          members = members.joins(:user).merge(User.search(params[:query])) if params[:query]
-          members = paginate(members)
+          users = source.users
+          users = users.merge(User.search(params[:query])) if params[:query]
+          users = paginate(users)
 
-          present members.map(&:user), with: Entities::Member, members: members
+          present users, with: Entities::Member, source: source
         end
 
         # Get a group/project member
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
new file mode 100644
index 0000000..07435d7
--- /dev/null
+++ b/lib/api/merge_request_diffs.rb
@@ -0,0 +1,45 @@
+module API
+  # MergeRequestDiff API
+  class MergeRequestDiffs < Grape::API
+    before { authenticate! }
+
+    resource :projects do
+      desc 'Get a list of merge request diff versions' do
+        detail 'This feature was introduced in GitLab 8.12.'
+        success Entities::MergeRequestDiff
+      end
+
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
+        requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+      end
+
+      get ":id/merge_requests/:merge_request_id/versions" do
+        merge_request = user_project.merge_requests.
+          find(params[:merge_request_id])
+
+        authorize! :read_merge_request, merge_request
+        present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
+      end
+
+      desc 'Get a single merge request diff version' do
+        detail 'This feature was introduced in GitLab 8.12.'
+        success Entities::MergeRequestDiffFull
+      end
+
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
+        requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+        requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
+      end
+
+      get ":id/merge_requests/:merge_request_id/versions/:version_id" do
+        merge_request = user_project.merge_requests.
+          find(params[:merge_request_id])
+
+        authorize! :read_merge_request, merge_request
+        present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
+      end
+    end
+  end
+end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 8bfa998..c5c214d 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -83,12 +83,12 @@ module API
             opts[:created_at] = params[:created_at]
           end
 
-          @note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+          note = ::Notes::CreateService.new(user_project, current_user, opts).execute
 
-          if @note.valid?
-            present @note, with: Entities::Note
+          if note.valid?
+            present note, with: Entities::const_get(note.class.name)
           else
-            not_found!("Note #{@note.errors.messages}")
+            not_found!("Note #{note.errors.messages}")
           end
         end
 
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
new file mode 100644
index 0000000..a70a7e7
--- /dev/null
+++ b/lib/api/notification_settings.rb
@@ -0,0 +1,97 @@
+module API
+  # notification_settings API
+  class NotificationSettings < Grape::API
+    before { authenticate! }
+
+    helpers ::API::Helpers::MembersHelpers
+
+    resource :notification_settings do
+      desc 'Get global notification level settings and email, defaults to Participate' do
+        detail 'This feature was introduced in GitLab 8.12'
+        success Entities::GlobalNotificationSetting
+      end
+      get do
+        notification_setting = current_user.global_notification_setting
+
+        present notification_setting, with: Entities::GlobalNotificationSetting
+      end
+
+      desc 'Update global notification level settings and email, defaults to Participate' do
+        detail 'This feature was introduced in GitLab 8.12'
+        success Entities::GlobalNotificationSetting
+      end
+      params do
+        optional :level, type: String, desc: 'The global notification level'
+        optional :notification_email, type: String, desc: 'The email address to send notifications'
+        NotificationSetting::EMAIL_EVENTS.each do |event|
+          optional event, type: Boolean, desc: 'Enable/disable this notification'
+        end
+      end
+      put do
+        notification_setting = current_user.global_notification_setting
+
+        begin
+          notification_setting.transaction do
+            new_notification_email = params.delete(:notification_email)
+            declared_params = declared(params, include_missing: false).to_h
+
+            current_user.update(notification_email: new_notification_email) if new_notification_email
+            notification_setting.update(declared_params)
+          end
+        rescue ArgumentError => e # catch level enum error
+          render_api_error! e.to_s, 400
+        end
+
+        render_validation_error! current_user
+        render_validation_error! notification_setting
+        present notification_setting, with: Entities::GlobalNotificationSetting
+      end
+    end
+
+    %w[group project].each do |source_type|
+      resource source_type.pluralize do
+        desc "Get #{source_type} level notification level settings, defaults to Global" do
+          detail 'This feature was introduced in GitLab 8.12'
+          success Entities::NotificationSetting
+        end
+        params do
+          requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME'
+        end
+        get ":id/notification_settings" do
+          source = find_source(source_type, params[:id])
+
+          notification_setting = current_user.notification_settings_for(source)
+
+          present notification_setting, with: Entities::NotificationSetting
+        end
+
+        desc "Update #{source_type} level notification level settings, defaults to Global" do
+          detail 'This feature was introduced in GitLab 8.12'
+          success Entities::NotificationSetting
+        end
+        params do
+          requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME'
+          optional :level, type: String, desc: "The #{source_type} notification level"
+          NotificationSetting::EMAIL_EVENTS.each do |event|
+            optional event, type: Boolean, desc: 'Enable/disable this notification'
+          end
+        end
+        put ":id/notification_settings" do
+          source = find_source(source_type, params.delete(:id))
+          notification_setting = current_user.notification_settings_for(source)
+
+          begin
+            declared_params = declared(params, include_missing: false).to_h
+
+            notification_setting.update(declared_params)
+          rescue ArgumentError => e # catch level enum error
+            render_api_error! e.to_s, 400
+          end
+
+          render_validation_error! notification_setting
+          present notification_setting, with: Entities::NotificationSetting
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 2aae75c..2a0c8e1 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -13,11 +13,14 @@ module API
       params do
         optional :page,     type: Integer, desc: 'Page number of the current request'
         optional :per_page, type: Integer, desc: 'Number of items per page'
+        optional :scope,    type: String, values: ['running', 'branches', 'tags'],
+                            desc: 'Either running, branches, or tags'
       end
       get ':id/pipelines' do
         authorize! :read_pipeline, user_project
 
-        present paginate(user_project.pipelines), with: Entities::Pipeline
+        pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope])
+        present paginate(pipelines), with: Entities::Pipeline
       end
 
       desc 'Gets a specific pipeline for the project' do
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 3f63cd6..14f5be3 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -46,6 +46,7 @@ module API
           :note_events,
           :build_events,
           :pipeline_events,
+          :wiki_page_events,
           :enable_ssl_verification
         ]
         @hook = user_project.hooks.new(attrs)
@@ -80,6 +81,7 @@ module API
           :note_events,
           :build_events,
           :pipeline_events,
+          :wiki_page_events,
           :enable_ssl_verification
         ]
 
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 60cfc10..6d99617 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -51,7 +51,7 @@ module API
         @projects = current_user.viewable_starred_projects
         @projects = filter_projects(@projects)
         @projects = paginate @projects
-        present @projects, with: Entities::Project
+        present @projects, with: Entities::Project, user: current_user
       end
 
       # Get all projects for admin user
@@ -91,8 +91,8 @@ module API
       # Create new project
       #
       # Parameters:
-      #   name (required) - name for new project
-      #   description (optional) - short project description
+      #   name (required)                   - name for new project
+      #   description (optional)            - short project description
       #   issues_enabled (optional)
       #   merge_requests_enabled (optional)
       #   builds_enabled (optional)
@@ -100,30 +100,35 @@ module API
       #   snippets_enabled (optional)
       #   container_registry_enabled (optional)
       #   shared_runners_enabled (optional)
-      #   namespace_id (optional) - defaults to user namespace
-      #   public (optional) - if true same as setting visibility_level = 20
-      #   visibility_level (optional) - 0 by default
+      #   namespace_id (optional)           - defaults to user namespace
+      #   public (optional)                 - if true same as setting visibility_level = 20
+      #   visibility_level (optional)       - 0 by default
       #   import_url (optional)
       #   public_builds (optional)
+      #   lfs_enabled (optional)
+      #   request_access_enabled (optional) - Allow users to request member access
       # Example Request
       #   POST /projects
       post do
         required_attributes! [:name]
-        attrs = attributes_for_keys [:name,
-                                     :path,
+        attrs = attributes_for_keys [:builds_enabled,
+                                     :container_registry_enabled,
                                      :description,
+                                     :import_url,
                                      :issues_enabled,
+                                     :lfs_enabled,
                                      :merge_requests_enabled,
-                                     :builds_enabled,
-                                     :wiki_enabled,
-                                     :snippets_enabled,
-                                     :container_registry_enabled,
-                                     :shared_runners_enabled,
+                                     :name,
                                      :namespace_id,
+                                     :only_allow_merge_if_build_succeeds,
+                                     :path,
                                      :public,
+                                     :public_builds,
+                                     :request_access_enabled,
+                                     :shared_runners_enabled,
+                                     :snippets_enabled,
                                      :visibility_level,
-                                     :import_url,
-                                     :public_builds]
+                                     :wiki_enabled]
         attrs = map_public_to_visibility_level(attrs)
         @project = ::Projects::CreateService.new(current_user, attrs).execute
         if @project.saved?
@@ -140,10 +145,10 @@ module API
       # Create new project for a specified user.  Only available to admin users.
       #
       # Parameters:
-      #   user_id (required) - The ID of a user
-      #   name (required) - name for new project
-      #   description (optional) - short project description
-      #   default_branch (optional) - 'master' by default
+      #   user_id (required)                - The ID of a user
+      #   name (required)                   - name for new project
+      #   description (optional)            - short project description
+      #   default_branch (optional)         - 'master' by default
       #   issues_enabled (optional)
       #   merge_requests_enabled (optional)
       #   builds_enabled (optional)
@@ -151,28 +156,33 @@ module API
       #   snippets_enabled (optional)
       #   container_registry_enabled (optional)
       #   shared_runners_enabled (optional)
-      #   public (optional) - if true same as setting visibility_level = 20
+      #   public (optional)                 - if true same as setting visibility_level = 20
       #   visibility_level (optional)
       #   import_url (optional)
       #   public_builds (optional)
+      #   lfs_enabled (optional)
+      #   request_access_enabled (optional) - Allow users to request member access
       # Example Request
       #   POST /projects/user/:user_id
       post "user/:user_id" do
         authenticated_as_admin!
         user = User.find(params[:user_id])
-        attrs = attributes_for_keys [:name,
-                                     :description,
+        attrs = attributes_for_keys [:builds_enabled,
                                      :default_branch,
+                                     :description,
+                                     :import_url,
                                      :issues_enabled,
+                                     :lfs_enabled,
                                      :merge_requests_enabled,
-                                     :builds_enabled,
-                                     :wiki_enabled,
-                                     :snippets_enabled,
-                                     :shared_runners_enabled,
+                                     :name,
+                                     :only_allow_merge_if_build_succeeds,
                                      :public,
+                                     :public_builds,
+                                     :request_access_enabled,
+                                     :shared_runners_enabled,
+                                     :snippets_enabled,
                                      :visibility_level,
-                                     :import_url,
-                                     :public_builds]
+                                     :wiki_enabled]
         attrs = map_public_to_visibility_level(attrs)
         @project = ::Projects::CreateService.new(user, attrs).execute
         if @project.saved?
@@ -183,16 +193,32 @@ module API
         end
       end
 
-      # Fork new project for the current user.
+      # Fork new project for the current user or provided namespace.
       #
       # Parameters:
       #   id (required) - The ID of a project
+      #   namespace (optional) - The ID or name of the namespace that the project will be forked into.
       # Example Request
       #   POST /projects/fork/:id
       post 'fork/:id' do
+        attrs = {}
+        namespace_id = params[:namespace]
+
+        if namespace_id.present?
+          namespace = Namespace.find_by(id: namespace_id) || Namespace.find_by_path_or_name(namespace_id)
+
+          unless namespace && can?(current_user, :create_projects, namespace)
+            not_found!('Target Namespace')
+          end
+
+          attrs[:namespace] = namespace
+        end
+
         @forked_project =
           ::Projects::ForkService.new(user_project,
-                                      current_user).execute
+                                      current_user,
+                                      attrs).execute
+
         if @forked_project.errors.any?
           conflict!(@forked_project.errors.messages)
         else
@@ -218,23 +244,27 @@ module API
       #   public (optional) - if true same as setting visibility_level = 20
       #   visibility_level (optional) - visibility level of a project
       #   public_builds (optional)
+      #   lfs_enabled (optional)
       # Example Request
       #   PUT /projects/:id
       put ':id' do
-        attrs = attributes_for_keys [:name,
-                                     :path,
-                                     :description,
+        attrs = attributes_for_keys [:builds_enabled,
+                                     :container_registry_enabled,
                                      :default_branch,
+                                     :description,
                                      :issues_enabled,
+                                     :lfs_enabled,
                                      :merge_requests_enabled,
-                                     :builds_enabled,
-                                     :wiki_enabled,
-                                     :snippets_enabled,
-                                     :container_registry_enabled,
-                                     :shared_runners_enabled,
+                                     :name,
+                                     :only_allow_merge_if_build_succeeds,
+                                     :path,
                                      :public,
+                                     :public_builds,
+                                     :request_access_enabled,
+                                     :shared_runners_enabled,
+                                     :snippets_enabled,
                                      :visibility_level,
-                                     :public_builds]
+                                     :wiki_enabled]
         attrs = map_public_to_visibility_level(attrs)
         authorize_admin_project
         authorize! :rename_project, user_project if attrs[:name].present?
@@ -405,18 +435,9 @@ module API
       # Example Request:
       #   GET /projects/search/:query
       get "/search/:query" do
-        ids = current_user.authorized_projects.map(&:id)
-        visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ]
-        projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%")
-        sort = params[:sort] == 'desc' ? 'desc' : 'asc'
-
-        projects = case params["order_by"]
-                   when 'id' then projects.order("id #{sort}")
-                   when 'name' then projects.order("name #{sort}")
-                   when 'created_at' then projects.order("created_at #{sort}")
-                   when 'last_activity_at' then projects.order("last_activity_at #{sort}")
-                   else projects
-                   end
+        search_service = Search::GlobalService.new(current_user, search: params[:query]).execute
+        projects = search_service.objects('projects', params[:page])
+        projects = projects.reorder(project_order_by => project_sort)
 
         present paginate(projects), with: Entities::Project
       end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 8a376d3..c440305 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -327,7 +327,7 @@ module API
       # Example Request:
       #   GET /user
       get do
-        present @current_user, with: Entities::UserLogin
+        present @current_user, with: Entities::UserFull
       end
 
       # Get currently authenticated user's keys
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index f117fc3..9fcd9a3 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -55,7 +55,7 @@ module Backup
         bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s)
         FileUtils.mv(path, bk_repos_path)
         # This is expected from gitlab:check
-        FileUtils.mkdir_p(path, mode: 2770)
+        FileUtils.mkdir_p(path, mode: 02770)
       end
 
       Project.find_each(batch_size: 1000) do |project|
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index d77a5e3..16cd774 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -18,10 +18,6 @@ module Banzai
         @object_sym ||= object_name.to_sym
       end
 
-      def self.object_class_title
-        @object_title ||= object_class.name.titleize
-      end
-
       # Public: Find references in text (like `!123` for merge requests)
       #
       #   AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
@@ -49,10 +45,6 @@ module Banzai
         self.class.object_sym
       end
 
-      def object_class_title
-        self.class.object_class_title
-      end
-
       def references_in(*args, &block)
         self.class.references_in(*args, &block)
       end
@@ -198,7 +190,7 @@ module Banzai
       end
 
       def object_link_title(object)
-        "#{object_class_title}: #{object.title}"
+        object.title
       end
 
       def object_link_text(object, matches)
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index bbb88c9..4358bf4 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -35,7 +35,7 @@ module Banzai
       end
 
       def object_link_title(range)
-        range.reference_title
+        nil
       end
     end
   end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index 2ce1816..a26dd09 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -28,10 +28,6 @@ module Banzai
                                         only_path: context[:only_path])
       end
 
-      def object_link_title(commit)
-        commit.link_title
-      end
-
       def object_link_text_extras(object, matches)
         extras = super
 
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index e258dc8..8f262ef 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -70,6 +70,11 @@ module Banzai
       def unescape_html_entities(text)
         CGI.unescapeHTML(text.to_s)
       end
+
+      def object_link_title(object)
+        # use title of wrapped element instead
+        nil
+      end
     end
   end
 end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index ca686c8..58fff49 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -59,6 +59,10 @@ module Banzai
             html_safe
         end
       end
+
+      def object_link_title(object)
+        nil
+      end
     end
   end
 end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index bf05824..2d22129 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -52,7 +52,7 @@ module Banzai
       end
 
       def reference_class(type)
-        "gfm gfm-#{type}"
+        "gfm gfm-#{type} has-tooltip"
       end
 
       # Ensure that a :project key exists in context
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 6e13282..2470362 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -7,7 +7,7 @@ module Banzai
       UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
 
       def whitelist
-        whitelist = super.dup
+        whitelist = super
 
         customize_whitelist(whitelist)
 
@@ -42,58 +42,58 @@ module Banzai
         # Allow any protocol in `a` elements...
         whitelist[:protocols].delete('a')
 
-        whitelist[:transformers] = whitelist[:transformers].dup
-
         # ...but then remove links with unsafe protocols
-        whitelist[:transformers].push(remove_unsafe_links)
+        whitelist[:transformers].push(self.class.remove_unsafe_links)
 
         # Remove `rel` attribute from `a` elements
-        whitelist[:transformers].push(remove_rel)
+        whitelist[:transformers].push(self.class.remove_rel)
 
         # Remove `class` attribute from non-highlight spans
-        whitelist[:transformers].push(clean_spans)
+        whitelist[:transformers].push(self.class.clean_spans)
 
         whitelist
       end
 
-      def remove_unsafe_links
-        lambda do |env|
-          node = env[:node]
+      class << self
+        def remove_unsafe_links
+          lambda do |env|
+            node = env[:node]
 
-          return unless node.name == 'a'
-          return unless node.has_attribute?('href')
+            return unless node.name == 'a'
+            return unless node.has_attribute?('href')
 
-          begin
-            uri = Addressable::URI.parse(node['href'])
-            uri.scheme = uri.scheme.strip.downcase if uri.scheme
+            begin
+              uri = Addressable::URI.parse(node['href'])
+              uri.scheme = uri.scheme.strip.downcase if uri.scheme
 
-            node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
-          rescue Addressable::URI::InvalidURIError
-            node.remove_attribute('href')
+              node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
+            rescue Addressable::URI::InvalidURIError
+              node.remove_attribute('href')
+            end
           end
         end
-      end
 
-      def remove_rel
-        lambda do |env|
-          if env[:node_name] == 'a'
-            env[:node].remove_attribute('rel')
+        def remove_rel
+          lambda do |env|
+            if env[:node_name] == 'a'
+              env[:node].remove_attribute('rel')
+            end
           end
         end
-      end
 
-      def clean_spans
-        lambda do |env|
-          node = env[:node]
+        def clean_spans
+          lambda do |env|
+            node = env[:node]
 
-          return unless node.name == 'span'
-          return unless node.has_attribute?('class')
+            return unless node.name == 'span'
+            return unless node.has_attribute?('class')
 
-          unless has_ancestor?(node, 'pre')
-            node.remove_attribute('class')
-          end
+            unless node.ancestors.any? { |n| n.name.casecmp('pre').zero? }
+              node.remove_attribute('class')
+            end
 
-          { node_whitelist: [node] }
+            { node_whitelist: [node] }
+          end
         end
       end
     end
diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb
index 2e2c8da..e7a1ec8 100644
--- a/lib/banzai/filter/wiki_link_filter/rewriter.rb
+++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb
@@ -31,6 +31,7 @@ module Banzai
         def apply_relative_link_rules!
           if @uri.relative? && @uri.path.present?
             link = ::File.join(@wiki_base_path, @uri.path)
+            link = "#{link}##{@uri.fragment}" if @uri.fragment
             @uri = Addressable::URI.parse(link)
           end
         end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 6cf218a..e8e03e4 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -211,7 +211,7 @@ module Banzai
       end
 
       def can?(user, permission, subject)
-        Ability.abilities.allowed?(user, permission, subject)
+        Ability.allowed?(user, permission, subject)
       end
 
       def find_projects_for_hash_keys(hash)
diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
index 17bb99a..a6b9bee 100644
--- a/lib/ci/api/api.rb
+++ b/lib/ci/api/api.rb
@@ -9,22 +9,14 @@ module Ci
       end
 
       rescue_from :all do |exception|
-        # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
-        # why is this not wrapped in something reusable?
-        trace = exception.backtrace
-
-        message = "\n#{exception.class} (#{exception.message}):\n"
-        message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
-        message << "  " << trace.join("\n  ")
-
-        API.logger.add Logger::FATAL, message
-        rack_response({ 'message' => '500 Internal Server Error' }, 500)
+        handle_api_exception(exception)
       end
 
       content_type :txt,  'text/plain'
       content_type :json, 'application/json'
       format :json
 
+      helpers ::SentryHelper
       helpers ::Ci::API::Helpers
       helpers ::API::Helpers
       helpers Gitlab::CurrentSettings
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 9f3b582..59f8541 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -12,7 +12,7 @@ module Ci
         #   POST /builds/register
         post "register" do
           authenticate_runner!
-          update_runner_last_contact
+          update_runner_last_contact(save: false)
           update_runner_info
           required_attributes! [:token]
           not_found! unless current_runner.active?
@@ -27,7 +27,7 @@ module Ci
           else
             Gitlab::Metrics.add_event(:build_not_found)
 
-            not_found!
+            build_not_found!
           end
         end
 
@@ -101,6 +101,7 @@ module Ci
         #   POST /builds/:id/artifacts/authorize
         post ":id/artifacts/authorize" do
           require_gitlab_workhorse!
+          Gitlab::Workhorse.verify_api_request!(headers)
           not_allowed! unless Gitlab.config.artifacts.enabled
           build = Ci::Build.find_by_id(params[:id])
           not_found! unless build
@@ -113,7 +114,8 @@ module Ci
           end
 
           status 200
-          { TempPath: ArtifactUploader.artifacts_upload_path }
+          content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+          Gitlab::Workhorse.artifact_upload_ok
         end
 
         # Upload artifacts to build - Runners only
diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb
index 3f5bdab..66c0577 100644
--- a/lib/ci/api/entities.rb
+++ b/lib/ci/api/entities.rb
@@ -15,6 +15,15 @@ module Ci
         expose :filename, :size
       end
 
+      class BuildOptions < Grape::Entity
+        expose :image
+        expose :services
+        expose :artifacts
+        expose :cache
+        expose :dependencies
+        expose :after_script
+      end
+
       class Build < Grape::Entity
         expose :id, :ref, :tag, :sha, :status
         expose :name, :token, :stage
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 199d62d..23353c6 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -3,7 +3,7 @@ module Ci
     module Helpers
       BUILD_TOKEN_HEADER = "HTTP_BUILD_TOKEN"
       BUILD_TOKEN_PARAM = :token
-      UPDATE_RUNNER_EVERY = 60
+      UPDATE_RUNNER_EVERY = 40 * 60
 
       def authenticate_runners!
         forbidden! unless runner_registration_token_valid?
@@ -14,19 +14,37 @@ module Ci
       end
 
       def authenticate_build_token!(build)
-        token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s
-        forbidden! unless token && build.valid_token?(token)
+        forbidden! unless build_token_valid?(build)
       end
 
       def runner_registration_token_valid?
-        params[:token] == current_application_settings.runners_registration_token
+        ActiveSupport::SecurityUtils.variable_size_secure_compare(
+          params[:token],
+          current_application_settings.runners_registration_token)
+      end
+
+      def build_token_valid?(build)
+        token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s
+
+        # We require to also check `runners_token` to maintain compatibility with old version of runners
+        token && (build.valid_token?(token) || build.project.valid_runners_token?(token))
       end
 
-      def update_runner_last_contact
+      def update_runner_last_contact(save: true)
         # Use a random threshold to prevent beating DB updates
+        # it generates a distribution between: [40m, 80m]
         contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY)
         if current_runner.contacted_at.nil? || Time.now - current_runner.contacted_at >= contacted_at_max_age
-          current_runner.update_attributes(contacted_at: Time.now)
+          current_runner.contacted_at = Time.now
+          current_runner.save if current_runner.changed? && save
+        end
+      end
+
+      def build_not_found!
+        if headers['User-Agent'].match(/gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /)
+          no_content!
+        else
+          not_found!
         end
       end
 
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 47efd5b..0369e80 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -55,29 +55,36 @@ module Ci
       {
         stage_idx: @stages.index(job[:stage]),
         stage: job[:stage],
-        ##
-        # Refactoring note:
-        #  - before script behaves differently than after script
-        #  - after script returns an array of commands
-        #  - before script should be a concatenated command
-        commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
+        commands: job[:commands],
         tag_list: job[:tags] || [],
         name: job[:name].to_s,
         allow_failure: job[:allow_failure] || false,
         when: job[:when] || 'on_success',
-        environment: job[:environment],
+        environment: job[:environment_name],
         yaml_variables: yaml_variables(name),
         options: {
-          image: job[:image] || @image,
-          services: job[:services] || @services,
+          image: job[:image],
+          services: job[:services],
           artifacts: job[:artifacts],
-          cache: job[:cache] || @cache,
+          cache: job[:cache],
           dependencies: job[:dependencies],
-          after_script: job[:after_script] || @after_script,
+          after_script: job[:after_script],
+          environment: job[:environment],
         }.compact
       }
     end
 
+    def self.validation_message(content)
+      return 'Please provide content of .gitlab-ci.yml' if content.blank?
+
+      begin
+        Ci::GitlabCiYamlProcessor.new(content)
+        nil
+      rescue ValidationError, Psych::SyntaxError => e
+        e.message
+      end
+    end
+
     private
 
     def initial_parsing
diff --git a/lib/ci/mask_secret.rb b/lib/ci/mask_secret.rb
new file mode 100644
index 0000000..997377a
--- /dev/null
+++ b/lib/ci/mask_secret.rb
@@ -0,0 +1,10 @@
+module Ci::MaskSecret
+  class << self
+    def mask!(value, token)
+      return value unless value.present? && token.present?
+
+      value.gsub!(token, 'x' * token.length)
+      value
+    end
+  end
+end
diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb
new file mode 100644
index 0000000..7b1533d
--- /dev/null
+++ b/lib/expand_variables.rb
@@ -0,0 +1,17 @@
+module ExpandVariables
+  class << self
+    def expand(value, variables)
+      # Convert hash array to variables
+      if variables.is_a?(Array)
+        variables = variables.reduce({}) do |hash, variable|
+          hash[variable[:key]] = variable[:value]
+          hash
+        end
+      end
+
+      value.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
+        variables[$1 || $2]
+      end
+    end
+  end
+end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index a293fa2..a4558d1 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -94,9 +94,7 @@ module ExtractsPath
     @options = params.select {|key, value| allowed_options.include?(key) && !value.blank? }
     @options = HashWithIndifferentAccess.new(@options)
 
-    @id = params[:id] || params[:ref]
-    @id += "/" + params[:path] unless params[:path].blank?
-
+    @id = get_id
     @ref, @path = extract_ref(@id)
     @repo = @project.repository
     if @options[:extended_sha1].blank?
@@ -118,4 +116,13 @@ module ExtractsPath
   def tree
     @tree ||= @repo.tree(@commit.id, @path)
   end
+
+  private
+
+  # overriden in subclasses, do not remove
+  def get_id
+    id = params[:id] || params[:ref]
+    id += "/" + params[:path] unless params[:path].blank?
+    id
+  end
 end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 91f0270..7c0f211 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,21 +1,22 @@
 module Gitlab
   module Auth
-    Result = Struct.new(:user, :type)
+    class MissingPersonalTokenError < StandardError; end
 
     class << self
       def find_for_git_client(login, password, project:, ip:)
         raise "Must provide an IP for rate limiting" if ip.nil?
 
-        result = Result.new
+        result =
+          service_request_check(login, password, project) ||
+          build_access_token_check(login, password) ||
+          user_with_password_for_git(login, password) ||
+          oauth_access_token_check(login, password) ||
+          lfs_token_check(login, password) ||
+          personal_access_token_check(login, password) ||
+          Gitlab::Auth::Result.new
 
-        if valid_ci_request?(login, password, project)
-          result.type = :ci
-        else
-          result = populate_result(login, password)
-        end
+        rate_limit!(ip, success: result.success?, login: login)
 
-        success = result.user.present? || [:ci, :missing_personal_token].include?(result.type)
-        rate_limit!(ip, success: success, login: login)
         result
       end
 
@@ -57,44 +58,31 @@ module Gitlab
 
       private
 
-      def valid_ci_request?(login, password, project)
+      def service_request_check(login, password, project)
         matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login)
 
-        return false unless project && matched_login.present?
+        return unless project && matched_login.present?
 
         underscored_service = matched_login['service'].underscore
 
-        if underscored_service == 'gitlab_ci'
-          project && project.valid_build_token?(password)
-        elsif Service.available_services_names.include?(underscored_service)
+        if Service.available_services_names.include?(underscored_service)
           # We treat underscored_service as a trusted input because it is included
           # in the Service.available_services_names whitelist.
           service = project.public_send("#{underscored_service}_service")
 
-          service && service.activated? && service.valid_token?(password)
-        end
-      end
-
-      def populate_result(login, password)
-        result =
-          user_with_password_for_git(login, password) ||
-          oauth_access_token_check(login, password) ||
-          personal_access_token_check(login, password)
-
-        if result
-          result.type = nil unless result.user
-
-          if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap
-            result.type = :missing_personal_token
+          if service && service.activated? && service.valid_token?(password)
+            Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities)
           end
         end
-
-        result || Result.new
       end
 
       def user_with_password_for_git(login, password)
         user = find_with_user_password(login, password)
-        Result.new(user, :gitlab_or_ldap) if user
+        return unless user
+
+        raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
+
+        Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
       end
 
       def oauth_access_token_check(login, password)
@@ -102,7 +90,7 @@ module Gitlab
           token = Doorkeeper::AccessToken.by_token(password)
           if token && token.accessible?
             user = User.find_by(id: token.resource_owner_id)
-            Result.new(user, :oauth)
+            Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities)
           end
         end
       end
@@ -111,9 +99,76 @@ module Gitlab
         if login && password
           user = User.find_by_personal_access_token(password)
           validation = User.by_login(login)
-          Result.new(user, :personal_token) if user == validation
+          Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities) if user.present? && user == validation
+        end
+      end
+
+      def lfs_token_check(login, password)
+        deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/)
+
+        actor =
+          if deploy_key_matches
+            DeployKey.find(deploy_key_matches[1])
+          else
+            User.by_login(login)
+          end
+
+        return unless actor
+
+        token_handler = Gitlab::LfsToken.new(actor)
+
+        authentication_abilities =
+          if token_handler.user?
+            full_authentication_abilities
+          else
+            read_authentication_abilities
+          end
+
+        Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.value, password)
+      end
+
+      def build_access_token_check(login, password)
+        return unless login == 'gitlab-ci-token'
+        return unless password
+
+        build = ::Ci::Build.running.find_by_token(password)
+        return unless build
+        return unless build.project.builds_enabled?
+
+        if build.user
+          # If user is assigned to build, use restricted credentials of user
+          Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
+        else
+          # Otherwise use generic CI credentials (backward compatibility)
+          Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities)
         end
       end
+
+      public
+
+      def build_authentication_abilities
+        [
+          :read_project,
+          :build_download_code,
+          :build_read_container_image,
+          :build_create_container_image
+        ]
+      end
+
+      def read_authentication_abilities
+        [
+          :read_project,
+          :download_code,
+          :read_container_image
+        ]
+      end
+
+      def full_authentication_abilities
+        read_authentication_abilities + [
+          :push_code,
+          :create_container_image
+        ]
+      end
     end
   end
 end
diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb
new file mode 100644
index 0000000..6be7f69
--- /dev/null
+++ b/lib/gitlab/auth/result.rb
@@ -0,0 +1,21 @@
+module Gitlab
+  module Auth
+    Result = Struct.new(:actor, :project, :type, :authentication_abilities) do
+      def ci?(for_project)
+        type == :ci &&
+          project &&
+          project == for_project
+      end
+
+      def lfs_deploy_token?(for_project)
+        type == :lfs_deploy_token &&
+          actor &&
+          actor.projects.include?(for_project)
+      end
+
+      def success?
+        actor.present? || type == :ci
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 839a4fa..79eac66 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -6,7 +6,12 @@ module Gitlab
 
     KeyAdder = Struct.new(:io) do
       def add_key(id, key)
-        key.gsub!(/[[:space:]]+/, ' ').strip!
+        key = Gitlab::Shell.strip_key(key)
+        # Newline and tab are part of the 'protocol' used to transmit id+key to the other end
+        if key.include?("\t") || key.include?("\n")
+          raise Error.new("Invalid key: #{key.inspect}")
+        end
+
         io.puts("#{id}\t#{key}")
       end
     end
@@ -16,6 +21,10 @@ module Gitlab
         @version_required ||= File.read(Rails.root.
                                         join('GITLAB_SHELL_VERSION')).strip
       end
+
+      def strip_key(key)
+        key.split(/ /)[0, 2].join(' ')
+      end
     end
 
     # Init new repository
@@ -107,7 +116,7 @@ module Gitlab
     #
     def add_key(key_id, key_content)
       Gitlab::Utils.system_silent([gitlab_shell_keys_path,
-                                   'add-key', key_id, key_content])
+                                   'add-key', key_id, self.class.strip_key(key_content)])
     end
 
     # Batch-add keys to authorized_keys
@@ -195,7 +204,7 @@ module Gitlab
     # Create (if necessary) and link the secret token file
     def generate_and_link_secret_token
       secret_file = Gitlab.config.gitlab_shell.secret_file
-      unless File.exist? secret_file
+      unless File.size?(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)
diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb
index 95d925d..9a04823 100644
--- a/lib/gitlab/badge/coverage/report.rb
+++ b/lib/gitlab/badge/coverage/report.rb
@@ -12,9 +12,7 @@ module Gitlab
           @ref = ref
           @job = job
 
-          @pipeline = @project.pipelines
-            .latest_successful_for(@ref)
-            .first
+          @pipeline = @project.pipelines.latest_successful_for(@ref)
         end
 
         def entity
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 7beaecd..f4b5097 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -21,7 +21,7 @@ module Gitlab
 
       private
 
-      def gl_user_id(project, bitbucket_id)
+      def gitlab_user_id(project, bitbucket_id)
         if bitbucket_id
           user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
           (user && user.id) || project.creator_id
@@ -74,7 +74,7 @@ module Gitlab
             description: body,
             title: issue["title"],
             state: %w(resolved invalid duplicate wontfix closed).include?(issue["status"]) ? 'closed' : 'opened',
-            author_id: gl_user_id(project, reporter)
+            author_id: gitlab_user_id(project, reporter)
           )
         end
       rescue ActiveRecord::RecordInvalid => e
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 4b32eb9..cb10652 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -23,6 +23,7 @@ module Gitlab
       protected
 
       def protected_branch_checks
+        return unless @branch_name
         return unless project.protected_branch?(@branch_name)
 
         if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches)
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index ae82c0d..bbfa6cf 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -14,7 +14,7 @@ module Gitlab
         @config = Loader.new(config).load!
 
         @global = Node::Global.new(@config)
-        @global.process!
+        @global.compose!
       end
 
       def valid?
diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb
index 2de82d4..6b7ab2f 100644
--- a/lib/gitlab/ci/config/node/configurable.rb
+++ b/lib/gitlab/ci/config/node/configurable.rb
@@ -23,9 +23,9 @@ module Gitlab
             end
           end
 
-          private
+          def compose!(deps = nil)
+            return unless valid?
 
-          def compose!
             self.class.nodes.each do |key, factory|
               factory
                 .value(@config[key])
@@ -33,6 +33,12 @@ module Gitlab
 
               @entries[key] = factory.create!
             end
+
+            yield if block_given?
+
+            @entries.each_value do |entry|
+              entry.compose!(deps)
+            end
           end
 
           class_methods do
diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb
index 0c782c4..8717eab 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -20,11 +20,14 @@ module Gitlab
             @validator.validate(:new)
           end
 
-          def process!
+          def [](key)
+            @entries[key] || Node::Undefined.new
+          end
+
+          def compose!(deps = nil)
             return unless valid?
 
-            compose!
-            descendants.each(&:process!)
+            yield if block_given?
           end
 
           def leaf?
@@ -73,11 +76,6 @@ module Gitlab
           def self.validator
             Validator
           end
-
-          private
-
-          def compose!
-          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb
new file mode 100644
index 0000000..d388ab6
--- /dev/null
+++ b/lib/gitlab/ci/config/node/environment.rb
@@ -0,0 +1,68 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents an environment.
+        #
+        class Environment < Entry
+          include Validatable
+
+          ALLOWED_KEYS = %i[name url]
+
+          validations do
+            validate do
+              unless hash? || string?
+                errors.add(:config, 'should be a hash or a string')
+              end
+            end
+
+            validates :name, presence: true
+            validates :name,
+              type: {
+                with: String,
+                message: Gitlab::Regex.environment_name_regex_message }
+
+            validates :name,
+              format: {
+                with: Gitlab::Regex.environment_name_regex,
+                message: Gitlab::Regex.environment_name_regex_message }
+
+            with_options if: :hash? do
+              validates :config, allowed_keys: ALLOWED_KEYS
+
+              validates :url,
+                        length: { maximum: 255 },
+                        addressable_url: true,
+                        allow_nil: true
+            end
+          end
+
+          def hash?
+            @config.is_a?(Hash)
+          end
+
+          def string?
+            @config.is_a?(String)
+          end
+
+          def name
+            value[:name]
+          end
+
+          def url
+            value[:url]
+          end
+
+          def value
+            case @config
+            when String then { name: @config }
+            when Hash then @config
+            else {}
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb
index 707b052..5387f29 100644
--- a/lib/gitlab/ci/config/node/factory.rb
+++ b/lib/gitlab/ci/config/node/factory.rb
@@ -37,8 +37,8 @@ module Gitlab
             # See issue #18775.
             #
             if @value.nil?
-              Node::Undefined.new(
-                fabricate_undefined
+              Node::Unspecified.new(
+                fabricate_unspecified
               )
             else
               fabricate(@node, @value)
@@ -47,13 +47,13 @@ module Gitlab
 
           private
 
-          def fabricate_undefined
+          def fabricate_unspecified
             ##
             # If node has a default value we fabricate concrete node
             # with default value.
             #
             if @node.default.nil?
-              fabricate(Node::Null)
+              fabricate(Node::Undefined)
             else
               fabricate(@node, @node.default)
             end
diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb
index ccd539f..2a2943c 100644
--- a/lib/gitlab/ci/config/node/global.rb
+++ b/lib/gitlab/ci/config/node/global.rb
@@ -36,15 +36,15 @@ module Gitlab
           helpers :before_script, :image, :services, :after_script,
                   :variables, :stages, :types, :cache, :jobs
 
-          private
-
-          def compose!
-            super
-
-            compose_jobs!
-            compose_deprecated_entries!
+          def compose!(_deps = nil)
+            super(self) do
+              compose_jobs!
+              compose_deprecated_entries!
+            end
           end
 
+          private
+
           def compose_jobs!
             factory = Node::Factory.new(Node::Jobs)
               .value(@config.except(*self.class.nodes.keys))
diff --git a/lib/gitlab/ci/config/node/hidden.rb b/lib/gitlab/ci/config/node/hidden.rb
new file mode 100644
index 0000000..fe4ee8a
--- /dev/null
+++ b/lib/gitlab/ci/config/node/hidden.rb
@@ -0,0 +1,22 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents a hidden CI/CD job.
+        #
+        class Hidden < Entry
+          include Validatable
+
+          validations do
+            validates :config, presence: true
+          end
+
+          def relevant?
+            false
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/hidden_job.rb b/lib/gitlab/ci/config/node/hidden_job.rb
deleted file mode 100644
index 073044b..0000000
--- a/lib/gitlab/ci/config/node/hidden_job.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module Gitlab
-  module Ci
-    class Config
-      module Node
-        ##
-        # Entry that represents a hidden CI/CD job.
-        #
-        class HiddenJob < Entry
-          include Validatable
-
-          validations do
-            validates :config, type: Hash
-            validates :config, presence: true
-          end
-
-          def relevant?
-            false
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb
index e84737a..603334d 100644
--- a/lib/gitlab/ci/config/node/job.rb
+++ b/lib/gitlab/ci/config/node/job.rb
@@ -13,7 +13,7 @@ module Gitlab
                             type stage when artifacts cache dependencies before_script
                             after_script variables environment]
 
-          attributes :tags, :allow_failure, :when, :environment, :dependencies
+          attributes :tags, :allow_failure, :when, :dependencies
 
           validations do
             validates :config, allowed_keys: ALLOWED_KEYS
@@ -29,58 +29,65 @@ module Gitlab
                 inclusion: { in: %w[on_success on_failure always manual],
                              message: 'should be on_success, on_failure, ' \
                                       'always or manual' }
-              validates :environment,
-                type: {
-                  with: String,
-                  message: Gitlab::Regex.environment_name_regex_message }
-              validates :environment,
-                format: {
-                  with: Gitlab::Regex.environment_name_regex,
-                  message: Gitlab::Regex.environment_name_regex_message }
 
               validates :dependencies, array_of_strings: true
             end
           end
 
-          node :before_script, Script,
+          node :before_script, Node::Script,
             description: 'Global before script overridden in this job.'
 
-          node :script, Commands,
+          node :script, Node::Commands,
             description: 'Commands that will be executed in this job.'
 
-          node :stage, Stage,
+          node :stage, Node::Stage,
             description: 'Pipeline stage this job will be executed into.'
 
-          node :type, Stage,
+          node :type, Node::Stage,
             description: 'Deprecated: stage this job will be executed into.'
 
-          node :after_script, Script,
+          node :after_script, Node::Script,
             description: 'Commands that will be executed when finishing job.'
 
-          node :cache, Cache,
+          node :cache, Node::Cache,
             description: 'Cache definition for this job.'
 
-          node :image, Image,
+          node :image, Node::Image,
             description: 'Image that will be used to execute this job.'
 
-          node :services, Services,
+          node :services, Node::Services,
             description: 'Services that will be used to execute this job.'
 
-          node :only, Trigger,
+          node :only, Node::Trigger,
             description: 'Refs policy this job will be executed for.'
 
-          node :except, Trigger,
+          node :except, Node::Trigger,
             description: 'Refs policy this job will be executed for.'
 
-          node :variables, Variables,
+          node :variables, Node::Variables,
             description: 'Environment variables available for this job.'
 
-          node :artifacts, Artifacts,
+          node :artifacts, Node::Artifacts,
             description: 'Artifacts configuration for this job.'
 
+          node :environment, Node::Environment,
+               description: 'Environment configuration for this job.'
+
           helpers :before_script, :script, :stage, :type, :after_script,
                   :cache, :image, :services, :only, :except, :variables,
-                  :artifacts
+                  :artifacts, :commands, :environment
+
+          def compose!(deps = nil)
+            super do
+              if type_defined? && !stage_defined?
+                @entries[:stage] = @entries[:type]
+              end
+
+              @entries.delete(:type)
+            end
+
+            inherit!(deps)
+          end
 
           def name
             @metadata[:name]
@@ -90,12 +97,30 @@ module Gitlab
             @config.merge(to_hash.compact)
           end
 
+          def commands
+            (before_script_value.to_a + script_value.to_a).join("\n")
+          end
+
           private
 
+          def inherit!(deps)
+            return unless deps
+
+            self.class.nodes.each_key do |key|
+              global_entry = deps[key]
+              job_entry = @entries[key]
+
+              if global_entry.specified? && !job_entry.specified?
+                @entries[key] = global_entry
+              end
+            end
+          end
+
           def to_hash
             { name: name,
               before_script: before_script,
               script: script,
+              commands: commands,
               image: image,
               services: services,
               stage: stage,
@@ -103,19 +128,11 @@ module Gitlab
               only: only,
               except: except,
               variables: variables_defined? ? variables : nil,
+              environment: environment_defined? ? environment : nil,
+              environment_name: environment_defined? ? environment[:name] : nil,
               artifacts: artifacts,
               after_script: after_script }
           end
-
-          def compose!
-            super
-
-            if type_defined? && !stage_defined?
-              @entries[:stage] = @entries[:type]
-            end
-
-            @entries.delete(:type)
-          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb
index 51683c8..d10e80d 100644
--- a/lib/gitlab/ci/config/node/jobs.rb
+++ b/lib/gitlab/ci/config/node/jobs.rb
@@ -26,19 +26,23 @@ module Gitlab
             name.to_s.start_with?('.')
           end
 
-          private
-
-          def compose!
-            @config.each do |name, config|
-              node = hidden?(name) ? Node::HiddenJob : Node::Job
-
-              factory = Node::Factory.new(node)
-                .value(config || {})
-                .metadata(name: name)
-                .with(key: name, parent: self,
-                      description: "#{name} job definition.")
+          def compose!(deps = nil)
+            super do
+              @config.each do |name, config|
+                node = hidden?(name) ? Node::Hidden : Node::Job
+
+                factory = Node::Factory.new(node)
+                  .value(config || {})
+                  .metadata(name: name)
+                  .with(key: name, parent: self,
+                        description: "#{name} job definition.")
+
+                @entries[name] = factory.create!
+              end
 
-              @entries[name] = factory.create!
+              @entries.each_value do |entry|
+                entry.compose!(deps)
+              end
             end
           end
         end
diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb
deleted file mode 100644
index 88a5f53..0000000
--- a/lib/gitlab/ci/config/node/null.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Gitlab
-  module Ci
-    class Config
-      module Node
-        ##
-        # This class represents an undefined node.
-        #
-        # Implements the Null Object pattern.
-        #
-        class Null < Entry
-          def value
-            nil
-          end
-
-          def valid?
-            true
-          end
-
-          def errors
-            []
-          end
-
-          def specified?
-            false
-          end
-
-          def relevant?
-            false
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb
index 45fef8c..33e7802 100644
--- a/lib/gitlab/ci/config/node/undefined.rb
+++ b/lib/gitlab/ci/config/node/undefined.rb
@@ -3,15 +3,34 @@ module Gitlab
     class Config
       module Node
         ##
-        # This class represents an unspecified entry node.
+        # This class represents an undefined node.
         #
-        # It decorates original entry adding method that indicates it is
-        # unspecified.
+        # Implements the Null Object pattern.
         #
-        class Undefined < SimpleDelegator
+        class Undefined < Entry
+          def initialize(*)
+            super(nil)
+          end
+
+          def value
+            nil
+          end
+
+          def valid?
+            true
+          end
+
+          def errors
+            []
+          end
+
           def specified?
             false
           end
+
+          def relevant?
+            false
+          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/unspecified.rb b/lib/gitlab/ci/config/node/unspecified.rb
new file mode 100644
index 0000000..a7d1f61
--- /dev/null
+++ b/lib/gitlab/ci/config/node/unspecified.rb
@@ -0,0 +1,19 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # This class represents an unspecified entry node.
+        #
+        # It decorates original entry adding method that indicates it is
+        # unspecified.
+        #
+        class Unspecified < SimpleDelegator
+          def specified?
+            false
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb
new file mode 100644
index 0000000..a210e76
--- /dev/null
+++ b/lib/gitlab/ci/pipeline_duration.rb
@@ -0,0 +1,141 @@
+module Gitlab
+  module Ci
+    # # Introduction - total running time
+    #
+    # The problem this module is trying to solve is finding the total running
+    # time amongst all the jobs, excluding retries and pending (queue) time.
+    # We could reduce this problem down to finding the union of periods.
+    #
+    # So each job would be represented as a `Period`, which consists of
+    # `Period#first` as when the job started and `Period#last` as when the
+    # job was finished. A simple example here would be:
+    #
+    # * A (1, 3)
+    # * B (2, 4)
+    # * C (6, 7)
+    #
+    # Here A begins from 1, and ends to 3. B begins from 2, and ends to 4.
+    # C begins from 6, and ends to 7. Visually it could be viewed as:
+    #
+    #     0  1  2  3  4  5  6  7
+    #        AAAAAAA
+    #           BBBBBBB
+    #                       CCCC
+    #
+    # The union of A, B, and C would be (1, 4) and (6, 7), therefore the
+    # total running time should be:
+    #
+    #     (4 - 1) + (7 - 6) => 4
+    #
+    # # The Algorithm
+    #
+    # The algorithm used here for union would be described as follow.
+    # First we make sure that all periods are sorted by `Period#first`.
+    # Then we try to merge periods by iterating through the first period
+    # to the last period. The goal would be merging all overlapped periods
+    # so that in the end all the periods are discrete. When all periods
+    # are discrete, we're free to just sum all the periods to get real
+    # running time.
+    #
+    # Here we begin from A, and compare it to B. We could find that
+    # before A ends, B already started. That is `B.first <= A.last`
+    # that is `2 <= 3` which means A and B are overlapping!
+    #
+    # When we found that two periods are overlapping, we would need to merge
+    # them into a new period and disregard the old periods. To make a new
+    # period, we take `A.first` as the new first because remember? we sorted
+    # them, so `A.first` must be smaller or equal to `B.first`. And we take
+    # `[A.last, B.last].max` as the new last because we want whoever ended
+    # later. This could be broken into two cases:
+    #
+    #     0  1  2  3  4
+    #        AAAAAAA
+    #           BBBBBBB
+    #
+    # Or:
+    #
+    #     0  1  2  3  4
+    #        AAAAAAAAAA
+    #           BBBB
+    #
+    # So that we need to take whoever ends later. Back to our example,
+    # after merging and discard A and B it could be visually viewed as:
+    #
+    #     0  1  2  3  4  5  6  7
+    #        DDDDDDDDDD
+    #                       CCCC
+    #
+    # Now we could go on and compare the newly created D and the old C.
+    # We could figure out that D and C are not overlapping by checking
+    # `C.first <= D.last` is `false`. Therefore we need to keep both C
+    # and D. The example would end here because there are no more jobs.
+    #
+    # After having the union of all periods, we just need to sum the length
+    # of all periods to get total time.
+    #
+    #     (4 - 1) + (7 - 6) => 4
+    #
+    # That is 4 is the answer in the example.
+    module PipelineDuration
+      extend self
+
+      Period = Struct.new(:first, :last) do
+        def duration
+          last - first
+        end
+      end
+
+      def from_pipeline(pipeline)
+        status = %w[success failed running canceled]
+        builds = pipeline.builds.latest.
+          where(status: status).where.not(started_at: nil).order(:started_at)
+
+        from_builds(builds)
+      end
+
+      def from_builds(builds)
+        now = Time.now
+
+        periods = builds.map do |b|
+          Period.new(b.started_at, b.finished_at || now)
+        end
+
+        from_periods(periods)
+      end
+
+      # periods should be sorted by `first`
+      def from_periods(periods)
+        process_duration(process_periods(periods))
+      end
+
+      private
+
+      def process_periods(periods)
+        return periods if periods.empty?
+
+        periods.drop(1).inject([periods.first]) do |result, current|
+          previous = result.last
+
+          if overlap?(previous, current)
+            result[-1] = merge(previous, current)
+            result
+          else
+            result << current
+          end
+        end
+      end
+
+      def overlap?(previous, current)
+        current.first <= previous.last
+      end
+
+      def merge(previous, current)
+        Period.new(previous.first, [previous.last, current.last].max)
+      end
+
+      def process_duration(periods)
+        periods.sum(&:duration)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb
index 2d4d55d..98e842c 100644
--- a/lib/gitlab/conflict/parser.rb
+++ b/lib/gitlab/conflict/parser.rb
@@ -18,7 +18,7 @@ module Gitlab
 
       def parse(text, our_path:, their_path:, parent_file: nil)
         raise UnmergeableFile if text.blank? # Typically a binary file
-        raise UnmergeableFile if text.length > 102400
+        raise UnmergeableFile if text.length > 200.kilobytes
 
         begin
           text.to_json
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 9dc2602..b164f5a 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -1,16 +1,16 @@
 module Gitlab
   class ContributionsCalendar
-    attr_reader :timestamps, :projects, :user
+    attr_reader :activity_dates, :projects, :user
 
     def initialize(projects, user)
       @projects = projects
       @user = user
     end
 
-    def timestamps
-      return @timestamps if @timestamps.present?
+    def activity_dates
+      return @activity_dates if @activity_dates.present?
 
-      @timestamps = {}
+      @activity_dates = {}
       date_from = 1.year.ago
 
       events = Event.reorder(nil).contributions.where(author_id: user.id).
@@ -19,19 +19,17 @@ module Gitlab
         select('date(created_at) as date, count(id) as total_amount').
         map(&:attributes)
 
-      dates = (1.year.ago.to_date..Date.today).to_a
+      activity_dates = (1.year.ago.to_date..Date.today).to_a
 
-      dates.each do |date|
-        date_id = date.to_time.to_i.to_s
-        @timestamps[date_id] = 0
+      activity_dates.each do |date|
         day_events = events.find { |day_events| day_events["date"] == date }
 
         if day_events
-          @timestamps[date_id] = day_events["total_amount"]
+          @activity_dates[date] = day_events["total_amount"]
         end
       end
 
-      @timestamps
+      @activity_dates
     end
 
     def events_by_date(date)
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 27acd81..12fbb78 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -41,7 +41,7 @@ module Gitlab
         default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
         default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
         domain_whitelist: Settings.gitlab['domain_whitelist'],
-        import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project],
+        import_sources: %w[github bitbucket gitlab google_code fogbugz git gitlab_project],
         shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
         max_artifacts_size: Settings.artifacts['max_size'],
         require_two_factor_authentication: false,
diff --git a/lib/gitlab/database/date_time.rb b/lib/gitlab/database/date_time.rb
new file mode 100644
index 0000000..b6a89f7
--- /dev/null
+++ b/lib/gitlab/database/date_time.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  module Database
+    module DateTime
+      # Find the first of the `end_time_attrs` that isn't `NULL`. Subtract from it
+      # the first of the `start_time_attrs` that isn't NULL. `SELECT` the resulting interval
+      # along with an alias specified by the `as` parameter.
+      #
+      # Note: For MySQL, the interval is returned in seconds.
+      #       For PostgreSQL, the interval is returned as an INTERVAL type.
+      def subtract_datetimes(query_so_far, end_time_attrs, start_time_attrs, as)
+        diff_fn = if Gitlab::Database.postgresql?
+                    Arel::Nodes::Subtraction.new(
+                      Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs)),
+                      Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(start_time_attrs)))
+                  elsif Gitlab::Database.mysql?
+                    Arel::Nodes::NamedFunction.new(
+                      "TIMESTAMPDIFF",
+                      [Arel.sql('second'),
+                       Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(start_time_attrs)),
+                       Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs))])
+                  end
+
+        query_so_far.project(diff_fn.as(as))
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
new file mode 100644
index 0000000..1444d25
--- /dev/null
+++ b/lib/gitlab/database/median.rb
@@ -0,0 +1,112 @@
+# https://www.periscopedata.com/blog/medians-in-sql.html
+module Gitlab
+  module Database
+    module Median
+      def median_datetime(arel_table, query_so_far, column_sym)
+        median_queries =
+          if Gitlab::Database.postgresql?
+            pg_median_datetime_sql(arel_table, query_so_far, column_sym)
+          elsif Gitlab::Database.mysql?
+            mysql_median_datetime_sql(arel_table, query_so_far, column_sym)
+          end
+
+        results = Array.wrap(median_queries).map do |query|
+          ActiveRecord::Base.connection.execute(query)
+        end
+        extract_median(results).presence
+      end
+
+      def extract_median(results)
+        result = results.compact.first
+
+        if Gitlab::Database.postgresql?
+          result = result.first.presence
+          median = result['median'] if result
+          median.to_f if median
+        elsif Gitlab::Database.mysql?
+          result.to_a.flatten.first
+        end
+      end
+
+      def mysql_median_datetime_sql(arel_table, query_so_far, column_sym)
+        query = arel_table.
+                from(arel_table.project(Arel.sql('*')).order(arel_table[column_sym]).as(arel_table.table_name)).
+                project(average([arel_table[column_sym]], 'median')).
+                where(
+                  Arel::Nodes::Between.new(
+                    Arel.sql("(select @row_id := @row_id + 1)"),
+                    Arel::Nodes::And.new(
+                      [Arel.sql('@ct/2.0'),
+                       Arel.sql('@ct/2.0 + 1')]
+                    )
+                  )
+                ).
+                # Disallow negative values
+                where(arel_table[column_sym].gteq(0))
+
+        [
+          Arel.sql("CREATE TEMPORARY TABLE IF NOT EXISTS #{query_so_far.to_sql}"),
+          Arel.sql("set @ct := (select count(1) from #{arel_table.table_name});"),
+          Arel.sql("set @row_id := 0;"),
+          query.to_sql,
+          Arel.sql("DROP TEMPORARY TABLE IF EXISTS #{arel_table.table_name};")
+        ]
+      end
+
+      def pg_median_datetime_sql(arel_table, query_so_far, column_sym)
+        # Create a CTE with the column we're operating on, row number (after sorting by the column
+        # we're operating on), and count of the table we're operating on (duplicated across) all rows
+        # of the CTE. For example, if we're looking to find the median of the `projects.star_count`
+        # column, the CTE might look like this:
+        #
+        #  star_count | row_id | ct
+        # ------------+--------+----
+        #           5 |      1 |  3
+        #           9 |      2 |  3
+        #          15 |      3 |  3
+        cte_table = Arel::Table.new("ordered_records")
+        cte = Arel::Nodes::As.new(
+          cte_table,
+          arel_table.
+            project(
+              arel_table[column_sym].as(column_sym.to_s),
+              Arel::Nodes::Over.new(Arel::Nodes::NamedFunction.new("row_number", []),
+                                    Arel::Nodes::Window.new.order(arel_table[column_sym])).as('row_id'),
+              arel_table.project("COUNT(1)").as('ct')).
+            # Disallow negative values
+            where(arel_table[column_sym].gteq(zero_interval)))
+
+        # From the CTE, select either the middle row or the middle two rows (this is accomplished
+        # by 'where cte.row_id between cte.ct / 2.0 AND cte.ct / 2.0 + 1'). Find the average of the
+        # selected rows, and this is the median value.
+        cte_table.project(average([extract_epoch(cte_table[column_sym])], "median")).
+          where(
+            Arel::Nodes::Between.new(
+              cte_table[:row_id],
+              Arel::Nodes::And.new(
+                [(cte_table[:ct] / Arel.sql('2.0')),
+                 (cte_table[:ct] / Arel.sql('2.0') + 1)]
+              )
+            )
+          ).
+          with(query_so_far, cte).
+          to_sql
+      end
+
+      private
+
+      def average(args, as)
+        Arel::Nodes::NamedFunction.new("AVG", args, as)
+      end
+
+      def extract_epoch(arel_attribute)
+        Arel.sql(%Q{EXTRACT(EPOCH FROM "#{arel_attribute.relation.name}"."#{arel_attribute.name}")})
+      end
+
+      # Need to cast '0' to an INTERVAL before we can check if the interval is positive
+      def zero_interval
+        Arel::Nodes::NamedFunction.new("CAST", [Arel.sql("'0' AS INTERVAL")])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 927f9da..0bd6e14 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -129,12 +129,14 @@ module Gitlab
       # column - The name of the column to add.
       # type - The column type (e.g. `:integer`).
       # default - The default value for the column.
+      # limit - Sets a column limit. For example, for :integer, the default is
+      #         4-bytes. Set `limit: 8` to allow 8-byte integers.
       # allow_null - When set to `true` the column will allow NULL values, the
       #              default is to not allow NULL values.
       #
       # This method can also take a block which is passed directly to the
       # `update_column_in_batches` method.
-      def add_column_with_default(table, column, type, default:, allow_null: false, &block)
+      def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false, &block)
         if transaction_open?
           raise 'add_column_with_default can not be run inside a transaction, ' \
             'you can disable transactions by calling disable_ddl_transaction! ' \
@@ -144,7 +146,11 @@ module Gitlab
         disable_statement_timeout
 
         transaction do
-          add_column(table, column, type, default: nil)
+          if limit
+            add_column(table, column, type, default: nil, limit: limit)
+          else
+            add_column(table, column, type, default: nil)
+          end
 
           # Changing the default before the update ensures any newly inserted
           # rows already use the proper default value.
diff --git a/lib/gitlab/diff/file_collection/merge_request.rb b/lib/gitlab/diff/file_collection/merge_request.rb
deleted file mode 100644
index 4f94690..0000000
--- a/lib/gitlab/diff/file_collection/merge_request.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-module Gitlab
-  module Diff
-    module FileCollection
-      class MergeRequest < Base
-        def initialize(merge_request, diff_options:)
-          @merge_request = merge_request
-
-          super(merge_request,
-            project: merge_request.project,
-            diff_options: diff_options,
-            diff_refs: merge_request.diff_refs)
-        end
-
-        def diff_files
-          super.tap { |_| store_highlight_cache }
-        end
-
-        private
-
-        # Extracted method to highlight in the same iteration to the diff_collection.
-        def decorate_diff!(diff)
-          diff_file = super
-          cache_highlight!(diff_file) if cacheable?
-          diff_file
-        end
-
-        def highlight_diff_file_from_cache!(diff_file, cache_diff_lines)
-          diff_file.highlighted_diff_lines = cache_diff_lines.map do |line|
-            Gitlab::Diff::Line.init_from_hash(line)
-          end
-        end
-
-        #
-        # If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted)
-        # for the highlighted ones, so we just skip their execution.
-        # If the highlighted diff files lines are not cached we calculate and cache them.
-        #
-        # The content of the cache is a Hash where the key correspond to the file_path and the values are Arrays of
-        # hashes that represent serialized diff lines.
-        #
-        def cache_highlight!(diff_file)
-          file_path = diff_file.file_path
-
-          if highlight_cache[file_path]
-            highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path])
-          else
-            highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash)
-          end
-        end
-
-        def highlight_cache
-          return @highlight_cache if defined?(@highlight_cache)
-
-          @highlight_cache = Rails.cache.read(cache_key) || {}
-          @highlight_cache_was_empty = @highlight_cache.empty?
-          @highlight_cache
-        end
-
-        def store_highlight_cache
-          Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty
-        end
-
-        def cacheable?
-          @merge_request.merge_request_diff.present?
-        end
-
-        def cache_key
-          [@merge_request.merge_request_diff, 'highlighted-diff-files', diff_options]
-        end
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb
new file mode 100644
index 0000000..36348b3
--- /dev/null
+++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb
@@ -0,0 +1,73 @@
+module Gitlab
+  module Diff
+    module FileCollection
+      class MergeRequestDiff < Base
+        def initialize(merge_request_diff, diff_options:)
+          @merge_request_diff = merge_request_diff
+
+          super(merge_request_diff,
+            project: merge_request_diff.project,
+            diff_options: diff_options,
+            diff_refs: merge_request_diff.diff_refs)
+        end
+
+        def diff_files
+          super.tap { |_| store_highlight_cache }
+        end
+
+        private
+
+        # Extracted method to highlight in the same iteration to the diff_collection.
+        def decorate_diff!(diff)
+          diff_file = super
+          cache_highlight!(diff_file) if cacheable?
+          diff_file
+        end
+
+        def highlight_diff_file_from_cache!(diff_file, cache_diff_lines)
+          diff_file.highlighted_diff_lines = cache_diff_lines.map do |line|
+            Gitlab::Diff::Line.init_from_hash(line)
+          end
+        end
+
+        #
+        # If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted)
+        # for the highlighted ones, so we just skip their execution.
+        # If the highlighted diff files lines are not cached we calculate and cache them.
+        #
+        # The content of the cache is a Hash where the key correspond to the file_path and the values are Arrays of
+        # hashes that represent serialized diff lines.
+        #
+        def cache_highlight!(diff_file)
+          file_path = diff_file.file_path
+
+          if highlight_cache[file_path]
+            highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path])
+          else
+            highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash)
+          end
+        end
+
+        def highlight_cache
+          return @highlight_cache if defined?(@highlight_cache)
+
+          @highlight_cache = Rails.cache.read(cache_key) || {}
+          @highlight_cache_was_empty = @highlight_cache.empty?
+          @highlight_cache
+        end
+
+        def store_highlight_cache
+          Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty
+        end
+
+        def cacheable?
+          @merge_request_diff.present?
+        end
+
+        def cache_key
+          [@merge_request_diff, 'highlighted-diff-files', diff_options]
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 7584efe..3cd515e 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -18,6 +18,16 @@ module Gitlab
         end
       end
 
+      def committer_hash(email:, name:)
+        return if email.nil? || name.nil?
+
+        {
+          email: email,
+          name: name,
+          time: Time.now
+        }
+      end
+
       def tag_name(ref)
         ref = ref.to_s
         if self.tag_ref?(ref)
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index 9b681e6..bd90d24 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -17,11 +17,13 @@ module Gitlab
       def trigger(gl_id, oldrev, newrev, ref)
         return [true, nil] unless exists?
 
-        case name
-        when "pre-receive", "post-receive"
-          call_receive_hook(gl_id, oldrev, newrev, ref)
-        when "update"
-          call_update_hook(gl_id, oldrev, newrev, ref)
+        Bundler.with_clean_env do
+          case name
+          when "pre-receive", "post-receive"
+            call_receive_hook(gl_id, oldrev, newrev, ref)
+          when "update"
+            call_update_hook(gl_id, oldrev, newrev, ref)
+          end
         end
       end
 
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 1882eb8..799794c 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -5,12 +5,13 @@ module Gitlab
     DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
     PUSH_COMMANDS = %w{ git-receive-pack }
 
-    attr_reader :actor, :project, :protocol, :user_access
+    attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities
 
-    def initialize(actor, project, protocol)
+    def initialize(actor, project, protocol, authentication_abilities:)
       @actor    = actor
       @project  = project
       @protocol = protocol
+      @authentication_abilities = authentication_abilities
       @user_access = UserAccess.new(user, project: project)
     end
 
@@ -60,14 +61,26 @@ module Gitlab
     end
 
     def user_download_access_check
-      unless user_access.can_do_action?(:download_code)
+      unless user_can_download_code? || build_can_download_code?
         return build_status_object(false, "You are not allowed to download code from this project.")
       end
 
       build_status_object(true)
     end
 
+    def user_can_download_code?
+      authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
+    end
+
+    def build_can_download_code?
+      authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
+    end
+
     def user_push_access_check(changes)
+      unless authentication_abilities.include?(:push_code)
+        return build_status_object(false, "You are not allowed to upload code for this project.")
+      end
+
       if changes.blank?
         return build_status_object(true)
       end
diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb
index 72992ba..8cacf4f 100644
--- a/lib/gitlab/github_import/base_formatter.rb
+++ b/lib/gitlab/github_import/base_formatter.rb
@@ -15,11 +15,16 @@ module Gitlab
 
       private
 
-      def gl_user_id(github_id)
+      def gitlab_user_id(github_id)
         User.joins(:identities).
           find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s).
           try(:id)
       end
+
+      def gitlab_author_id
+        return @gitlab_author_id if defined?(@gitlab_author_id)
+        @gitlab_author_id = gitlab_user_id(raw_data.user.id)
+      end
     end
   end
 end
diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb
index 2c1b94e..2bddcde 100644
--- a/lib/gitlab/github_import/comment_formatter.rb
+++ b/lib/gitlab/github_import/comment_formatter.rb
@@ -21,7 +21,7 @@ module Gitlab
       end
 
       def author_id
-        gl_user_id(raw_data.user.id) || project.creator_id
+        gitlab_author_id || project.creator_id
       end
 
       def body
@@ -52,7 +52,11 @@ module Gitlab
       end
 
       def note
-        formatter.author_line(author) + body
+        if gitlab_author_id
+          body
+        else
+          formatter.author_line(author) + body
+        end
       end
 
       def type
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 02ffb43..d35ee2a 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -24,6 +24,7 @@ module Gitlab
         import_issues
         import_pull_requests
         import_wiki
+        import_releases
         handle_errors
 
         true
@@ -133,8 +134,7 @@ module Gitlab
 
         if issue.labels.count > 0
           label_ids = issue.labels
-            .map { |raw| LabelFormatter.new(project, raw).attributes }
-            .map { |attrs| Label.find_by(attrs).try(:id) }
+            .map { |attrs| project.labels.find_by(title: attrs.name).try(:id) }
             .compact
 
           issuable.update_attribute(:label_ids, label_ids)
@@ -152,12 +152,14 @@ module Gitlab
       end
 
       def create_comments(issuable, comments)
-        comments.each do |raw|
-          begin
-            comment = CommentFormatter.new(project, raw)
-            issuable.notes.create!(comment.attributes)
-          rescue => e
-            errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+        ActiveRecord::Base.no_touching do
+          comments.each do |raw|
+            begin
+              comment = CommentFormatter.new(project, raw)
+              issuable.notes.create!(comment.attributes)
+            rescue => e
+              errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+            end
           end
         end
       end
@@ -166,7 +168,7 @@ module Gitlab
         unless project.wiki_enabled?
           wiki = WikiFormatter.new(project)
           gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url)
-          project.update_attribute(:wiki_enabled, true)
+          project.project.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
         end
       rescue Gitlab::Shell::Error => e
         # GitHub error message when the wiki repo has not been created,
@@ -176,6 +178,18 @@ module Gitlab
           errors << { type: :wiki, errors: e.message }
         end
       end
+
+      def import_releases
+        releases = client.releases(repo, per_page: 100)
+        releases.each do |raw|
+          begin
+            gh_release = ReleaseFormatter.new(project, raw)
+            gh_release.create! if gh_release.valid?
+          rescue => e
+            errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb
index 835ec85..77621de 100644
--- a/lib/gitlab/github_import/issue_formatter.rb
+++ b/lib/gitlab/github_import/issue_formatter.rb
@@ -12,7 +12,7 @@ module Gitlab
           author_id: author_id,
           assignee_id: assignee_id,
           created_at: raw_data.created_at,
-          updated_at: updated_at
+          updated_at: raw_data.updated_at
         }
       end
 
@@ -40,7 +40,7 @@ module Gitlab
 
       def assignee_id
         if assigned?
-          gl_user_id(raw_data.assignee.id)
+          gitlab_user_id(raw_data.assignee.id)
         end
       end
 
@@ -49,7 +49,7 @@ module Gitlab
       end
 
       def author_id
-        gl_user_id(raw_data.user.id) || project.creator_id
+        gitlab_author_id || project.creator_id
       end
 
       def body
@@ -57,7 +57,11 @@ module Gitlab
       end
 
       def description
-        @formatter.author_line(author) + body
+        if gitlab_author_id
+          body
+        else
+          formatter.author_line(author) + body
+        end
       end
 
       def milestone
@@ -69,10 +73,6 @@ module Gitlab
       def state
         raw_data.state == 'closed' ? 'closed' : 'opened'
       end
-
-      def updated_at
-        state == 'closed' ? raw_data.closed_at : raw_data.updated_at
-      end
     end
   end
 end
diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb
index 9f18244..2cad7fc 100644
--- a/lib/gitlab/github_import/label_formatter.rb
+++ b/lib/gitlab/github_import/label_formatter.rb
@@ -13,6 +13,12 @@ module Gitlab
         Label
       end
 
+      def create!
+        project.labels.find_or_create_by!(title: title) do |label|
+          label.color = color
+        end
+      end
+
       private
 
       def color
diff --git a/lib/gitlab/github_import/milestone_formatter.rb b/lib/gitlab/github_import/milestone_formatter.rb
index 53d4b31..b2fa524 100644
--- a/lib/gitlab/github_import/milestone_formatter.rb
+++ b/lib/gitlab/github_import/milestone_formatter.rb
@@ -3,14 +3,14 @@ module Gitlab
     class MilestoneFormatter < BaseFormatter
       def attributes
         {
-          iid: number,
+          iid: raw_data.number,
           project: project,
-          title: title,
-          description: description,
-          due_date: due_date,
+          title: raw_data.title,
+          description: raw_data.description,
+          due_date: raw_data.due_on,
           state: state,
-          created_at: created_at,
-          updated_at: updated_at
+          created_at: raw_data.created_at,
+          updated_at: raw_data.updated_at
         }
       end
 
@@ -20,33 +20,9 @@ module Gitlab
 
       private
 
-      def number
-        raw_data.number
-      end
-
-      def title
-        raw_data.title
-      end
-
-      def description
-        raw_data.description
-      end
-
-      def due_date
-        raw_data.due_on
-      end
-
       def state
         raw_data.state == 'closed' ? 'closed' : 'active'
       end
-
-      def created_at
-        raw_data.created_at
-      end
-
-      def updated_at
-        state == 'closed' ? raw_data.closed_at : raw_data.updated_at
-      end
     end
   end
 end
diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb
index f422100..605abfa 100644
--- a/lib/gitlab/github_import/project_creator.rb
+++ b/lib/gitlab/github_import/project_creator.rb
@@ -3,26 +3,33 @@ module Gitlab
     class ProjectCreator
       attr_reader :repo, :namespace, :current_user, :session_data
 
-      def initialize(repo, namespace, current_user, session_data)
+      def initialize(repo, name, namespace, current_user, session_data)
         @repo = repo
+        @name = name
         @namespace = namespace
         @current_user = current_user
         @session_data = session_data
       end
 
       def execute
-        ::Projects::CreateService.new(
+        project = ::Projects::CreateService.new(
           current_user,
-          name: repo.name,
-          path: repo.name,
+          name: @name,
+          path: @name,
           description: repo.description,
           namespace_id: namespace.id,
-          visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC,
+          visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility,
           import_type: "github",
           import_source: repo.full_name,
-          import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"),
-          wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later
+          import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@")
         ).execute
+
+        # If repo has wiki we'll import it later
+        if repo.has_wiki? && project
+          project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
+        end
+
+        project
       end
     end
   end
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index 04aa366..1408683 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -20,7 +20,7 @@ module Gitlab
           author_id: author_id,
           assignee_id: assignee_id,
           created_at: raw_data.created_at,
-          updated_at: updated_at
+          updated_at: raw_data.updated_at
         }
       end
 
@@ -68,7 +68,7 @@ module Gitlab
 
       def assignee_id
         if assigned?
-          gl_user_id(raw_data.assignee.id)
+          gitlab_user_id(raw_data.assignee.id)
         end
       end
 
@@ -77,7 +77,7 @@ module Gitlab
       end
 
       def author_id
-        gl_user_id(raw_data.user.id) || project.creator_id
+        gitlab_author_id || project.creator_id
       end
 
       def body
@@ -85,7 +85,11 @@ module Gitlab
       end
 
       def description
-        formatter.author_line(author) + body
+        if gitlab_author_id
+          body
+        else
+          formatter.author_line(author) + body
+        end
       end
 
       def milestone
@@ -103,15 +107,6 @@ module Gitlab
                      'opened'
                    end
       end
-
-      def updated_at
-        case state
-        when 'merged' then raw_data.merged_at
-        when 'closed' then raw_data.closed_at
-        else
-          raw_data.updated_at
-        end
-      end
     end
   end
 end
diff --git a/lib/gitlab/github_import/release_formatter.rb b/lib/gitlab/github_import/release_formatter.rb
new file mode 100644
index 0000000..73d643b
--- /dev/null
+++ b/lib/gitlab/github_import/release_formatter.rb
@@ -0,0 +1,23 @@
+module Gitlab
+  module GithubImport
+    class ReleaseFormatter < BaseFormatter
+      def attributes
+        {
+          project: project,
+          tag: raw_data.tag_name,
+          description: raw_data.body,
+          created_at: raw_data.created_at,
+          updated_at: raw_data.created_at
+        }
+      end
+
+      def klass
+        Release
+      end
+
+      def valid?
+        !raw_data.draft
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
index 46d40f7..e44d793 100644
--- a/lib/gitlab/gitlab_import/importer.rb
+++ b/lib/gitlab/gitlab_import/importer.rb
@@ -41,7 +41,8 @@ module Gitlab
               title: issue["title"],
               state: issue["state"],
               updated_at: issue["updated_at"],
-              author_id: gl_user_id(project, issue["author"]["id"])
+              author_id: gitlab_user_id(project, issue["author"]["id"]),
+              confidential: issue["confidential"]
             )
           end
         end
@@ -51,7 +52,7 @@ module Gitlab
 
       private
 
-      def gl_user_id(project, gitlab_id)
+      def gitlab_user_id(project, gitlab_id)
         user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'gitlab'", gitlab_id.to_s)
         (user && user.id) || project.creator_id
       end
diff --git a/lib/gitlab/gitorious_import.rb b/lib/gitlab/gitorious_import.rb
deleted file mode 100644
index 8d0132a..0000000
--- a/lib/gitlab/gitorious_import.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Gitlab
-  module GitoriousImport
-    GITORIOUS_HOST = "https://gitorious.org"
-  end
-end
diff --git a/lib/gitlab/gitorious_import/client.rb b/lib/gitlab/gitorious_import/client.rb
deleted file mode 100644
index 99fe5bd..0000000
--- a/lib/gitlab/gitorious_import/client.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module Gitlab
-  module GitoriousImport
-    class Client
-      attr_reader :repo_list
-
-      def initialize(repo_list)
-        @repo_list = repo_list
-      end
-
-      def authorize_url(redirect_uri)
-        "#{GITORIOUS_HOST}/gitlab-import?callback_url=#{redirect_uri}"
-      end
-
-      def repos
-        @repos ||= repo_names.map { |full_name| GitoriousImport::Repository.new(full_name) }
-      end
-
-      def repo(id)
-        repos.find { |repo| repo.id == id }
-      end
-
-      private
-
-      def repo_names
-        repo_list.to_s.split(',').map(&:strip).reject(&:blank?)
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/gitorious_import/project_creator.rb b/lib/gitlab/gitorious_import/project_creator.rb
deleted file mode 100644
index 8e22aa9..0000000
--- a/lib/gitlab/gitorious_import/project_creator.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module Gitlab
-  module GitoriousImport
-    class ProjectCreator
-      attr_reader :repo, :namespace, :current_user
-
-      def initialize(repo, namespace, current_user)
-        @repo = repo
-        @namespace = namespace
-        @current_user = current_user
-      end
-
-      def execute
-        ::Projects::CreateService.new(
-          current_user,
-          name: repo.name,
-          path: repo.path,
-          description: repo.description,
-          namespace_id: namespace.id,
-          visibility_level: Gitlab::VisibilityLevel::PUBLIC,
-          import_type: "gitorious",
-          import_source: repo.full_name,
-          import_url: repo.import_url
-        ).execute
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/gitorious_import/repository.rb b/lib/gitlab/gitorious_import/repository.rb
deleted file mode 100644
index c88f1ae..0000000
--- a/lib/gitlab/gitorious_import/repository.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module Gitlab
-  module GitoriousImport
-    Repository = Struct.new(:full_name) do
-      def id
-        Digest::SHA1.hexdigest(full_name)
-      end
-
-      def namespace
-        segments.first
-      end
-
-      def path
-        segments.last
-      end
-
-      def name
-        path.titleize
-      end
-
-      def description
-        ""
-      end
-
-      def import_url
-        "#{GITORIOUS_HOST}/#{full_name}.git"
-      end
-
-      private
-
-      def segments
-        full_name.split('/')
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index c5a1114..2c21804 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -11,7 +11,6 @@ module Gitlab
 
       if current_user
         gon.current_user_id = current_user.id
-        gon.api_token = current_user.private_token
       end
     end
   end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index bb562bd..181e288 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -2,7 +2,8 @@ module Gitlab
   module ImportExport
     extend self
 
-    VERSION = '0.1.3'
+    # For every version update, the version history in import_export.md has to be kept up to date.
+    VERSION = '0.1.4'
     FILENAME_LIMIT = 50
 
     def export_path(relative_path:)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 1da5104..88803d7 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -10,6 +10,7 @@ project_tree:
     - milestone:
       - :events
   - snippets:
+    - :award_emoji
     - notes:
         :author
   - :releases
@@ -35,19 +36,18 @@ project_tree:
   - :deploy_keys
   - :services
   - :hooks
-  - :protected_branches
+  - protected_branches:
+    - :merge_access_levels
+    - :push_access_levels
   - :labels
   - milestones:
     - :events
+  - :project_feature
 
 # Only include the following attributes for the models specified.
 included_attributes:
   project:
     - :description
-    - :issues_enabled
-    - :merge_requests_enabled
-    - :wiki_enabled
-    - :snippets_enabled
     - :visibility_level
     - :archived
   user:
@@ -67,9 +67,11 @@ excluded_attributes:
     - :milestone_id
   merge_requests:
     - :milestone_id
+  award_emoji:
+    - :awardable_id
 
 methods:
   statuses:
     - :type
   merge_request_diff:
-    - :utf8_st_diffs
\ No newline at end of file
+    - :utf8_st_diffs
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index b072626..354ccd6 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -7,7 +7,9 @@ module Gitlab
                     variables: 'Ci::Variable',
                     triggers: 'Ci::Trigger',
                     builds: 'Ci::Build',
-                    hooks: 'ProjectHook' }.freeze
+                    hooks: 'ProjectHook',
+                    merge_access_levels: 'ProtectedBranch::MergeAccessLevel',
+                    push_access_levels: 'ProtectedBranch::PushAccessLevel' }.freeze
 
       USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze
 
@@ -17,6 +19,8 @@ module Gitlab
 
       EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze
 
+      FINDER_ATTRIBUTES = %w[title project_id].freeze
+
       def self.create(*args)
         new(*args).create
       end
@@ -149,7 +153,7 @@ module Gitlab
       end
 
       def parsed_relation_hash
-        @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
+        @parsed_relation_hash ||= @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
       end
 
       def set_st_diffs
@@ -161,14 +165,30 @@ module Gitlab
         # Otherwise always create the record, skipping the extra SELECT clause.
         @existing_or_new_object ||= begin
           if EXISTING_OBJECT_CHECK.include?(@relation_name)
-            existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id'))
-            existing_object.assign_attributes(parsed_relation_hash)
+            events = parsed_relation_hash.delete('events')
+
+            unless events.blank?
+              existing_object.assign_attributes(events: events)
+            end
+
             existing_object
           else
             relation_class.new(parsed_relation_hash)
           end
         end
       end
+
+      def existing_object
+        @existing_object ||=
+          begin
+            finder_hash = parsed_relation_hash.slice(*FINDER_ATTRIBUTES)
+            existing_object = relation_class.find_or_create_by(finder_hash)
+            # Done in two steps, as MySQL behaves differently than PostgreSQL using
+            # the +find_or_create_by+ method and does not return the ID the second time.
+            existing_object.update(parsed_relation_hash)
+            existing_object
+          end
+      end
     end
   end
 end
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index 6d9379a..d1e33ea 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -22,10 +22,6 @@ module Gitlab
 
       private
 
-      def repos_path
-        Gitlab.config.gitlab_shell.repos_path
-      end
-
       def path_to_repo
         @project.repository.path_to_repo
       end
diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb
index de3fe6d..fc08082 100644
--- a/lib/gitlab/import_export/version_checker.rb
+++ b/lib/gitlab/import_export/version_checker.rb
@@ -24,8 +24,8 @@ module Gitlab
       end
 
       def verify_version!(version)
-        if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version)
-          raise Gitlab::ImportExport::Error.new("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
+        if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
+          raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
         else
           true
         end
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 59a0541..94261b7 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -14,13 +14,12 @@ module Gitlab
 
       def options
         {
-          'GitHub'          => 'github',
-          'Bitbucket'       => 'bitbucket',
-          'GitLab.com'      => 'gitlab',
-          'Gitorious.org'   => 'gitorious',
-          'Google Code'     => 'google_code',
-          'FogBugz'         => 'fogbugz',
-          'Repo by URL'     => 'git',
+          'GitHub'        => 'github',
+          'Bitbucket'     => 'bitbucket',
+          'GitLab.com'    => 'gitlab',
+          'Google Code'   => 'google_code',
+          'FogBugz'       => 'fogbugz',
+          'Repo by URL'   => 'git',
           'GitLab export' => 'gitlab_project'
         }
       end
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index 9a5bcfb..9100719 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -23,31 +23,7 @@ module Gitlab
       end
 
       def users(field, value, limit = nil)
-        if field.to_sym == :dn
-          options = {
-            base: value,
-            scope: Net::LDAP::SearchScope_BaseObject
-          }
-        else
-          options = {
-            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)
-
-          options[:filter] = if options[:filter]
-                               Net::LDAP::Filter.join(options[:filter], user_filter)
-                             else
-                               user_filter
-                             end
-        end
-
-        if limit.present?
-          options.merge!(size: limit)
-        end
+        options = user_options(field, value, limit)
 
         entries = ldap_search(options).select do |entry|
           entry.respond_to? config.uid
@@ -90,6 +66,38 @@ module Gitlab
         Rails.logger.warn("LDAP search timed out after #{config.timeout} seconds")
         []
       end
+
+      private
+
+      def user_options(field, value, limit)
+        options = { attributes: %W(#{config.uid} cn mail dn) }
+        options[:size] = limit if limit
+
+        if field.to_sym == :dn
+          options[:base] = value
+          options[:scope] = Net::LDAP::SearchScope_BaseObject
+          options[:filter] = user_filter
+        else
+          options[:base] = config.base
+          options[:filter] = user_filter(Net::LDAP::Filter.eq(field, value))
+        end
+
+        options
+      end
+
+      def user_filter(filter = nil)
+        if config.user_filter.present?
+          user_filter = Net::LDAP::Filter.construct(config.user_filter)
+        end
+
+        if user_filter && filter
+          Net::LDAP::Filter.join(filter, user_filter)
+        elsif user_filter
+          user_filter
+        else
+          filter
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb
new file mode 100644
index 0000000..d089a2f
--- /dev/null
+++ b/lib/gitlab/lfs_token.rb
@@ -0,0 +1,54 @@
+module Gitlab
+  class LfsToken
+    attr_accessor :actor
+
+    TOKEN_LENGTH = 50
+    EXPIRY_TIME = 1800
+
+    def initialize(actor)
+      @actor =
+        case actor
+        when DeployKey, User
+          actor
+        when Key
+          actor.user
+        else
+          raise 'Bad Actor'
+        end
+    end
+
+    def generate
+      token = Devise.friendly_token(TOKEN_LENGTH)
+
+      Gitlab::Redis.with do |redis|
+        redis.set(redis_key, token, ex: EXPIRY_TIME)
+      end
+
+      token
+    end
+
+    def value
+      Gitlab::Redis.with do |redis|
+        redis.get(redis_key)
+      end
+    end
+
+    def user?
+      actor.is_a?(User)
+    end
+
+    def type
+      actor.is_a?(User) ? :lfs_token : :lfs_deploy_token
+    end
+
+    def actor_name
+      actor.is_a?(User) ? actor.username : "lfs+deploy-key-#{actor.id}"
+    end
+
+    private
+
+    def redis_key
+      "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb
index b4493bf..01c96a6 100644
--- a/lib/gitlab/metrics/rack_middleware.rb
+++ b/lib/gitlab/metrics/rack_middleware.rb
@@ -4,6 +4,17 @@ module Gitlab
     class RackMiddleware
       CONTROLLER_KEY = 'action_controller.instance'
       ENDPOINT_KEY   = 'api.endpoint'
+      CONTENT_TYPES = {
+        'text/html' => :html,
+        'text/plain' => :txt,
+        'application/json' => :json,
+        'text/js' => :js,
+        'application/atom+xml' => :atom,
+        'image/png' => :png,
+        'image/jpeg' => :jpeg,
+        'image/gif' => :gif,
+        'image/svg+xml' => :svg
+      }
 
       def initialize(app)
         @app = app
@@ -46,8 +57,15 @@ module Gitlab
       end
 
       def tag_controller(trans, env)
-        controller   = env[CONTROLLER_KEY]
-        trans.action = "#{controller.class.name}##{controller.action_name}"
+        controller = env[CONTROLLER_KEY]
+        action = "#{controller.class.name}##{controller.action_name}"
+        suffix = CONTENT_TYPES[controller.content_type]
+
+        if suffix && suffix != :html
+          action += ".#{suffix}"
+        end
+
+        trans.action = action
       end
 
       def tag_endpoint(trans, env)
diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb
index ca23cce..cc74bb2 100644
--- a/lib/gitlab/popen.rb
+++ b/lib/gitlab/popen.rb
@@ -18,18 +18,18 @@ module Gitlab
         FileUtils.mkdir_p(path)
       end
 
-      @cmd_output = ""
-      @cmd_status = 0
+      cmd_output = ""
+      cmd_status = 0
       Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
-        # We are not using stdin so we should close it, in case the command we
-        # are running waits for input.
+        yield(stdin) if block_given?
         stdin.close
-        @cmd_output << stdout.read
-        @cmd_output << stderr.read
-        @cmd_status = wait_thr.value.exitstatus
+
+        cmd_output << stdout.read
+        cmd_output << stderr.read
+        cmd_status = wait_thr.value.exitstatus
       end
 
-      [@cmd_output, @cmd_status]
+      [cmd_output, cmd_status]
     end
   end
 end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 183bd10..5b9cfae 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -28,11 +28,6 @@ module Gitlab
       end
     end
 
-    def total_count
-      @total_count ||= issues_count + merge_requests_count + blobs_count +
-                       notes_count + wiki_blobs_count + commits_count
-    end
-
     def blobs_count
       @blobs_count ||= blobs.count
     end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index ffad5e1..bc8bbf3 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -96,11 +96,11 @@ module Gitlab
     end
 
     def environment_name_regex
-      @environment_name_regex ||= /\A[a-zA-Z0-9_-]+\z/.freeze
+      @environment_name_regex ||= /\A[a-zA-Z0-9_\\\/\${}. -]+\z/.freeze
     end
 
     def environment_name_regex_message
-      "can contain only letters, digits, '-' and '_'."
+      "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.' and spaces"
     end
   end
 end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index f8ab2b1..2690938 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -27,11 +27,6 @@ module Gitlab
       end
     end
 
-    def total_count
-      @total_count ||= projects_count + issues_count + merge_requests_count +
-        milestones_count
-    end
-
     def projects_count
       @projects_count ||= projects.count
     end
@@ -48,10 +43,6 @@ module Gitlab
       @milestones_count ||= milestones.count
     end
 
-    def empty?
-      total_count.zero?
-    end
-
     private
 
     def projects
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
new file mode 100644
index 0000000..117fc50
--- /dev/null
+++ b/lib/gitlab/sentry.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  module Sentry
+    def self.enabled?
+      Rails.env.production? && current_application_settings.sentry_enabled?
+    end
+
+    def self.context(current_user = nil)
+      return unless self.enabled?
+
+      if current_user
+        Raven.user_context(
+          id: current_user.id,
+          email: current_user.email,
+          username: current_user.username,
+        )
+      end
+    end
+
+    def self.program_context
+      if Sidekiq.server?
+        'sidekiq'
+      else
+        'rails'
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index e0e74ff..9e01f02 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -20,10 +20,6 @@ module Gitlab
       end
     end
 
-    def total_count
-      @total_count ||= snippet_titles_count + snippet_blobs_count
-    end
-
     def snippet_titles_count
       @snippet_titles_count ||= snippet_titles.count
     end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index fe65c24..99d0c28 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -22,6 +22,8 @@ module Gitlab
         note_url
       when WikiPage
         wiki_page_url
+      when ProjectSnippet
+        project_snippet_url(object)
       else
         raise NotImplementedError.new("No URL builder defined for #{object.class}")
       end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index c6826a0..60aae54 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -1,19 +1,38 @@
 require 'base64'
 require 'json'
+require 'securerandom'
 
 module Gitlab
   class Workhorse
     SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'
     VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'
+    INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'
+    INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'
+
+    # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
+    # bytes https://tools.ietf.org/html/rfc4868#section-2.6
+    SECRET_LENGTH = 32
 
     class << self
       def git_http_ok(repository, user)
         {
-          'GL_ID' => Gitlab::GlId.gl_id(user),
-          'RepoPath' => repository.path_to_repo,
+          GL_ID: Gitlab::GlId.gl_id(user),
+          RepoPath: repository.path_to_repo,
         }
       end
 
+      def lfs_upload_ok(oid, size)
+        {
+          StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload",
+          LfsOid: oid,
+          LfsSize: size,
+        }
+      end
+
+      def artifact_upload_ok
+        { TempPath: ArtifactUploader.artifacts_upload_path }
+      end
+
       def send_git_blob(repository, blob)
         params = {
           'RepoPath' => repository.path_to_repo,
@@ -81,6 +100,35 @@ module Gitlab
         path.readable? ? path.read.chomp : 'unknown'
       end
 
+      def secret
+        @secret ||= begin
+          bytes = Base64.strict_decode64(File.read(secret_path).chomp)
+          raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH
+          bytes
+        end
+      end
+      
+      def write_secret
+        bytes = SecureRandom.random_bytes(SECRET_LENGTH)
+        File.open(secret_path, 'w:BINARY', 0600) do |f| 
+          f.chmod(0600)
+          f.write(Base64.strict_encode64(bytes))
+        end
+      end
+      
+      def verify_api_request!(request_headers)
+        JWT.decode(
+          request_headers[INTERNAL_API_REQUEST_HEADER],
+          secret,
+          true,
+          { iss: 'gitlab-workhorse', verify_iss: true, algorithm: 'HS256' },
+        )
+      end
+
+      def secret_path
+        Rails.root.join('.gitlab_workhorse_secret')
+      end
+      
       protected
 
       def encode(hash)
diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake
new file mode 100644
index 0000000..609dfaa
--- /dev/null
+++ b/lib/tasks/haml-lint.rake
@@ -0,0 +1,5 @@
+unless Rails.env.production?
+  require 'haml_lint/rake_task'
+
+  HamlLint::RakeTask.new
+end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 44128a4..a121cb2 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -237,6 +237,56 @@ describe AutocompleteController do
       end
     end
 
+    context 'authorized projects apply limit' do
+      before do
+        authorized_project2 = create(:project)
+        authorized_project3 = create(:project)
+
+        authorized_project.team << [user, :master]
+        authorized_project2.team << [user, :master]
+        authorized_project3.team << [user, :master]
+
+        stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
+      end
+
+      describe 'GET #projects with project ID' do
+        before do
+          get(:projects, project_id: project.id)
+        end
+
+        let(:body) { JSON.parse(response.body) }
+
+        it do
+          expect(body).to be_kind_of(Array)
+          expect(body.size).to eq 3 # Of a total of 4
+        end
+      end
+    end
+
+    context 'authorized projects with offset' do
+      before do
+        authorized_project2 = create(:project)
+        authorized_project3 = create(:project)
+
+        authorized_project.team << [user, :master]
+        authorized_project2.team << [user, :master]
+        authorized_project3.team << [user, :master]
+      end
+
+      describe 'GET #projects with project ID and offset_id' do
+        before do
+          get(:projects, project_id: project.id, offset_id: authorized_project.id)
+        end
+
+        let(:body) { JSON.parse(response.body) }
+
+        it do
+          expect(body.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
+          expect(body.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
+        end
+      end
+    end
+
     context 'authorized projects without admin_issue ability' do
       before(:each) do
         authorized_project.team << [user, :guest]
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 07bf8d2..1d3c9fb 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -146,21 +146,42 @@ describe Import::BitbucketController do
       end
 
       context "when a namespace with the Bitbucket user's username doesn't exist" do
-        it "creates the namespace" do
-          expect(Gitlab::BitbucketImport::ProjectCreator).
-            to receive(:new).and_return(double(execute: true))
+        context "when current user can create namespaces" do
+          it "creates the namespace" do
+            expect(Gitlab::BitbucketImport::ProjectCreator).
+              to receive(:new).and_return(double(execute: true))
 
-          post :create, format: :js
+            expect { post :create, format: :js }.to change(Namespace, :count).by(1)
+          end
+
+          it "takes the new namespace" do
+            expect(Gitlab::BitbucketImport::ProjectCreator).
+              to receive(:new).with(bitbucket_repo, an_instance_of(Group), user, access_params).
+              and_return(double(execute: true))
 
-          expect(Namespace.where(name: other_username).first).not_to be_nil
+            post :create, format: :js
+          end
         end
 
-        it "takes the new namespace" do
-          expect(Gitlab::BitbucketImport::ProjectCreator).
-            to receive(:new).with(bitbucket_repo, an_instance_of(Group), user, access_params).
-            and_return(double(execute: true))
+        context "when current user can't create namespaces" do
+          before do
+            user.update_attribute(:can_create_group, false)
+          end
 
-          post :create, format: :js
+          it "doesn't create the namespace" do
+            expect(Gitlab::BitbucketImport::ProjectCreator).
+              to receive(:new).and_return(double(execute: true))
+
+            expect { post :create, format: :js }.not_to change(Namespace, :count)
+          end
+
+          it "takes the current user's namespace" do
+            expect(Gitlab::BitbucketImport::ProjectCreator).
+              to receive(:new).with(bitbucket_repo, user.namespace, user, access_params).
+              and_return(double(execute: true))
+
+            post :create, format: :js
+          end
         end
       end
     end
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 51d5952..4f96567 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -124,8 +124,8 @@ describe Import::GithubController do
       context "when the GitHub user and GitLab user's usernames match" do
         it "takes the current user's namespace" do
           expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).with(github_repo, user.namespace, user, access_params).
-            and_return(double(execute: true))
+            to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
+              and_return(double(execute: true))
 
           post :create, format: :js
         end
@@ -136,8 +136,8 @@ describe Import::GithubController do
 
         it "takes the current user's namespace" do
           expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).with(github_repo, user.namespace, user, access_params).
-            and_return(double(execute: true))
+            to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
+              and_return(double(execute: true))
 
           post :create, format: :js
         end
@@ -158,8 +158,8 @@ describe Import::GithubController do
         context "when the namespace is owned by the GitLab user" do
           it "takes the existing namespace" do
             expect(Gitlab::GithubImport::ProjectCreator).
-              to receive(:new).with(github_repo, existing_namespace, user, access_params).
-              and_return(double(execute: true))
+              to receive(:new).with(github_repo, github_repo.name, existing_namespace, user, access_params).
+                and_return(double(execute: true))
 
             post :create, format: :js
           end
@@ -171,9 +171,10 @@ describe Import::GithubController do
             existing_namespace.save
           end
 
-          it "doesn't create a project" do
+          it "creates a project using user's namespace" do
             expect(Gitlab::GithubImport::ProjectCreator).
-              not_to receive(:new)
+              to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
+                and_return(double(execute: true))
 
             post :create, format: :js
           end
@@ -181,21 +182,63 @@ describe Import::GithubController do
       end
 
       context "when a namespace with the GitHub user's username doesn't exist" do
-        it "creates the namespace" do
-          expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).and_return(double(execute: true))
+        context "when current user can create namespaces" do
+          it "creates the namespace" do
+            expect(Gitlab::GithubImport::ProjectCreator).
+              to receive(:new).and_return(double(execute: true))
 
-          post :create, format: :js
+            expect { post :create, target_namespace: github_repo.name, format: :js }.to change(Namespace, :count).by(1)
+          end
+
+          it "takes the new namespace" do
+            expect(Gitlab::GithubImport::ProjectCreator).
+              to receive(:new).with(github_repo, github_repo.name, an_instance_of(Group), user, access_params).
+              and_return(double(execute: true))
+
+            post :create, target_namespace: github_repo.name, format: :js
+          end
+        end
+
+        context "when current user can't create namespaces" do
+          before do
+            user.update_attribute(:can_create_group, false)
+          end
+
+          it "doesn't create the namespace" do
+            expect(Gitlab::GithubImport::ProjectCreator).
+              to receive(:new).and_return(double(execute: true))
+
+            expect { post :create, format: :js }.not_to change(Namespace, :count)
+          end
 
-          expect(Namespace.where(name: other_username).first).not_to be_nil
+          it "takes the current user's namespace" do
+            expect(Gitlab::GithubImport::ProjectCreator).
+              to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
+              and_return(double(execute: true))
+
+            post :create, format: :js
+          end
         end
+      end
 
-        it "takes the new namespace" do
+      context 'user has chosen a namespace and name for the project' do
+        let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) }
+        let(:test_name) { 'test_name' }
+
+        it 'takes the selected namespace and name' do
           expect(Gitlab::GithubImport::ProjectCreator).
-            to receive(:new).with(github_repo, an_instance_of(Group), user, access_params).
-            and_return(double(execute: true))
+            to receive(:new).with(github_repo, test_name, test_namespace, user, access_params).
+              and_return(double(execute: true))
 
-          post :create, format: :js
+          post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :js }
+        end
+
+        it 'takes the selected name and default namespace' do
+          expect(Gitlab::GithubImport::ProjectCreator).
+            to receive(:new).with(github_repo, test_name, user.namespace, user, access_params).
+              and_return(double(execute: true))
+
+          post :create, { new_name: test_name, format: :js }
         end
       end
     end
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index e8cf6aa..6f75ebb 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -136,21 +136,42 @@ describe Import::GitlabController do
       end
 
       context "when a namespace with the GitLab.com user's username doesn't exist" do
-        it "creates the namespace" do
-          expect(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).and_return(double(execute: true))
+        context "when current user can create namespaces" do
+          it "creates the namespace" do
+            expect(Gitlab::GitlabImport::ProjectCreator).
+              to receive(:new).and_return(double(execute: true))
 
-          post :create, format: :js
+            expect { post :create, format: :js }.to change(Namespace, :count).by(1)
+          end
+
+          it "takes the new namespace" do
+            expect(Gitlab::GitlabImport::ProjectCreator).
+              to receive(:new).with(gitlab_repo, an_instance_of(Group), user, access_params).
+              and_return(double(execute: true))
 
-          expect(Namespace.where(name: other_username).first).not_to be_nil
+            post :create, format: :js
+          end
         end
 
-        it "takes the new namespace" do
-          expect(Gitlab::GitlabImport::ProjectCreator).
-            to receive(:new).with(gitlab_repo, an_instance_of(Group), user, access_params).
-            and_return(double(execute: true))
+        context "when current user can't create namespaces" do
+          before do
+            user.update_attribute(:can_create_group, false)
+          end
 
-          post :create, format: :js
+          it "doesn't create the namespace" do
+            expect(Gitlab::GitlabImport::ProjectCreator).
+              to receive(:new).and_return(double(execute: true))
+
+            expect { post :create, format: :js }.not_to change(Namespace, :count)
+          end
+
+          it "takes the current user's namespace" do
+            expect(Gitlab::GitlabImport::ProjectCreator).
+              to receive(:new).with(gitlab_repo, user.namespace, user, access_params).
+              and_return(double(execute: true))
+
+            post :create, format: :js
+          end
         end
       end
     end
diff --git a/spec/controllers/import/gitorious_controller_spec.rb b/spec/controllers/import/gitorious_controller_spec.rb
deleted file mode 100644
index 4ae2b78..0000000
--- a/spec/controllers/import/gitorious_controller_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-require 'spec_helper'
-
-describe Import::GitoriousController do
-  include ImportSpecHelper
-
-  let(:user) { create(:user) }
-
-  before do
-    sign_in(user)
-  end
-
-  describe "GET new" do
-    it "redirects to import endpoint on gitorious.org" do
-      get :new
-
-      expect(controller).to redirect_to("https://gitorious.org/gitlab-import?callback_url=http://test.host/import/gitorious/callback")
-    end
-  end
-
-  describe "GET callback" do
-    it "stores repo list in session" do
-      get :callback, repos: 'foo/bar,baz/qux'
-
-      expect(session[:gitorious_repos]).to eq('foo/bar,baz/qux')
-    end
-  end
-
-  describe "GET status" do
-    before do
-      @repo = OpenStruct.new(full_name: 'asd/vim')
-    end
-
-    it "assigns variables" do
-      @project = create(:project, import_type: 'gitorious', creator_id: user.id)
-      stub_client(repos: [@repo])
-
-      get :status
-
-      expect(assigns(:already_added_projects)).to eq([@project])
-      expect(assigns(:repos)).to eq([@repo])
-    end
-
-    it "does not show already added project" do
-      @project = create(:project, import_type: 'gitorious', creator_id: user.id, import_source: 'asd/vim')
-      stub_client(repos: [@repo])
-
-      get :status
-
-      expect(assigns(:already_added_projects)).to eq([@project])
-      expect(assigns(:repos)).to eq([])
-    end
-  end
-
-  describe "POST create" do
-    before do
-      @repo = Gitlab::GitoriousImport::Repository.new('asd/vim')
-    end
-
-    it "takes already existing namespace" do
-      namespace = create(:namespace, name: "asd", owner: user)
-      expect(Gitlab::GitoriousImport::ProjectCreator).
-        to receive(:new).with(@repo, namespace, user).
-        and_return(double(execute: true))
-      stub_client(repo: @repo)
-
-      post :create, format: :js
-    end
-  end
-end
diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb
index d0ad5e2..2896636 100644
--- a/spec/controllers/projects/boards/issues_controller_spec.rb
+++ b/spec/controllers/projects/boards/issues_controller_spec.rb
@@ -41,8 +41,8 @@ describe Projects::Boards::IssuesController do
 
     context 'with unauthorized user' do
       before do
-        allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true)
-        allow(Ability.abilities).to receive(:allowed?).with(user, :read_issue, project).and_return(false)
+        allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
+        allow(Ability).to receive(:allowed?).with(user, :read_issue, project).and_return(false)
       end
 
       it 'returns a successful 403 response' do
diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb
index 9496636..d687dea 100644
--- a/spec/controllers/projects/boards/lists_controller_spec.rb
+++ b/spec/controllers/projects/boards/lists_controller_spec.rb
@@ -35,11 +35,11 @@ describe Projects::Boards::ListsController do
 
     context 'with unauthorized user' do
       before do
-        allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true)
-        allow(Ability.abilities).to receive(:allowed?).with(user, :read_list, project).and_return(false)
+        allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
+        allow(Ability).to receive(:allowed?).with(user, :read_list, project).and_return(false)
       end
 
-      it 'returns a successful 403 response' do
+      it 'returns a forbidden 403 response' do
         read_board_list user: user
 
         expect(response).to have_http_status(403)
@@ -56,9 +56,9 @@ describe Projects::Boards::ListsController do
   end
 
   describe 'POST create' do
-    let(:label) { create(:label, project: project, name: 'Development') }
-
     context 'with valid params' do
+      let(:label) { create(:label, project: project, name: 'Development') }
+
       it 'returns a successful 200 response' do
         create_board_list user: user, label_id: label.id
 
@@ -73,20 +73,29 @@ describe Projects::Boards::ListsController do
     end
 
     context 'with invalid params' do
-      it 'returns an error' do
-        create_board_list user: user, label_id: nil
+      context 'when label is nil' do
+        it 'returns a not found 404 response' do
+          create_board_list user: user, label_id: nil
+
+          expect(response).to have_http_status(404)
+        end
+      end
 
-        parsed_response = JSON.parse(response.body)
+      context 'when label that does not belongs to project' do
+        it 'returns a not found 404 response' do
+          label = create(:label, name: 'Development')
 
-        expect(parsed_response['label']).to contain_exactly "can't be blank"
-        expect(response).to have_http_status(422)
+          create_board_list user: user, label_id: label.id
+
+          expect(response).to have_http_status(404)
+        end
       end
     end
 
     context 'with unauthorized user' do
-      let(:label) { create(:label, project: project, name: 'Development') }
+      it 'returns a forbidden 403 response' do
+        label = create(:label, project: project, name: 'Development')
 
-      it 'returns a successful 403 response' do
         create_board_list user: guest, label_id: label.id
 
         expect(response).to have_http_status(403)
@@ -122,7 +131,7 @@ describe Projects::Boards::ListsController do
     end
 
     context 'with invalid position' do
-      it 'returns a unprocessable entity 422 response' do
+      it 'returns an unprocessable entity 422 response' do
         move user: user, list: planning, position: 6
 
         expect(response).to have_http_status(422)
@@ -138,7 +147,7 @@ describe Projects::Boards::ListsController do
     end
 
     context 'with unauthorized user' do
-      it 'returns a successful 403 response' do
+      it 'returns a forbidden 403 response' do
         move user: guest, list: planning, position: 6
 
         expect(response).to have_http_status(403)
@@ -180,7 +189,7 @@ describe Projects::Boards::ListsController do
     end
 
     context 'with unauthorized user' do
-      it 'returns a successful 403 response' do
+      it 'returns a forbidden 403 response' do
         remove_board_list user: guest, list: planning
 
         expect(response).to have_http_status(403)
@@ -213,7 +222,7 @@ describe Projects::Boards::ListsController do
     end
 
     context 'when board lists is not empty' do
-      it 'returns a unprocessable entity 422 response' do
+      it 'returns an unprocessable entity 422 response' do
         create(:list, board: board)
 
         generate_default_board_lists user: user
@@ -223,7 +232,7 @@ describe Projects::Boards::ListsController do
     end
 
     context 'with unauthorized user' do
-      it 'returns a successful 403 response' do
+      it 'returns a forbidden 403 response' do
         generate_default_board_lists user: guest
 
         expect(response).to have_http_status(403)
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index 75a6d39..6f6e608 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -23,8 +23,8 @@ describe Projects::BoardsController do
 
     context 'with unauthorized user' do
       before do
-        allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true)
-        allow(Ability.abilities).to receive(:allowed?).with(user, :read_board, project).and_return(false)
+        allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
+        allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false)
       end
 
       it 'returns a successful 404 response' do
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index 2bec673..ff617fe 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -21,7 +21,7 @@ describe Projects::DiscussionsController do
       sign_in user
     end
 
-    xcontext "when the user is not authorized to resolve the discussion" do
+    context "when the user is not authorized to resolve the discussion" do
       it "returns status 404" do
         post :resolve, request_params
 
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 1692976..9041936 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -370,6 +370,12 @@ describe Projects::IssuesController do
         expect(response).to have_http_status(302)
         expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now
       end
+
+      it 'delegates the update of the todos count cache to TodoService' do
+        expect_any_instance_of(TodoService).to receive(:destroy_issue).with(issue, owner).once
+
+        delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+      end
     end
   end
 
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index c64c2b0..94c9edc 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -170,6 +170,35 @@ describe Projects::MergeRequestsController do
         expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
         expect(merge_request.reload.closed?).to be_truthy
       end
+
+      it 'allows editing of a closed merge request' do
+        merge_request.close!
+
+        put :update,
+            namespace_id: project.namespace.path,
+            project_id: project.path,
+            id: merge_request.iid,
+            merge_request: {
+              title: 'New title'
+            }
+
+        expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
+        expect(merge_request.reload.title).to eq 'New title'
+      end
+
+      it 'does not allow to update target branch closed merge request' do
+        merge_request.close!
+
+        put :update,
+            namespace_id: project.namespace.path,
+            project_id: project.path,
+            id: merge_request.iid,
+            merge_request: {
+              target_branch: 'new_branch'
+            }
+
+        expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch }
+      end
     end
   end
 
@@ -291,6 +320,12 @@ describe Projects::MergeRequestsController do
         expect(response).to have_http_status(302)
         expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
       end
+
+      it 'delegates the update of the todos count cache to TodoService' do
+        expect_any_instance_of(TodoService).to receive(:destroy_merge_request).with(merge_request, owner).once
+
+        delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+      end
     end
   end
 
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index cccd492..2e44b51 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -49,4 +49,20 @@ describe Projects::ServicesController do
       let!(:referrer) { nil }
     end
   end
+
+  describe 'PUT #update' do
+    context 'on successful update' do
+      it 'sets the flash' do
+        expect(service).to receive(:to_param).and_return('hipchat')
+
+        put :update,
+          namespace_id: project.namespace.id,
+          project_id: project.id,
+          id: service.id,
+          service: { active: false }
+
+        expect(flash[:notice]).to eq 'Successfully updated.'
+      end
+    end
+  end
 end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index b8a28f4..72a3ebf 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::SnippetsController do
-  let(:project) { create(:project_empty_repo, :public, snippets_enabled: true) }
+  let(:project) { create(:project_empty_repo, :public) }
   let(:user)    { create(:user) }
   let(:user2)   { create(:user) }
 
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index ffe0641..b0f740f 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -181,6 +181,25 @@ describe ProjectsController do
       expect(response).to have_http_status(302)
       expect(response).to redirect_to(dashboard_projects_path)
     end
+
+    context "when the project is forked" do
+      let(:project)      { create(:project) }
+      let(:fork_project) { create(:project, forked_from_project: project) }
+      let(:merge_request) do
+        create(:merge_request,
+          source_project: fork_project,
+          target_project: project)
+      end
+
+      it "closes all related merge requests" do
+        project.merge_requests << merge_request
+        sign_in(admin)
+
+        delete :destroy, namespace_id: fork_project.namespace.path, id: fork_project.path
+
+        expect(merge_request.reload.state).to eq('closed')
+      end
+    end
   end
 
   describe "POST #toggle_star" do
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 9ced397..191e290 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -1,25 +1,108 @@
 require 'rails_helper'
 
 describe SentNotificationsController, type: :controller do
-  let(:user)              { create(:user) }
-  let(:issue)             { create(:issue, author: user) }
-  let(:sent_notification) { create(:sent_notification, noteable: issue) }
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project) }
+  let(:sent_notification) { create(:sent_notification, noteable: issue, recipient: user) }
 
-  describe 'GET #unsubscribe' do
-    it 'returns a 404 when calling without existing id' do
-      get(:unsubscribe, id: '0' * 32)
+  let(:issue) do
+    create(:issue, project: project, author: user) do |issue|
+      issue.subscriptions.create(user: user, subscribed: true)
+    end
+  end
+
+  describe 'GET unsubscribe' do
+    context 'when the user is not logged in' do
+      context 'when the force param is passed' do
+        before { get(:unsubscribe, id: sent_notification.reply_key, force: true) }
+
+        it 'unsubscribes the user' do
+          expect(issue.subscribed?(user)).to be_falsey
+        end
+
+        it 'sets the flash message' do
+          expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+        end
+
+        it 'redirects to the login page' do
+          expect(response).to redirect_to(new_user_session_path)
+        end
+      end
+
+      context 'when the force param is not passed' do
+        before { get(:unsubscribe, id: sent_notification.reply_key) }
+
+        it 'does not unsubscribe the user' do
+          expect(issue.subscribed?(user)).to be_truthy
+        end
 
-      expect(response.status).to be 404
+        it 'does not set the flash message' do
+          expect(controller).not_to set_flash[:notice]
+        end
+
+        it 'redirects to the login page' do
+          expect(response).to render_template :unsubscribe
+        end
+      end
     end
 
-    context 'calling with id' do
-      it 'shows a flash message to the user' do
-        get(:unsubscribe, id: sent_notification.reply_key)
+    context 'when the user is logged in' do
+      before { sign_in(user) }
+
+      context 'when the ID passed does not exist' do
+        before { get(:unsubscribe, id: sent_notification.reply_key.reverse) }
+
+        it 'does not unsubscribe the user' do
+          expect(issue.subscribed?(user)).to be_truthy
+        end
+
+        it 'does not set the flash message' do
+          expect(controller).not_to set_flash[:notice]
+        end
+
+        it 'returns a 404' do
+          expect(response).to have_http_status(:not_found)
+        end
+      end
+
+      context 'when the force param is passed' do
+        before { get(:unsubscribe, id: sent_notification.reply_key, force: true) }
+
+        it 'unsubscribes the user' do
+          expect(issue.subscribed?(user)).to be_falsey
+        end
+
+        it 'sets the flash message' do
+          expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+        end
+
+        it 'redirects to the issue page' do
+          expect(response).
+            to redirect_to(namespace_project_issue_path(project.namespace, project, issue))
+        end
+      end
+
+      context 'when the force param is not passed' do
+        let(:merge_request) do
+          create(:merge_request, source_project: project, author: user) do |merge_request|
+            merge_request.subscriptions.create(user: user, subscribed: true)
+          end
+        end
+        let(:sent_notification) { create(:sent_notification, noteable: merge_request, recipient: user) }
+        before { get(:unsubscribe, id: sent_notification.reply_key) }
+
+        it 'unsubscribes the user' do
+          expect(merge_request.subscribed?(user)).to be_falsey
+        end
 
-        expect(response.status).to be 302
+        it 'sets the flash message' do
+          expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+        end
 
-        expect(response).to redirect_to new_user_session_path
-        expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+        it 'redirects to the merge request page' do
+          expect(response).
+            to redirect_to(namespace_project_merge_request_path(project.namespace, project, merge_request))
+        end
       end
     end
   end
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 4e9bfb0..8f27e61 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -136,6 +136,29 @@ describe SessionsController do
         post(:create, { user: user_params }, { otp_user_id: user.id })
       end
 
+      context 'remember_me field' do
+        it 'sets a remember_user_token cookie when enabled' do
+          allow(U2fRegistration).to receive(:authenticate).and_return(true)
+          allow(controller).to receive(:find_user).and_return(user)
+          expect(controller).
+            to receive(:remember_me).with(user).and_call_original
+
+          authenticate_2fa_u2f(remember_me: '1', login: user.username, device_response: "{}")
+
+          expect(response.cookies['remember_user_token']).to be_present
+        end
+
+        it 'does nothing when disabled' do
+          allow(U2fRegistration).to receive(:authenticate).and_return(true)
+          allow(controller).to receive(:find_user).and_return(user)
+          expect(controller).not_to receive(:remember_me)
+
+          authenticate_2fa_u2f(remember_me: '0', login: user.username, device_response: "{}")
+
+          expect(response.cookies['remember_user_token']).to be_nil
+        end
+      end
+
       it "creates an audit log record" do
         allow(U2fRegistration).to receive(:authenticate).and_return(true)
         expect { authenticate_2fa_u2f(login: user.username, device_response: "{}") }.to change { SecurityEvent.count }.by(1)
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 2a89159..41d263a 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -1,9 +1,9 @@
 require 'spec_helper'
 
 describe SnippetsController do
-  describe 'GET #show' do
-    let(:user) { create(:user) }
+  let(:user) { create(:user) }
 
+  describe 'GET #show' do
     context 'when the personal snippet is private' do
       let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
 
@@ -230,4 +230,33 @@ describe SnippetsController do
       end
     end
   end
+
+  context 'award emoji on snippets' do
+    let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+    let(:another_user) { create(:user) }
+
+    before do
+      sign_in(another_user)
+    end
+
+    describe 'POST #toggle_award_emoji' do
+      it "toggles the award emoji" do
+        expect do
+          post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup")
+        end.to change { personal_snippet.award_emoji.count }.from(0).to(1)
+
+        expect(response.status).to eq(200)
+      end
+
+      it "removes the already awarded emoji" do
+        post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup")
+
+        expect do
+          post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup")
+        end.to change { personal_snippet.award_emoji.count }.from(1).to(0)
+
+        expect(response.status).to eq(200)
+      end
+    end
+  end
 end
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index 83fccad..3372e5a 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: runner_projects
-#
-#  id         :integer          not null, primary key
-#  runner_id  :integer          not null
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#
-
 FactoryGirl.define do
   factory :ci_runner_project, class: Ci::RunnerProject do
     runner_id 1
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index 5b645fa..e3b73e2 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: runners
-#
-#  id           :integer          not null, primary key
-#  token        :string(255)
-#  created_at   :datetime
-#  updated_at   :datetime
-#  description  :string(255)
-#  contacted_at :datetime
-#  active       :boolean          default(TRUE), not null
-#  is_shared    :boolean          default(FALSE)
-#  name         :string(255)
-#  version      :string(255)
-#  revision     :string(255)
-#  platform     :string(255)
-#  architecture :string(255)
-#
-
 FactoryGirl.define do
   factory :ci_runner, class: Ci::Runner do
     sequence :description do |n|
@@ -30,5 +11,9 @@ FactoryGirl.define do
     trait :shared do
       is_shared true
     end
+
+    trait :inactive do
+      active false
+    end
   end
 end
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
index 856a8e7..6653f0b 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_variables
-#
-#  id                   :integer          not null, primary key
-#  project_id           :integer          not null
-#  key                  :string(255)
-#  value                :text
-#  encrypted_value      :text
-#  encrypted_value_salt :string(255)
-#  encrypted_value_iv   :string(255)
-#  gl_project_id        :integer
-#
-
 FactoryGirl.define do
   factory :ci_variable, class: Ci::Variable do
     sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 8259160..6f24bf5 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -3,11 +3,12 @@ FactoryGirl.define do
     sha '97de212e80737a608d939f648d959671fb0a0142'
     ref 'master'
     tag false
+    project nil
 
     environment factory: :environment
 
     after(:build) do |deployment, evaluator|
-      deployment.project = deployment.environment.project
+      deployment.project ||= deployment.environment.project
     end
   end
 end
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index 90788f3..8820d52 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -1,10 +1,11 @@
 FactoryGirl.define do
   factory :event do
+    project
+    author factory: :user
+
     factory :closed_issue_event do
-      project
       action { Event::CLOSED }
       target factory: :closed_issue
-      author factory: :user
     end
   end
 end
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index debb86d..2044ebe 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: group_members
-#
-#  id                 :integer          not null, primary key
-#  group_access       :integer          not null
-#  group_id           :integer          not null
-#  user_id            :integer          not null
-#  created_at         :datetime
-#  updated_at         :datetime
-#  notification_level :integer          default(3), not null
-#
-
 FactoryGirl.define do
   factory :group_member do
     access_level { GroupMember::OWNER }
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 2c0a2dd..2b4670b 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -1,4 +1,8 @@
 FactoryGirl.define do
+  sequence :issue_created_at do |n|
+    4.hours.ago + ( 2 * n ).seconds
+  end
+
   factory :issue do
     title
     author
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 83e3809..6919002 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -28,6 +28,11 @@ FactoryGirl.define do
           diff_refs: noteable.diff_refs
         )
       end
+
+      trait :resolved do
+        resolved_at { Time.now }
+        resolved_by { create(:user) }
+      end
     end
 
     factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index 4fd51a2..424ecc6 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -14,6 +14,7 @@ FactoryGirl.define do
       note_events true
       build_events true
       pipeline_events true
+      wiki_page_events true
     end
   end
 end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index f82d68a..fb84ba0 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -8,7 +8,6 @@ FactoryGirl.define do
     path { name.downcase.gsub(/\s/, '_') }
     namespace
     creator
-    snippets_enabled true
 
     trait :public do
       visibility_level Gitlab::VisibilityLevel::PUBLIC
@@ -27,6 +26,26 @@ FactoryGirl.define do
         project.create_repository
       end
     end
+
+    # Nest Project Feature attributes
+    transient do
+      wiki_access_level ProjectFeature::ENABLED
+      builds_access_level ProjectFeature::ENABLED
+      snippets_access_level ProjectFeature::ENABLED
+      issues_access_level ProjectFeature::ENABLED
+      merge_requests_access_level ProjectFeature::ENABLED
+    end
+
+    after(:create) do |project, evaluator|
+      project.project_feature.
+        update_attributes(
+          wiki_access_level: evaluator.wiki_access_level,
+          builds_access_level: evaluator.builds_access_level,
+          snippets_access_level: evaluator.snippets_access_level,
+          issues_access_level: evaluator.issues_access_level,
+          merge_requests_access_level: evaluator.merge_requests_access_level,
+        )
+    end
   end
 
   # Project with empty repository
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 5d77789..1994197 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
 
 describe 'Issue Boards', feature: true, js: true do
   include WaitForAjax
+  include WaitForVueResource
 
   let(:project) { create(:empty_project, :public) }
   let(:user)    { create(:user) }
@@ -93,15 +94,8 @@ describe 'Issue Boards', feature: true, js: true do
     end
 
     it 'shows issues in lists' do
-      page.within(find('.board:nth-child(2)')) do
-        expect(page.find('.board-header')).to have_content('2')
-        expect(page).to have_selector('.card', count: 2)
-      end
-
-      page.within(find('.board:nth-child(3)')) do
-        expect(page.find('.board-header')).to have_content('2')
-        expect(page).to have_selector('.card', count: 2)
-      end
+      wait_for_board_cards(2, 2)
+      wait_for_board_cards(3, 2)
     end
 
     it 'shows confidential issues with icon' do
@@ -110,6 +104,45 @@ describe 'Issue Boards', feature: true, js: true do
       end
     end
 
+    it 'search backlog list' do
+      page.within('#js-boards-seach') do
+        find('.form-control').set(issue1.title)
+      end
+
+      wait_for_vue_resource
+
+      expect(find('.board:nth-child(1)')).to have_selector('.card', count: 1)
+      expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0)
+    end
+
+    it 'search done list' do
+      page.within('#js-boards-seach') do
+        find('.form-control').set(issue8.title)
+      end
+
+      wait_for_vue_resource
+
+      expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1)
+    end
+
+    it 'search list' do
+      page.within('#js-boards-seach') do
+        find('.form-control').set(issue5.title)
+      end
+
+      wait_for_vue_resource
+
+      expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1)
+      expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
+      expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0)
+    end
+
     it 'allows user to delete board' do
       page.within(find('.board:nth-child(2)')) do
         find('.board-delete').click
@@ -143,76 +176,53 @@ describe 'Issue Boards', feature: true, js: true do
       wait_for_vue_resource
 
       page.within(find('.board', match: :first)) do
-        expect(page.find('.board-header')).to have_content('20')
+        expect(page.find('.board-header')).to have_content('56')
         expect(page).to have_selector('.card', count: 20)
+        expect(page).to have_content('Showing 20 of 56 issues')
 
         evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
-        wait_for_vue_resource(spinner: false)
+        wait_for_vue_resource
 
-        expect(page.find('.board-header')).to have_content('40')
         expect(page).to have_selector('.card', count: 40)
+        expect(page).to have_content('Showing 40 of 56 issues')
+
+        evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
+        wait_for_vue_resource
+
+        expect(page).to have_selector('.card', count: 56)
+        expect(page).to have_content('Showing all issues')
       end
     end
 
     context 'backlog' do
       it 'shows issues in backlog with no labels' do
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('6')
-          expect(page).to have_selector('.card', count: 6)
-        end
-      end
-
-      it 'is searchable' do
-        page.within(find('.board', match: :first)) do
-          find('.form-control').set issue1.title
-
-          wait_for_vue_resource(spinner: false)
-
-          expect(page).to have_selector('.card', count: 1)
-        end
-      end
-
-      it 'clears search' do
-        page.within(find('.board', match: :first)) do
-          find('.form-control').set issue1.title
-
-          expect(page).to have_selector('.card', count: 1)
-
-          find('.board-search-clear-btn').click
-        end
-
-        wait_for_vue_resource
-
-        page.within(find('.board', match: :first)) do
-          expect(page).to have_selector('.card', count: 6)
-        end
+        wait_for_board_cards(1, 6)
       end
 
       it 'moves issue from backlog into list' do
         drag_to(list_to_index: 1)
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('5')
-          expect(page).to have_selector('.card', count: 5)
-        end
-
         wait_for_vue_resource
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('3')
-          expect(page).to have_selector('.card', count: 3)
-        end
+        wait_for_board_cards(1, 5)
+        wait_for_board_cards(2, 3)
       end
     end
 
     context 'done' do
       it 'shows list of done issues' do
-        expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1)
+        wait_for_board_cards(4, 1)
+        wait_for_ajax
       end
 
       it 'moves issue to done' do
         drag_to(list_from_index: 0, list_to_index: 3)
 
+        wait_for_board_cards(1, 5)
+        wait_for_board_cards(2, 2)
+        wait_for_board_cards(3, 2)
+        wait_for_board_cards(4, 2)
+
+        expect(find('.board:nth-child(1)')).not_to have_content(issue9.title)
         expect(find('.board:nth-child(4)')).to have_selector('.card', count: 2)
         expect(find('.board:nth-child(4)')).to have_content(issue9.title)
         expect(find('.board:nth-child(4)')).not_to have_content(planning.title)
@@ -221,8 +231,12 @@ describe 'Issue Boards', feature: true, js: true do
       it 'removes all of the same issue to done' do
         drag_to(list_from_index: 1, list_to_index: 3)
 
-        expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1)
-        expect(find('.board:nth-child(3)')).to have_selector('.card', count: 1)
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 1)
+        wait_for_board_cards(3, 1)
+        wait_for_board_cards(4, 2)
+
+        expect(find('.board:nth-child(2)')).not_to have_content(issue6.title)
         expect(find('.board:nth-child(4)')).to have_content(issue6.title)
         expect(find('.board:nth-child(4)')).not_to have_content(planning.title)
       end
@@ -232,6 +246,11 @@ describe 'Issue Boards', feature: true, js: true do
       it 'changes position of list' do
         drag_to(list_from_index: 1, list_to_index: 2, selector: '.board-header')
 
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 2)
+        wait_for_board_cards(3, 2)
+        wait_for_board_cards(4, 1)
+
         expect(find('.board:nth-child(2)')).to have_content(development.title)
         expect(find('.board:nth-child(2)')).to have_content(planning.title)
       end
@@ -239,8 +258,11 @@ describe 'Issue Boards', feature: true, js: true do
       it 'issue moves between lists' do
         drag_to(list_from_index: 1, card_index: 1, list_to_index: 2)
 
-        expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1)
-        expect(find('.board:nth-child(3)')).to have_selector('.card', count: 3)
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 1)
+        wait_for_board_cards(3, 3)
+        wait_for_board_cards(4, 1)
+
         expect(find('.board:nth-child(3)')).to have_content(issue6.title)
         expect(find('.board:nth-child(3)').all('.card').last).not_to have_content(development.title)
       end
@@ -248,8 +270,11 @@ describe 'Issue Boards', feature: true, js: true do
       it 'issue moves between lists' do
         drag_to(list_from_index: 2, list_to_index: 1)
 
-        expect(find('.board:nth-child(2)')).to have_selector('.card', count: 3)
-        expect(find('.board:nth-child(3)')).to have_selector('.card', count: 1)
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 3)
+        wait_for_board_cards(3, 1)
+        wait_for_board_cards(4, 1)
+
         expect(find('.board:nth-child(2)')).to have_content(issue7.title)
         expect(find('.board:nth-child(2)').all('.card').first).not_to have_content(planning.title)
       end
@@ -257,8 +282,12 @@ describe 'Issue Boards', feature: true, js: true do
       it 'issue moves from done' do
         drag_to(list_from_index: 3, list_to_index: 1)
 
-        expect(find('.board:nth-child(2)')).to have_selector('.card', count: 3)
         expect(find('.board:nth-child(2)')).to have_content(issue8.title)
+
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 3)
+        wait_for_board_cards(3, 2)
+        wait_for_board_cards(4, 0)
       end
 
       context 'issue card' do
@@ -321,10 +350,7 @@ describe 'Issue Boards', feature: true, js: true do
         end
 
         it 'moves issues from backlog into new list' do
-          page.within(find('.board', match: :first)) do
-            expect(page.find('.board-header')).to have_content('6')
-            expect(page).to have_selector('.card', count: 6)
-          end
+          wait_for_board_cards(1, 6)
 
           click_button 'Create new list'
           wait_for_ajax
@@ -335,10 +361,7 @@ describe 'Issue Boards', feature: true, js: true do
 
           wait_for_vue_resource
 
-          page.within(find('.board', match: :first)) do
-            expect(page.find('.board-header')).to have_content('5')
-            expect(page).to have_selector('.card', count: 5)
-          end
+          wait_for_board_cards(1, 5)
         end
       end
     end
@@ -352,22 +375,14 @@ describe 'Issue Boards', feature: true, js: true do
           page.within '.dropdown-menu-author' do
             click_link(user2.name)
           end
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
 
           expect(find('.js-author-search')).to have_content(user2.name)
         end
 
         wait_for_vue_resource
-
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
       end
 
       it 'filters by assignee' do
@@ -378,22 +393,15 @@ describe 'Issue Boards', feature: true, js: true do
           page.within '.dropdown-menu-assignee' do
             click_link(user.name)
           end
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
 
           expect(find('.js-assignee-search')).to have_content(user.name)
         end
 
         wait_for_vue_resource
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
       end
 
       it 'filters by milestone' do
@@ -404,22 +412,16 @@ describe 'Issue Boards', feature: true, js: true do
           page.within '.milestone-filter' do
             click_link(milestone.title)
           end
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
 
           expect(find('.js-milestone-select')).to have_content(milestone.title)
         end
 
         wait_for_vue_resource
-
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
+        wait_for_board_cards(1, 0)
+        wait_for_board_cards(2, 1)
+        wait_for_board_cards(3, 0)
+        wait_for_board_cards(4, 0)
       end
 
       it 'filters by label' do
@@ -429,22 +431,14 @@ describe 'Issue Boards', feature: true, js: true do
 
           page.within '.dropdown-menu-labels' do
             click_link(testing.title)
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             find('.dropdown-menu-close').click
           end
         end
 
         wait_for_vue_resource
-
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
       end
 
       it 'infinite scrolls list with label filter' do
@@ -458,7 +452,7 @@ describe 'Issue Boards', feature: true, js: true do
 
           page.within '.dropdown-menu-labels' do
             click_link(testing.title)
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             find('.dropdown-menu-close').click
           end
         end
@@ -466,13 +460,19 @@ describe 'Issue Boards', feature: true, js: true do
         wait_for_vue_resource
 
         page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('20')
+          expect(page.find('.board-header')).to have_content('51')
           expect(page).to have_selector('.card', count: 20)
+          expect(page).to have_content('Showing 20 of 51 issues')
 
           evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
 
-          expect(page.find('.board-header')).to have_content('40')
           expect(page).to have_selector('.card', count: 40)
+          expect(page).to have_content('Showing 40 of 51 issues')
+
+          evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
+
+          expect(page).to have_selector('.card', count: 51)
+          expect(page).to have_content('Showing all issues')
         end
       end
 
@@ -483,24 +483,17 @@ describe 'Issue Boards', feature: true, js: true do
 
           page.within(find('.dropdown-menu-labels')) do
             click_link(testing.title)
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             click_link(bug.title)
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             find('.dropdown-menu-close').click
           end
         end
 
         wait_for_vue_resource
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
       end
 
       it 'filters by no label' do
@@ -510,22 +503,17 @@ describe 'Issue Boards', feature: true, js: true do
 
           page.within '.dropdown-menu-labels' do
             click_link("No Label")
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             find('.dropdown-menu-close').click
           end
         end
 
         wait_for_vue_resource
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('5')
-          expect(page).to have_selector('.card', count: 5)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 5)
+        wait_for_board_cards(2, 0)
+        wait_for_board_cards(3, 0)
+        wait_for_board_cards(4, 1)
       end
 
       it 'filters by clicking label button on issue' do
@@ -533,20 +521,13 @@ describe 'Issue Boards', feature: true, js: true do
           expect(page).to have_selector('.card', count: 6)
           expect(find('.card', match: :first)).to have_content(bug.title)
           click_button(bug.title)
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
         end
 
         wait_for_vue_resource
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
 
         page.within('.labels-filter') do
           expect(find('.dropdown-toggle-text')).to have_content(bug.title)
@@ -558,7 +539,7 @@ describe 'Issue Boards', feature: true, js: true do
           page.within(find('.card', match: :first)) do
             click_button(bug.title)
           end
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
 
           expect(page).to have_selector('.card', count: 1)
         end
@@ -622,13 +603,16 @@ describe 'Issue Boards', feature: true, js: true do
     wait_for_vue_resource
   end
 
-  def wait_for_vue_resource(spinner: true)
-    Timeout.timeout(Capybara.default_max_wait_time) do
-      loop until page.evaluate_script('Vue.activeResources').zero?
+  def wait_for_board_cards(board_number, expected_cards)
+    page.within(find(".board:nth-child(#{board_number})")) do
+      expect(page.find('.board-header')).to have_content(expected_cards.to_s)
+      expect(page).to have_selector('.card', count: expected_cards)
     end
+  end
 
-    if spinner
-      expect(find('.boards-list')).not_to have_selector('.fa-spinner')
+  def wait_for_empty_boards(board_numbers)
+    board_numbers.each do |board|
+      wait_for_board_cards(board, 0)
     end
   end
 end
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
new file mode 100644
index 0000000..7ef68e9
--- /dev/null
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+describe 'Issue Boards shortcut', feature: true, js: true do
+  include WaitForVueResource
+
+  let(:project) { create(:empty_project) }
+
+  before do
+    project.create_board
+    project.board.lists.create(list_type: :backlog)
+    project.board.lists.create(list_type: :done)
+
+    login_as :admin
+
+    visit namespace_project_path(project.namespace, project)
+  end
+
+  it 'takes user to issue board index' do
+    find('body').native.send_keys('gl')
+    expect(page).to have_selector('.boards-list')
+
+    wait_for_vue_resource
+  end
+end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
deleted file mode 100644
index 0cfeb2e..0000000
--- a/spec/features/builds_spec.rb
+++ /dev/null
@@ -1,326 +0,0 @@
-require 'spec_helper'
-
-describe "Builds" do
-  let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
-
-  before do
-    login_as(:user)
-    @commit = FactoryGirl.create :ci_pipeline
-    @build = FactoryGirl.create :ci_build, pipeline: @commit
-    @build2 = FactoryGirl.create :ci_build
-    @project = @commit.project
-    @project.team << [@user, :developer]
-  end
-
-  describe "GET /:project/builds" do
-    context "Pending scope" do
-      before do
-        visit namespace_project_builds_path(@project.namespace, @project, scope: :pending)
-      end
-
-      it "shows Pending tab builds" do
-        expect(page).to have_link 'Cancel running'
-        expect(page).to have_selector('.nav-links li.active', text: 'Pending')
-        expect(page).to have_content @build.short_sha
-        expect(page).to have_content @build.ref
-        expect(page).to have_content @build.name
-      end
-    end
-
-    context "Running scope" do
-      before do
-        @build.run!
-        visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
-      end
-
-      it "shows Running tab builds" do
-        expect(page).to have_selector('.nav-links li.active', text: 'Running')
-        expect(page).to have_link 'Cancel running'
-        expect(page).to have_content @build.short_sha
-        expect(page).to have_content @build.ref
-        expect(page).to have_content @build.name
-      end
-    end
-
-    context "Finished scope" do
-      before do
-        @build.run!
-        visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
-      end
-
-      it "shows Finished tab builds" do
-        expect(page).to have_selector('.nav-links li.active', text: 'Finished')
-        expect(page).to have_content 'No builds to show'
-        expect(page).to have_link 'Cancel running'
-      end
-    end
-
-    context "All builds" do
-      before do
-        @project.builds.running_or_pending.each(&:success)
-        visit namespace_project_builds_path(@project.namespace, @project)
-      end
-
-      it "shows All tab builds" do
-        expect(page).to have_selector('.nav-links li.active', text: 'All')
-        expect(page).to have_content @build.short_sha
-        expect(page).to have_content @build.ref
-        expect(page).to have_content @build.name
-        expect(page).not_to have_link 'Cancel running'
-      end
-    end
-  end
-
-  describe "POST /:project/builds/:id/cancel_all" do
-    before do
-      @build.run!
-      visit namespace_project_builds_path(@project.namespace, @project)
-      click_link "Cancel running"
-    end
-
-    it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
-    it { expect(page).to have_content 'canceled' }
-    it { expect(page).to have_content @build.short_sha }
-    it { expect(page).to have_content @build.ref }
-    it { expect(page).to have_content @build.name }
-    it { expect(page).not_to have_link 'Cancel running' }
-  end
-
-  describe "GET /:project/builds/:id" do
-    context "Build from project" do
-      before do
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-      end
-
-      it { expect(page.status_code).to eq(200) }
-      it { expect(page).to have_content @commit.sha[0..7] }
-      it { expect(page).to have_content @commit.git_commit_message }
-      it { expect(page).to have_content @commit.git_author_name }
-    end
-
-    context "Build from other project" do
-      before do
-        visit namespace_project_build_path(@project.namespace, @project, @build2)
-      end
-
-      it { expect(page.status_code).to eq(404) }
-    end
-
-    context "Download artifacts" do
-      before do
-        @build.update_attributes(artifacts_file: artifacts_file)
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-      end
-
-      it 'has button to download artifacts' do
-        expect(page).to have_content 'Download'
-      end
-    end
-
-    context 'Artifacts expire date' do
-      before do
-        @build.update_attributes(artifacts_file: artifacts_file, artifacts_expire_at: expire_at)
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-      end
-
-      context 'no expire date defined' do
-        let(:expire_at) { nil }
-
-        it 'does not have the Keep button' do
-          expect(page).not_to have_content 'Keep'
-        end
-      end
-
-      context 'when expire date is defined' do
-        let(:expire_at) { Time.now + 7.days }
-
-        it 'keeps artifacts when Keep button is clicked' do
-          expect(page).to have_content 'The artifacts will be removed'
-          click_link 'Keep'
-
-          expect(page).not_to have_link 'Keep'
-          expect(page).not_to have_content 'The artifacts will be removed'
-        end
-      end
-
-      context 'when artifacts expired' do
-        let(:expire_at) { Time.now - 7.days }
-
-        it 'does not have the Keep button' do
-          expect(page).to have_content 'The artifacts were removed'
-          expect(page).not_to have_link 'Keep'
-        end
-      end
-    end
-
-    context 'Build raw trace' do
-      before do
-        @build.run!
-        @build.trace = 'BUILD TRACE'
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-      end
-
-      it do
-        expect(page).to have_link 'Raw'
-      end
-    end
-  end
-
-  describe "POST /:project/builds/:id/cancel" do
-    context "Build from project" do
-      before do
-        @build.run!
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-        click_link "Cancel"
-      end
-
-      it { expect(page.status_code).to eq(200) }
-      it { expect(page).to have_content 'canceled' }
-      it { expect(page).to have_content 'Retry' }
-    end
-
-    context "Build from other project" do
-      before do
-        @build.run!
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-        page.driver.post(cancel_namespace_project_build_path(@project.namespace, @project, @build2))
-      end
-
-      it { expect(page.status_code).to eq(404) }
-    end
-  end
-
-  describe "POST /:project/builds/:id/retry" do
-    context "Build from project" do
-      before do
-        @build.run!
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-        click_link 'Cancel'
-        click_link 'Retry'
-      end
-
-      it 'shows the right status and buttons' do
-        expect(page).to have_http_status(200)
-        expect(page).to have_content 'pending'
-        page.within('aside.right-sidebar') do
-          expect(page).to have_content 'Cancel'
-        end
-      end
-    end
-
-    context "Build from other project" do
-      before do
-        @build.run!
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-        click_link 'Cancel'
-        page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2))
-      end
-
-      it { expect(page).to have_http_status(404) }
-    end
-
-    context "Build that current user is not allowed to retry" do
-      before do
-        @build.run!
-        @build.cancel!
-        @project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
-
-        logout_direct
-        login_with(create(:user))
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-      end
-
-      it 'does not show the Retry button' do
-        page.within('aside.right-sidebar') do
-          expect(page).not_to have_content 'Retry'
-        end
-      end
-    end
-  end
-
-  describe "GET /:project/builds/:id/download" do
-    before do
-      @build.update_attributes(artifacts_file: artifacts_file)
-      visit namespace_project_build_path(@project.namespace, @project, @build)
-      click_link 'Download'
-    end
-
-    context "Build from other project" do
-      before do
-        @build2.update_attributes(artifacts_file: artifacts_file)
-        visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2)
-      end
-
-      it { expect(page.status_code).to eq(404) }
-    end
-  end
-
-  describe "GET /:project/builds/:id/raw" do
-    context "Build from project" do
-      before do
-        Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
-        @build.run!
-        @build.trace = 'BUILD TRACE'
-        visit namespace_project_build_path(@project.namespace, @project, @build)
-        page.within('.js-build-sidebar') { click_link 'Raw' }
-      end
-
-      it 'sends the right headers' do
-        expect(page.status_code).to eq(200)
-        expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
-        expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace)
-      end
-    end
-
-    context "Build from other project" do
-      before do
-        Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
-        @build2.run!
-        @build2.trace = 'BUILD TRACE'
-        visit raw_namespace_project_build_path(@project.namespace, @project, @build2)
-        puts page.status_code
-        puts current_url
-      end
-
-      it 'sends the right headers' do
-        expect(page.status_code).to eq(404)
-      end
-    end
-  end
-
-  describe "GET /:project/builds/:id/trace.json" do
-    context "Build from project" do
-      before do
-        visit trace_namespace_project_build_path(@project.namespace, @project, @build, format: :json)
-      end
-
-      it { expect(page.status_code).to eq(200) }
-    end
-
-    context "Build from other project" do
-      before do
-        visit trace_namespace_project_build_path(@project.namespace, @project, @build2, format: :json)
-      end
-
-      it { expect(page.status_code).to eq(404) }
-    end
-  end
-
-  describe "GET /:project/builds/:id/status" do
-    context "Build from project" do
-      before do
-        visit status_namespace_project_build_path(@project.namespace, @project, @build)
-      end
-
-      it { expect(page.status_code).to eq(200) }
-    end
-
-    context "Build from other project" do
-      before do
-        visit status_namespace_project_build_path(@project.namespace, @project, @build2)
-      end
-
-      it { expect(page.status_code).to eq(404) }
-    end
-  end
-end
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
new file mode 100644
index 0000000..fd5fbaf
--- /dev/null
+++ b/spec/features/calendar_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+feature 'Contributions Calendar', js: true, feature: true do
+  include WaitForAjax
+
+  let(:contributed_project) { create(:project, :public) }
+
+  before do
+    login_as :user
+
+    issue_params = { title: 'Bug in old browser' }
+    Issues::CreateService.new(contributed_project, @user, issue_params).execute
+
+    # Push code contribution
+    push_params = {
+      project: contributed_project,
+      action: Event::PUSHED,
+      author_id: @user.id,
+      data: { commit_count: 3 }
+    }
+
+    Event.create(push_params)
+
+    visit @user.username
+    wait_for_ajax
+  end
+
+  it 'displays calendar', js: true do
+    expect(page).to have_css('.js-contrib-calendar')
+  end
+
+  it 'displays calendar activity log', js: true do
+    expect(find('.content_list .event-note')).to have_content "Bug in old browser"
+  end
+
+  it 'displays calendar activity square color', js: true do
+    expect(page).to have_selector('.user-contrib-cell[fill=\'#acd5f2\']', count: 1)
+  end
+end
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index fcd41b3..4309a72 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -150,7 +150,7 @@ feature 'Environments', feature: true do
 
       context 'for invalid name' do
         before do
-          fill_in('Name', with: 'name with spaces')
+          fill_in('Name', with: 'name,with,commas')
           click_on 'Save'
         end
 
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 688f68d..8863554 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -211,6 +211,13 @@ feature 'Expand and collapse diffs', js: true, feature: true do
   context 'expanding all diffs' do
     before do
       click_link('Expand all')
+
+      # Wait for elements to appear to ensure full page reload
+      expect(page).to have_content('This diff was suppressed by a .gitattributes entry')
+      expect(page).to have_content('This diff could not be displayed because it is too large.')
+      expect(page).to have_content('too_large_image.jpg')
+      find('.note-textarea')
+
       wait_for_ajax
       execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
     end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 6eb04cf..79cc50b 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -12,7 +12,6 @@ describe 'Awards Emoji', feature: true do
   describe 'Click award emoji from issue#show' do
     let!(:issue) do
       create(:issue,
-             author: @user,
              assignee: @user,
              project: project)
     end
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index e262f28..d1501c9 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -8,6 +8,7 @@ describe 'Filter issues', feature: true do
   let!(:milestone) { create(:milestone, project: project) }
   let!(:label)     { create(:label, project: project) }
   let!(:issue1)    { create(:issue, project: project) }
+  let!(:wontfix)   { create(:label, project: project, title: "Won't fix") }
 
   before do
     project.team << [user, :master]
@@ -100,13 +101,49 @@ describe 'Filter issues', feature: true do
       expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label')
     end
 
-    it 'filters by no label' do
+    it 'filters by a label' do
       find('.dropdown-menu-labels a', text: label.title).click
       page.within '.labels-filter' do
         expect(page).to have_content label.title
       end
       expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
     end
+
+    it "filters by `won't fix` and another label" do
+      find('.dropdown-menu-labels a', text: label.title).click
+      page.within '.labels-filter' do
+        expect(page).to have_content wontfix.title
+        click_link wontfix.title
+      end
+      expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title)
+    end
+
+    it "filters by `won't fix` label followed by another label after page load" do
+      find('.dropdown-menu-labels a', text: wontfix.title).click
+      # Close label dropdown to load
+      find('body').click
+      expect(find('.filtered-labels')).to have_content(wontfix.title)
+
+      find('.js-label-select').click
+      wait_for_ajax
+      find('.dropdown-menu-labels a', text: label.title).click
+      # Close label dropdown to load
+      find('body').click
+      expect(find('.filtered-labels')).to have_content(label.title)
+
+      find('.js-label-select').click
+      wait_for_ajax
+      expect(find('.dropdown-menu-labels li', text: wontfix.title)).to have_css('.is-active')
+      expect(find('.dropdown-menu-labels li', text: label.title)).to have_css('.is-active')
+    end
+
+    it "selects and unselects `won't fix`" do
+      find('.dropdown-menu-labels a', text: wontfix.title).click
+      find('.dropdown-menu-labels a', text: wontfix.title).click
+      # Close label dropdown to load
+      find('body').click
+      expect(page).not_to have_css('.filtered-labels')
+    end
   end
 
   describe 'Filter issues for assignee and label from issues#index' do
@@ -169,7 +206,7 @@ describe 'Filter issues', feature: true do
 
     context 'only text', js: true do
       it 'filters issues by searched text' do
-        fill_in 'issue_search', with: 'Bug'
+        fill_in 'issuable_search', with: 'Bug'
 
         page.within '.issues-list' do
           expect(page).to have_selector('.issue', count: 2)
@@ -177,7 +214,7 @@ describe 'Filter issues', feature: true do
       end
 
       it 'does not show any issues' do
-        fill_in 'issue_search', with: 'testing'
+        fill_in 'issuable_search', with: 'testing'
 
         page.within '.issues-list' do
           expect(page).not_to have_selector('.issue')
@@ -187,7 +224,7 @@ describe 'Filter issues', feature: true do
 
     context 'text and dropdown options', js: true do
       it 'filters by text and label' do
-        fill_in 'issue_search', with: 'Bug'
+        fill_in 'issuable_search', with: 'Bug'
 
         page.within '.issues-list' do
           expect(page).to have_selector('.issue', count: 2)
@@ -205,7 +242,7 @@ describe 'Filter issues', feature: true do
       end
 
       it 'filters by text and milestone' do
-        fill_in 'issue_search', with: 'Bug'
+        fill_in 'issuable_search', with: 'Bug'
 
         page.within '.issues-list' do
           expect(page).to have_selector('.issue', count: 2)
@@ -222,7 +259,7 @@ describe 'Filter issues', feature: true do
       end
 
       it 'filters by text and assignee' do
-        fill_in 'issue_search', with: 'Bug'
+        fill_in 'issuable_search', with: 'Bug'
 
         page.within '.issues-list' do
           expect(page).to have_selector('.issue', count: 2)
@@ -239,7 +276,7 @@ describe 'Filter issues', feature: true do
       end
 
       it 'filters by text and author' do
-        fill_in 'issue_search', with: 'Bug'
+        fill_in 'issuable_search', with: 'Bug'
 
         page.within '.issues-list' do
           expect(page).to have_selector('.issue', count: 2)
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
index e528aff..fb0c470 100644
--- a/spec/features/issues/new_branch_button_spec.rb
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -20,7 +20,7 @@ feature 'Start new branch from an issue', feature: true do
     context "when there is a referenced merge request" do
       let(:note) do
         create(:note, :on_issue, :system, project: project,
-                                          note: "mentioned in !#{referenced_mr.iid}")
+                                          note: "Mentioned in !#{referenced_mr.iid}")
       end
       let(:referenced_mr) do
         create(:merge_request, :simple, source_project: project, target_project: project,
diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb
new file mode 100644
index 0000000..f4d0f13
--- /dev/null
+++ b/spec/features/issues/reset_filters_spec.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+feature 'Issues filter reset button', feature: true, js: true do
+  include WaitForAjax
+  include IssueHelpers
+
+  let!(:project)    { create(:project, :public) }
+  let!(:user)        { create(:user)}
+  let!(:milestone)  { create(:milestone, project: project) }
+  let!(:bug)        { create(:label, project: project, name: 'bug')}
+  let!(:issue1)     { create(:issue, project: project, milestone: milestone, author: user, assignee: user, title: 'Feature')}
+  let!(:issue2)     { create(:labeled_issue, project: project, labels: [bug], title: 'Bugfix1')}
+
+  before do
+    project.team << [user, :developer]
+  end
+
+  context 'when a milestone filter has been applied' do
+    it 'resets the milestone filter' do
+      visit_issues(project, milestone_title: milestone.title)
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when a label filter has been applied' do
+    it 'resets the label filter' do
+      visit_issues(project, label_name: bug.name)
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when a text search has been conducted' do
+    it 'resets the text search filter' do
+      visit_issues(project, search: 'Bug')
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when author filter has been applied' do
+    it 'resets the author filter' do
+      visit_issues(project, author_id: user.id)
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when assignee filter has been applied' do
+    it 'resets the assignee filter' do
+      visit_issues(project, assignee_id: user.id)
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when all filters have been applied' do
+    it 'resets all filters' do
+      visit_issues(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
+      expect(page).to have_css('.issue', count: 0)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  def reset_filters
+    find('.reset-filters').click
+  end
+end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 2883e39..105629c 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -1,6 +1,7 @@
 require 'rails_helper'
 
 feature 'Issues > User uses slash commands', feature: true, js: true do
+  include SlashCommandsHelpers
   include WaitForAjax
 
   it_behaves_like 'issuable record that supports slash commands in its description and notes', :issue do
@@ -17,14 +18,15 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
       visit namespace_project_issue_path(project.namespace, project, issue)
     end
 
+    after do
+      wait_for_ajax
+    end
+
     describe 'adding a due date from note' do
       let(:issue) { create(:issue, project: project) }
 
       it 'does not create a note, and sets the due date accordingly' do
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/due 2016-08-28"
-          click_button 'Comment'
-        end
+        write_note("/due 2016-08-28")
 
         expect(page).not_to have_content '/due 2016-08-28'
         expect(page).to have_content 'Your commands have been executed!'
@@ -41,10 +43,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
       it 'does not create a note, and removes the due date accordingly' do
         expect(issue.due_date).to eq Date.new(2016, 8, 28)
 
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/remove_due_date"
-          click_button 'Comment'
-        end
+        write_note("/remove_due_date")
 
         expect(page).not_to have_content '/remove_due_date'
         expect(page).to have_content 'Your commands have been executed!'
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 2e59595..22359c8 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -122,6 +122,17 @@ describe 'Issues', feature: true do
           expect(page).to have_content date.to_s(:medium)
         end
       end
+
+      it 'warns about version conflict' do
+        issue.update(title: "New title")
+
+        fill_in 'issue_title', with: 'bug 345'
+        fill_in 'issue_description', with: 'bug description'
+
+        click_button 'Save changes'
+
+        expect(page).to have_content 'Someone edited the issue the same time you did'
+      end
     end
   end
 
@@ -133,7 +144,7 @@ describe 'Issues', feature: true do
       visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id)
 
       expect(page).to have_content 'foobar'
-      expect(page.all('.issue-no-comments').first.text).to eq "0"
+      expect(page.all('.no-comments').first.text).to eq "0"
     end
   end
 
diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb
new file mode 100644
index 0000000..06fad10
--- /dev/null
+++ b/spec/features/merge_requests/diff_notes_spec.rb
@@ -0,0 +1,238 @@
+require 'spec_helper'
+
+feature 'Diff notes', js: true, feature: true do
+  include WaitForAjax
+
+  before do
+    login_as :admin
+    @merge_request = create(:merge_request)
+    @project = @merge_request.source_project
+  end
+
+  context 'merge request diffs' do
+    let(:comment_button_class) { '.add-diff-note' }
+    let(:notes_holder_input_class) { 'js-temp-notes-holder' }
+    let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' }
+    let(:test_note_comment) { 'this is a test note!' }
+
+    context 'when hovering over a parallel view diff file' do
+      before(:each) do
+        visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'parallel')
+      end
+
+      context 'with an old line on the left and no line on the right' do
+        it 'should allow commenting on the left side' do
+          should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'left')
+        end
+
+        it 'should not allow commenting on the right side' do
+          should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right')
+        end
+      end
+
+      context 'with no line on the left and a new line on the right' do
+        it 'should not allow commenting on the left side' do
+          should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left')
+        end
+
+        it 'should allow commenting on the right side' do
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right')
+        end
+      end
+
+      context 'with an old line on the left and a new line on the right' do
+        it 'should allow commenting on the left side' do
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left')
+        end
+
+        it 'should allow commenting on the right side' do
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right')
+        end
+      end
+
+      context 'with an unchanged line on the left and an unchanged line on the right' do
+        it 'should allow commenting on the left side' do
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left')
+        end
+
+        it 'should allow commenting on the right side' do
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'right')
+        end
+      end
+
+      context 'with a match line' do
+        it 'should not allow commenting on the left side' do
+          should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'left')
+        end
+
+        it 'should not allow commenting on the right side' do
+          should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right')
+        end
+      end
+
+      context 'with an unfolded line' do
+        before(:each) do
+          find('.js-unfold', match: :first).click
+          wait_for_ajax
+        end
+
+        # The first `.js-unfold` unfolds upwards, therefore the first
+        # `.line_holder` will be an unfolded line.
+        let(:line_holder) { first('.line_holder[id="1"]') }
+
+        it 'should not allow commenting on the left side' do
+          should_not_allow_commenting(line_holder, 'left')
+        end
+
+        it 'should not allow commenting on the right side' do
+          should_not_allow_commenting(line_holder, 'right')
+        end
+      end
+    end
+
+    context 'when hovering over an inline view diff file' do
+      before do
+        visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
+      end
+
+      context 'with a new line' do
+        it 'should allow commenting' do
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+        end
+      end
+
+      context 'with an old line' do
+        it 'should allow commenting' do
+          should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
+        end
+      end
+
+      context 'with an unchanged line' do
+        it 'should allow commenting' do
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
+        end
+      end
+
+      context 'with a match line' do
+        it 'should not allow commenting' do
+          should_not_allow_commenting(find('.match', match: :first))
+        end
+      end
+
+      context 'with an unfolded line' do
+        before(:each) do
+          find('.js-unfold', match: :first).click
+          wait_for_ajax
+        end
+
+        # The first `.js-unfold` unfolds upwards, therefore the first
+        # `.line_holder` will be an unfolded line.
+        let(:line_holder) { first('.line_holder[id="1"]') }
+
+        it 'should not allow commenting' do
+          should_not_allow_commenting line_holder
+        end
+      end
+
+      context 'when hovering over a diff discussion' do
+        before do
+          visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
+          visit namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+        end
+
+        it 'should not allow commenting' do
+          should_not_allow_commenting(find('.line_holder', match: :first))
+        end
+      end
+    end
+
+    context 'when the MR only supports legacy diff notes' do
+      before do
+        @merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
+        visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
+      end
+
+      context 'with a new line' do
+        it 'should allow commenting' do
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+        end
+      end
+
+      context 'with an old line' do
+        it 'should allow commenting' do
+          should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
+        end
+      end
+
+      context 'with an unchanged line' do
+        it 'should allow commenting' do
+          should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
+        end
+      end
+
+      context 'with a match line' do
+        it 'should not allow commenting' do
+          should_not_allow_commenting(find('.match', match: :first))
+        end
+      end
+    end
+
+    def should_allow_commenting(line_holder, diff_side = nil)
+      line = get_line_components(line_holder, diff_side)
+      line[:content].hover
+      expect(line[:num]).to have_css comment_button_class
+
+      comment_on_line(line_holder, line)
+
+      assert_comment_persistence(line_holder)
+    end
+
+    def should_not_allow_commenting(line_holder, diff_side = nil)
+      line = get_line_components(line_holder, diff_side)
+      line[:content].hover
+      expect(line[:num]).not_to have_css comment_button_class
+    end
+
+    def get_line_components(line_holder, diff_side = nil)
+      if diff_side.nil?
+        get_inline_line_components(line_holder)
+      else
+        get_parallel_line_components(line_holder, diff_side)
+      end
+    end
+
+    def get_inline_line_components(line_holder)
+      { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) }
+    end
+
+    def get_parallel_line_components(line_holder, diff_side = nil)
+      side_index = diff_side == 'left' ? 0 : 1
+      # Wait for `.line_content`
+      line_holder.find('.line_content', match: :first)
+      # Wait for `.diff-line-num`
+      line_holder.find('.diff-line-num', match: :first)
+      { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] }
+    end
+
+    def comment_on_line(line_holder, line)
+      line[:num].find(comment_button_class).trigger 'click'
+      line_holder.find(:xpath, notes_holder_input_xpath)
+
+      notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath)
+      expect(notes_holder_input[:class]).to include(notes_holder_input_class)
+
+      notes_holder_input.fill_in 'note[note]', with: test_note_comment
+      click_button 'Comment'
+      wait_for_ajax
+    end
+
+    def assert_comment_persistence(line_holder)
+      expect(line_holder).to have_xpath notes_holder_input_xpath
+
+      notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath)
+      expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class)
+      expect(notes_holder_saved).to have_content test_note_comment
+    end
+  end
+end
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index 4109e78..c77e719 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -17,5 +17,16 @@ feature 'Edit Merge Request', feature: true do
     it 'has class js-quick-submit in form' do
       expect(page).to have_selector('.js-quick-submit')
     end
+
+    it 'warns about version conflict' do
+      merge_request.update(title: "New title")
+
+      fill_in 'merge_request_title', with: 'bug 345'
+      fill_in 'merge_request_description', with: 'bug description'
+
+      click_button 'Save changes'
+
+      expect(page).to have_content 'Someone edited the merge request the same time you did'
+    end
   end
 end
diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb
new file mode 100644
index 0000000..22d9e42
--- /dev/null
+++ b/spec/features/merge_requests/merge_request_versions_spec.rb
@@ -0,0 +1,76 @@
+require 'spec_helper'
+
+feature 'Merge Request versions', js: true, feature: true do
+  before do
+    login_as :admin
+    merge_request = create(:merge_request, importing: true)
+    merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
+    merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+    project = merge_request.source_project
+    visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+  end
+
+  it 'show the latest version of the diff' do
+    page.within '.mr-version-dropdown' do
+      expect(page).to have_content 'latest version'
+    end
+
+    expect(page).to have_content '8 changed files'
+  end
+
+  describe 'switch between versions' do
+    before do
+      page.within '.mr-version-dropdown' do
+        find('.btn-default').click
+        click_link 'version 1'
+      end
+    end
+
+    it 'should show older version' do
+      page.within '.mr-version-dropdown' do
+        expect(page).to have_content 'version 1'
+      end
+
+      expect(page).to have_content '5 changed files'
+    end
+
+    it 'show the message about disabled comments' do
+      expect(page).to have_content 'Comments are disabled'
+    end
+  end
+
+  describe 'compare with older version' do
+    before do
+      page.within '.mr-version-compare-dropdown' do
+        find('.btn-default').click
+        click_link 'version 1'
+      end
+    end
+
+    it 'should have correct value in the compare dropdown' do
+      page.within '.mr-version-compare-dropdown' do
+        expect(page).to have_content 'version 1'
+      end
+    end
+
+    it 'show the message about disabled comments' do
+      expect(page).to have_content 'Comments are disabled'
+    end
+
+    it 'show diff between new and old version' do
+      expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
+    end
+
+    it 'show diff between new and old version' do
+      expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
+    end
+
+    it 'should return to latest version when "Show latest version" button is clicked' do
+      click_link 'Show latest version'
+      page.within '.mr-version-dropdown' do
+        expect(page).to have_content 'latest version'
+      end
+      expect(page).to have_content '8 changed files'
+    end
+  end
+end
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
new file mode 100644
index 0000000..b56fdfe
--- /dev/null
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -0,0 +1,132 @@
+require 'rails_helper'
+
+feature 'Multiple merge requests updating from merge_requests#index', feature: true do
+  include WaitForAjax
+
+  let!(:user)    { create(:user)}
+  let!(:project) { create(:project) }
+  let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+
+  before do
+    project.team << [user, :master]
+    login_as(user)
+  end
+
+  context 'status', js: true do
+    describe 'close merge request' do
+      before do
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it 'closes merge request' do
+        change_status('Closed')
+
+        expect(page).to have_selector('.merge-request', count: 0)
+      end
+    end
+
+    describe 'reopen merge request' do
+      before do
+        merge_request.close
+        visit namespace_project_merge_requests_path(project.namespace, project, state: 'closed')
+      end
+
+      it 'reopens merge request' do
+        change_status('Open')
+
+        expect(page).to have_selector('.merge-request', count: 0)
+      end
+    end
+  end
+
+  context 'assignee', js: true do
+    describe 'set assignee' do
+      before do
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it "updates merge request with assignee" do
+        change_assignee(user.name)
+
+        page.within('.merge-request .controls') do
+          expect(find('.author_link')["title"]).to have_content(user.name)
+        end
+      end
+    end
+
+    describe 'remove assignee' do
+      before do
+        merge_request.assignee = user
+        merge_request.save
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it "removes assignee from the merge request" do
+        change_assignee('Unassigned')
+
+        expect(find('.merge-request .controls')).not_to have_css('.author_link')
+      end
+    end
+  end
+
+  context 'milestone', js: true do
+    let(:milestone)  { create(:milestone, project: project) }
+
+    describe 'set milestone' do
+      before do
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it "updates merge request with milestone" do
+        change_milestone(milestone.title)
+
+        expect(find('.merge-request')).to have_content milestone.title
+      end
+    end
+
+    describe 'unset milestone' do
+      before do
+        merge_request.milestone = milestone
+        merge_request.save
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it "removes milestone from the merge request" do
+        change_milestone("No Milestone")
+
+        expect(find('.merge-request')).not_to have_content milestone.title
+      end
+    end
+  end
+
+  def change_status(text)
+    find('#check_all_issues').click
+    find('.js-issue-status').click
+    find('.dropdown-menu-status a', text: text).click
+    click_update_merge_requests_button
+  end
+
+  def change_assignee(text)
+    find('#check_all_issues').click
+    find('.js-update-assignee').click
+    wait_for_ajax
+
+    page.within '.dropdown-menu-user' do
+      click_link text
+    end
+
+    click_update_merge_requests_button
+  end
+
+  def change_milestone(text)
+    find('#check_all_issues').click
+    find('.issues_bulk_update .js-milestone-select').click
+    find('.dropdown-menu-milestone a', text: text).click
+    click_update_merge_requests_button
+  end
+
+  def click_update_merge_requests_button
+    find('.update_selected_issues').click
+    wait_for_ajax
+  end
+end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index d9ef0d1..22d9d1b 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -1,6 +1,7 @@
 require 'rails_helper'
 
 feature 'Merge Requests > User uses slash commands', feature: true, js: true do
+  include SlashCommandsHelpers
   include WaitForAjax
 
   let(:user) { create(:user) }
@@ -20,11 +21,12 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
       visit namespace_project_merge_request_path(project.namespace, project, merge_request)
     end
 
+    after do
+      wait_for_ajax
+    end
+
     it 'does not recognize the command nor create a note' do
-      page.within('.js-main-target-form') do
-        fill_in 'note[note]', with: "/due 2016-08-28"
-        click_button 'Comment'
-      end
+      write_note("/due 2016-08-28")
 
       expect(page).not_to have_content '/due 2016-08-28'
     end
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index c43661e..b8c838b 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -3,9 +3,8 @@ require 'rails_helper'
 feature 'Milestone', feature: true do
   include WaitForAjax
 
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:empty_project, :public) }
   let(:user)   { create(:user) }
-  let(:milestone) { create(:milestone, project: project, title: 8.7) }
 
   before do
     project.team << [user, :master]
@@ -13,7 +12,7 @@ feature 'Milestone', feature: true do
   end
 
   feature 'Create a milestone' do
-    scenario 'shows an informative message for a new issue' do
+    scenario 'shows an informative message for a new milestone' do
       visit new_namespace_project_milestone_path(project.namespace, project)
       page.within '.milestone-form' do
         fill_in "milestone_title", with: '8.7'
@@ -26,10 +25,26 @@ feature 'Milestone', feature: true do
 
   feature 'Open a milestone with closed issues' do
     scenario 'shows an informative message' do
+      milestone = create(:milestone, project: project, title: 8.7)
+
       create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed")
       visit namespace_project_milestone_path(project.namespace, project, milestone)
 
       expect(find('.alert-success')).to have_content('All issues for this milestone are closed. You may close this milestone now.')
     end
   end
+
+  feature 'Open a milestone with an existing title' do
+    scenario 'displays validation message' do
+      milestone = create(:milestone, project: project, title: 8.7)
+
+      visit new_namespace_project_milestone_path(project.namespace, project)
+      page.within '.milestone-form' do
+        fill_in "milestone_title", with: milestone.title
+      end
+      find('input[name="commit"]').click
+
+      expect(find('.alert-danger')).to have_content('Title has already been taken')
+    end
+  end
 end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 7a9edbb..f1c5221 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -141,7 +141,7 @@ describe 'Comments', feature: true do
     let(:project2) { create(:project, :private) }
     let(:issue) { create(:issue, project: project2) }
     let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') }
-    let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") }
+    let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "Mentioned in #{issue.to_reference(project)}") }
 
     it 'shows the system note' do
       login_as :admin
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
new file mode 100644
index 0000000..3b20d38
--- /dev/null
+++ b/spec/features/profiles/keys_spec.rb
@@ -0,0 +1,18 @@
+require 'rails_helper'
+
+describe 'Profile > SSH Keys', feature: true do
+  let(:user) { create(:user) }
+
+  before do
+    login_as(user)
+    visit profile_keys_path
+  end
+
+  describe 'User adds an SSH key' do
+    it 'auto-populates the title', js: true do
+      fill_in('Key', with: attributes_for(:key).fetch(:key))
+
+      expect(find_field('Title').value).to eq 'dummy at gitlab.com'
+    end
+  end
+end
diff --git a/spec/features/projects/branches/delete_spec.rb b/spec/features/projects/branches/delete_spec.rb
new file mode 100644
index 0000000..63878c5
--- /dev/null
+++ b/spec/features/projects/branches/delete_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+feature 'Delete branch', feature: true, js: true do
+  include WaitForAjax
+
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+
+  before do
+    project.team << [user, :master]
+    login_as user
+    visit namespace_project_branches_path(project.namespace, project)
+  end
+
+  it 'destroys tooltip' do
+    first('.remove-row').hover
+    expect(page).to have_selector('.tooltip')
+
+    first('.remove-row').click
+    wait_for_ajax
+
+    expect(page).not_to have_selector('.tooltip')
+  end
+end
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
new file mode 100644
index 0000000..92028c1
--- /dev/null
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+feature 'Download buttons in branches page', feature: true do
+  given(:user) { create(:user) }
+  given(:role) { :developer }
+  given(:status) { 'success' }
+  given(:project) { create(:project) }
+
+  given(:pipeline) do
+    create(:ci_pipeline,
+           project: project,
+           sha: project.commit('binary-encoding').sha,
+           ref: 'binary-encoding', # make sure the branch is in the 1st page!
+           status: status)
+  end
+
+  given!(:build) do
+    create(:ci_build, :success, :artifacts,
+           pipeline: pipeline,
+           status: pipeline.status,
+           name: 'build')
+  end
+
+  background do
+    login_as(user)
+    project.team << [user, role]
+  end
+
+  describe 'when checking branches' do
+    context 'with artifacts' do
+      before do
+        visit namespace_project_branches_path(project.namespace, project)
+      end
+
+      scenario 'shows download artifacts button' do
+        href = latest_succeeded_namespace_project_artifacts_path(
+          project.namespace, project, 'binary-encoding/download',
+          job: 'build')
+
+        expect(page).to have_link "Download '#{build.name}'", href: href
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 79abba2..d26a0ca 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -1,32 +1,46 @@
 require 'spec_helper'
 
 describe 'Branches', feature: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :public) }
   let(:repository) { project.repository }
 
-  before do
-    login_as :user
-    project.team << [@user, :developer]
-  end
+  context 'logged in' do
+    before do
+      login_as :user
+      project.team << [@user, :developer]
+    end
 
-  describe 'Initial branches page' do
-    it 'shows all the branches' do
-      visit namespace_project_branches_path(project.namespace, project)
+    describe 'Initial branches page' do
+      it 'shows all the branches' do
+        visit namespace_project_branches_path(project.namespace, project)
 
-      repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
-      expect(page).to have_content("Protected branches can be managed in project settings")
+        repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
+        expect(page).to have_content("Protected branches can be managed in project settings")
+      end
     end
-  end
 
-  describe 'Find branches' do
-    it 'shows filtered branches', js: true do
-      visit namespace_project_branches_path(project.namespace, project, project.id)
+    describe 'Find branches' do
+      it 'shows filtered branches', js: true do
+        visit namespace_project_branches_path(project.namespace, project)
+
+        fill_in 'branch-search', with: 'fix'
+        find('#branch-search').native.send_keys(:enter)
 
-      fill_in 'branch-search', with: 'fix'
-      find('#branch-search').native.send_keys(:enter)
+        expect(page).to have_content('fix')
+        expect(find('.all-branches')).to have_selector('li', count: 1)
+      end
+    end
+  end
+
+  context 'logged out' do
+    before do
+      visit namespace_project_branches_path(project.namespace, project)
+    end
 
-      expect(page).to have_content('fix')
-      expect(find('.all-branches')).to have_selector('li', count: 1)
+    it 'does not show merge request button' do
+      page.within first('.all-branches li') do
+        expect(page).not_to have_content 'Merge Request'
+      end
     end
   end
 end
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
new file mode 100644
index 0000000..d1685f9
--- /dev/null
+++ b/spec/features/projects/builds_spec.rb
@@ -0,0 +1,412 @@
+require 'spec_helper'
+require 'tempfile'
+
+describe "Builds" do
+  let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+
+  before do
+    login_as(:user)
+    @commit = FactoryGirl.create :ci_pipeline
+    @build = FactoryGirl.create :ci_build, :trace, pipeline: @commit
+    @build2 = FactoryGirl.create :ci_build
+    @project = @commit.project
+    @project.team << [@user, :developer]
+  end
+
+  describe "GET /:project/builds" do
+    context "Pending scope" do
+      before do
+        visit namespace_project_builds_path(@project.namespace, @project, scope: :pending)
+      end
+
+      it "shows Pending tab builds" do
+        expect(page).to have_link 'Cancel running'
+        expect(page).to have_selector('.nav-links li.active', text: 'Pending')
+        expect(page).to have_content @build.short_sha
+        expect(page).to have_content @build.ref
+        expect(page).to have_content @build.name
+      end
+    end
+
+    context "Running scope" do
+      before do
+        @build.run!
+        visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
+      end
+
+      it "shows Running tab builds" do
+        expect(page).to have_selector('.nav-links li.active', text: 'Running')
+        expect(page).to have_link 'Cancel running'
+        expect(page).to have_content @build.short_sha
+        expect(page).to have_content @build.ref
+        expect(page).to have_content @build.name
+      end
+    end
+
+    context "Finished scope" do
+      before do
+        @build.run!
+        visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
+      end
+
+      it "shows Finished tab builds" do
+        expect(page).to have_selector('.nav-links li.active', text: 'Finished')
+        expect(page).to have_content 'No builds to show'
+        expect(page).to have_link 'Cancel running'
+      end
+    end
+
+    context "All builds" do
+      before do
+        @project.builds.running_or_pending.each(&:success)
+        visit namespace_project_builds_path(@project.namespace, @project)
+      end
+
+      it "shows All tab builds" do
+        expect(page).to have_selector('.nav-links li.active', text: 'All')
+        expect(page).to have_content @build.short_sha
+        expect(page).to have_content @build.ref
+        expect(page).to have_content @build.name
+        expect(page).not_to have_link 'Cancel running'
+      end
+    end
+  end
+
+  describe "POST /:project/builds/:id/cancel_all" do
+    before do
+      @build.run!
+      visit namespace_project_builds_path(@project.namespace, @project)
+      click_link "Cancel running"
+    end
+
+    it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
+    it { expect(page).to have_content 'canceled' }
+    it { expect(page).to have_content @build.short_sha }
+    it { expect(page).to have_content @build.ref }
+    it { expect(page).to have_content @build.name }
+    it { expect(page).not_to have_link 'Cancel running' }
+  end
+
+  describe "GET /:project/builds/:id" do
+    context "Build from project" do
+      before do
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+      end
+
+      it { expect(page.status_code).to eq(200) }
+      it { expect(page).to have_content @commit.sha[0..7] }
+      it { expect(page).to have_content @commit.git_commit_message }
+      it { expect(page).to have_content @commit.git_author_name }
+    end
+
+    context "Build from other project" do
+      before do
+        visit namespace_project_build_path(@project.namespace, @project, @build2)
+      end
+
+      it { expect(page.status_code).to eq(404) }
+    end
+
+    context "Download artifacts" do
+      before do
+        @build.update_attributes(artifacts_file: artifacts_file)
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+      end
+
+      it 'has button to download artifacts' do
+        expect(page).to have_content 'Download'
+      end
+    end
+
+    context 'Artifacts expire date' do
+      before do
+        @build.update_attributes(artifacts_file: artifacts_file, artifacts_expire_at: expire_at)
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+      end
+
+      context 'no expire date defined' do
+        let(:expire_at) { nil }
+
+        it 'does not have the Keep button' do
+          expect(page).not_to have_content 'Keep'
+        end
+      end
+
+      context 'when expire date is defined' do
+        let(:expire_at) { Time.now + 7.days }
+
+        it 'keeps artifacts when Keep button is clicked' do
+          expect(page).to have_content 'The artifacts will be removed'
+          click_link 'Keep'
+
+          expect(page).not_to have_link 'Keep'
+          expect(page).not_to have_content 'The artifacts will be removed'
+        end
+      end
+
+      context 'when artifacts expired' do
+        let(:expire_at) { Time.now - 7.days }
+
+        it 'does not have the Keep button' do
+          expect(page).to have_content 'The artifacts were removed'
+          expect(page).not_to have_link 'Keep'
+        end
+      end
+    end
+
+    context 'Build raw trace' do
+      before do
+        @build.run!
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+      end
+
+      it do
+        expect(page).to have_link 'Raw'
+      end
+    end
+
+    describe 'Variables' do
+      before do
+        @trigger_request = create :ci_trigger_request_with_variables 
+        @build = create :ci_build, pipeline: @commit, trigger_request: @trigger_request
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+      end
+
+      it 'shows variable key and value after click', js: true do
+        expect(page).to have_css('.reveal-variables')
+        expect(page).not_to have_css('.js-build-variable')
+        expect(page).not_to have_css('.js-build-value')
+     
+        click_button 'Reveal Variables'
+
+        expect(page).not_to have_css('.reveal-variables')
+        expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
+        expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
+      end
+    end    
+  end
+
+  describe "POST /:project/builds/:id/cancel" do
+    context "Build from project" do
+      before do
+        @build.run!
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+        click_link "Cancel"
+      end
+
+      it { expect(page.status_code).to eq(200) }
+      it { expect(page).to have_content 'canceled' }
+      it { expect(page).to have_content 'Retry' }
+    end
+
+    context "Build from other project" do
+      before do
+        @build.run!
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+        page.driver.post(cancel_namespace_project_build_path(@project.namespace, @project, @build2))
+      end
+
+      it { expect(page.status_code).to eq(404) }
+    end
+  end
+
+  describe "POST /:project/builds/:id/retry" do
+    context "Build from project" do
+      before do
+        @build.run!
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+        click_link 'Cancel'
+        click_link 'Retry'
+      end
+
+      it 'shows the right status and buttons' do
+        expect(page).to have_http_status(200)
+        expect(page).to have_content 'pending'
+        page.within('aside.right-sidebar') do
+          expect(page).to have_content 'Cancel'
+        end
+      end
+    end
+
+    context "Build from other project" do
+      before do
+        @build.run!
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+        click_link 'Cancel'
+        page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2))
+      end
+
+      it { expect(page).to have_http_status(404) }
+    end
+
+    context "Build that current user is not allowed to retry" do
+      before do
+        @build.run!
+        @build.cancel!
+        @project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+        logout_direct
+        login_with(create(:user))
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+      end
+
+      it 'does not show the Retry button' do
+        page.within('aside.right-sidebar') do
+          expect(page).not_to have_content 'Retry'
+        end
+      end
+    end
+  end
+
+  describe "GET /:project/builds/:id/download" do
+    before do
+      @build.update_attributes(artifacts_file: artifacts_file)
+      visit namespace_project_build_path(@project.namespace, @project, @build)
+      click_link 'Download'
+    end
+
+    context "Build from other project" do
+      before do
+        @build2.update_attributes(artifacts_file: artifacts_file)
+        visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2)
+      end
+
+      it { expect(page.status_code).to eq(404) }
+    end
+  end
+
+  describe 'GET /:project/builds/:id/raw' do
+    context 'access source' do
+      context 'build from project' do
+        before do
+          Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+          @build.run!
+          visit namespace_project_build_path(@project.namespace, @project, @build)
+          page.within('.js-build-sidebar') { click_link 'Raw' }
+        end
+
+        it 'sends the right headers' do
+          expect(page.status_code).to eq(200)
+          expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
+          expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace)
+        end
+      end
+
+      context 'build from other project' do
+        before do
+          Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+          @build2.run!
+          visit raw_namespace_project_build_path(@project.namespace, @project, @build2)
+        end
+
+        it 'sends the right headers' do
+          expect(page.status_code).to eq(404)
+        end
+      end
+    end
+
+    context 'storage form' do
+      let(:existing_file) { Tempfile.new('existing-trace-file').path }
+      let(:non_existing_file) do
+        file = Tempfile.new('non-existing-trace-file')
+        path = file.path
+        file.unlink
+        path
+      end
+
+      context 'when build has trace in file' do
+        before do
+          Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+          @build.run!
+          visit namespace_project_build_path(@project.namespace, @project, @build)
+
+          allow_any_instance_of(Project).to receive(:ci_id).and_return(nil)
+          allow_any_instance_of(Ci::Build).to receive(:path_to_trace).and_return(existing_file)
+          allow_any_instance_of(Ci::Build).to receive(:old_path_to_trace).and_return(non_existing_file)
+
+          page.within('.js-build-sidebar') { click_link 'Raw' }
+        end
+
+        it 'sends the right headers' do
+          expect(page.status_code).to eq(200)
+          expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
+          expect(page.response_headers['X-Sendfile']).to eq(existing_file)
+        end
+      end
+
+      context 'when build has trace in old file' do
+        before do
+          Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+          @build.run!
+          visit namespace_project_build_path(@project.namespace, @project, @build)
+
+          allow_any_instance_of(Project).to receive(:ci_id).and_return(999)
+          allow_any_instance_of(Ci::Build).to receive(:path_to_trace).and_return(non_existing_file)
+          allow_any_instance_of(Ci::Build).to receive(:old_path_to_trace).and_return(existing_file)
+
+          page.within('.js-build-sidebar') { click_link 'Raw' }
+        end
+
+        it 'sends the right headers' do
+          expect(page.status_code).to eq(200)
+          expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
+          expect(page.response_headers['X-Sendfile']).to eq(existing_file)
+        end
+      end
+
+      context 'when build has trace in DB' do
+        before do
+          Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+          @build.run!
+          visit namespace_project_build_path(@project.namespace, @project, @build)
+
+          allow_any_instance_of(Project).to receive(:ci_id).and_return(nil)
+          allow_any_instance_of(Ci::Build).to receive(:path_to_trace).and_return(non_existing_file)
+          allow_any_instance_of(Ci::Build).to receive(:old_path_to_trace).and_return(non_existing_file)
+
+          page.within('.js-build-sidebar') { click_link 'Raw' }
+        end
+
+        it 'sends the right headers' do
+          expect(page.status_code).to eq(404)
+        end
+      end
+    end
+  end
+
+  describe "GET /:project/builds/:id/trace.json" do
+    context "Build from project" do
+      before do
+        visit trace_namespace_project_build_path(@project.namespace, @project, @build, format: :json)
+      end
+
+      it { expect(page.status_code).to eq(200) }
+    end
+
+    context "Build from other project" do
+      before do
+        visit trace_namespace_project_build_path(@project.namespace, @project, @build2, format: :json)
+      end
+
+      it { expect(page.status_code).to eq(404) }
+    end
+  end
+
+  describe "GET /:project/builds/:id/status" do
+    context "Build from project" do
+      before do
+        visit status_namespace_project_build_path(@project.namespace, @project, @build)
+      end
+
+      it { expect(page.status_code).to eq(200) }
+    end
+
+    context "Build from other project" do
+      before do
+        visit status_namespace_project_build_path(@project.namespace, @project, @build2)
+      end
+
+      it { expect(page.status_code).to eq(404) }
+    end
+  end
+end
diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb
index 1b4ff6b..e45e3a3 100644
--- a/spec/features/projects/commits/cherry_pick_spec.rb
+++ b/spec/features/projects/commits/cherry_pick_spec.rb
@@ -1,4 +1,5 @@
 require 'spec_helper'
+include WaitForAjax
 
 describe 'Cherry-pick Commits' do
   let(:project) { create(:project) }
@@ -8,12 +9,11 @@ describe 'Cherry-pick Commits' do
   before do
     login_as :user
     project.team << [@user, :master]
-    visit namespace_project_commits_path(project.namespace, project, project.repository.root_ref, { limit: 5 })
+    visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
   end
 
   context "I cherry-pick a commit" do
     it do
-      visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
       find("a[href='#modal-cherry-pick-commit']").click
       expect(page).not_to have_content('v1.0.0') # Only branches, not tags
       page.within('#modal-cherry-pick-commit') do
@@ -26,7 +26,6 @@ describe 'Cherry-pick Commits' do
 
   context "I cherry-pick a merge commit" do
     it do
-      visit namespace_project_commit_path(project.namespace, project, master_pickable_merge.id)
       find("a[href='#modal-cherry-pick-commit']").click
       page.within('#modal-cherry-pick-commit') do
         uncheck 'create_merge_request'
@@ -38,7 +37,6 @@ describe 'Cherry-pick Commits' do
 
   context "I cherry-pick a commit that was previously cherry-picked" do
     it do
-      visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
       find("a[href='#modal-cherry-pick-commit']").click
       page.within('#modal-cherry-pick-commit') do
         uncheck 'create_merge_request'
@@ -56,7 +54,6 @@ describe 'Cherry-pick Commits' do
 
   context "I cherry-pick a commit in a new merge request" do
     it do
-      visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
       find("a[href='#modal-cherry-pick-commit']").click
       page.within('#modal-cherry-pick-commit') do
         click_button 'Cherry-pick'
@@ -64,4 +61,28 @@ describe 'Cherry-pick Commits' do
       expect(page).to have_content('The commit has been successfully cherry-picked. You can now submit a merge request to get this change into the original branch.')
     end
   end
+
+  context "I cherry-pick a commit from a different branch", js: true do
+    it do
+      find('.commit-action-buttons a.dropdown-toggle').click
+      find(:css, "a[href='#modal-cherry-pick-commit']").click
+
+      page.within('#modal-cherry-pick-commit') do
+        click_button 'master'
+      end
+
+      wait_for_ajax
+
+      page.within('#modal-cherry-pick-commit .dropdown-menu .dropdown-content') do
+        click_link 'feature'
+      end
+
+      page.within('#modal-cherry-pick-commit') do
+        uncheck 'create_merge_request'
+        click_button 'Cherry-pick'
+      end
+
+      expect(page).to have_content('The commit has been successfully cherry-picked.')
+    end
+  end
 end
diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb
new file mode 100644
index 0000000..a1643fd
--- /dev/null
+++ b/spec/features/projects/edit_spec.rb
@@ -0,0 +1,57 @@
+require 'rails_helper'
+
+feature 'Project edit', feature: true, js: true do
+  include WaitForAjax
+
+  let(:user)    { create(:user) }
+  let(:project) { create(:project) }
+
+  before do
+    project.team << [user, :master]
+    login_as(user)
+
+    visit edit_namespace_project_path(project.namespace, project)
+  end
+
+  context 'feature visibility' do
+    context 'merge requests select' do
+      it 'hides merge requests section' do
+        select('Disabled', from: 'project_project_feature_attributes_merge_requests_access_level')
+
+        expect(page).to have_selector('.merge-requests-feature', visible: false)
+      end
+
+      it 'hides merge requests section after save' do
+        select('Disabled', from: 'project_project_feature_attributes_merge_requests_access_level')
+
+        expect(page).to have_selector('.merge-requests-feature', visible: false)
+
+        click_button 'Save changes'
+
+        wait_for_ajax
+
+        expect(page).to have_selector('.merge-requests-feature', visible: false)
+      end
+    end
+
+    context 'builds select' do
+      it 'hides merge requests section' do
+        select('Disabled', from: 'project_project_feature_attributes_builds_access_level')
+
+        expect(page).to have_selector('.builds-feature', visible: false)
+      end
+
+      it 'hides merge requests section after save' do
+        select('Disabled', from: 'project_project_feature_attributes_builds_access_level')
+
+        expect(page).to have_selector('.builds-feature', visible: false)
+
+        click_button 'Save changes'
+
+        wait_for_ajax
+
+        expect(page).to have_selector('.builds-feature', visible: false)
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
new file mode 100644
index 0000000..9b487e3
--- /dev/null
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -0,0 +1,122 @@
+require 'spec_helper'
+include WaitForAjax
+
+describe 'Edit Project Settings', feature: true do
+  let(:member) { create(:user) }
+  let!(:project) { create(:project, :public, path: 'gitlab', name: 'sample') }
+  let(:non_member) { create(:user) }
+
+  describe 'project features visibility selectors', js: true do
+    before do
+      project.team << [member, :master]
+      login_as(member)
+    end
+
+    tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests" }
+
+    tools.each do |tool_name, shortcut_name|
+      describe "feature #{tool_name}" do
+        it 'toggles visibility' do
+          visit edit_namespace_project_path(project.namespace, project)
+
+          select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level"
+          click_button 'Save changes'
+          wait_for_ajax
+          expect(page).not_to have_selector(".shortcuts-#{shortcut_name}")
+
+          select 'Everyone with access', from: "project_project_feature_attributes_#{tool_name}_access_level"
+          click_button 'Save changes'
+          wait_for_ajax
+          expect(page).to have_selector(".shortcuts-#{shortcut_name}")
+
+          select 'Only team members', from: "project_project_feature_attributes_#{tool_name}_access_level"
+          click_button 'Save changes'
+          wait_for_ajax
+          expect(page).to have_selector(".shortcuts-#{shortcut_name}")
+
+          sleep 0.1
+        end
+      end
+    end
+  end
+
+  describe 'project features visibility pages' do
+    before do
+      @tools =
+        {
+          builds: namespace_project_pipelines_path(project.namespace, project),
+          issues: namespace_project_issues_path(project.namespace, project),
+          wiki: namespace_project_wiki_path(project.namespace, project, :home),
+          snippets: namespace_project_snippets_path(project.namespace, project),
+          merge_requests: namespace_project_merge_requests_path(project.namespace, project),
+        }
+    end
+
+    context 'normal user' do
+      it 'renders 200 if tool is enabled' do
+        @tools.each do |method_name, url|
+          project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::ENABLED)
+          visit url
+          expect(page.status_code).to eq(200)
+        end
+      end
+
+      it 'renders 404 if feature is disabled' do
+        @tools.each do |method_name, url|
+          project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED)
+          visit url
+          expect(page.status_code).to eq(404)
+        end
+      end
+
+      it 'renders 404 if feature is enabled only for team members' do
+        project.team.truncate
+
+        @tools.each do |method_name, url|
+          project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE)
+          visit url
+          expect(page.status_code).to eq(404)
+        end
+      end
+
+      it 'renders 200 if users is member of group' do
+        group = create(:group)
+        project.group = group
+        project.save
+
+        group.add_owner(member)
+
+        @tools.each do |method_name, url|
+          project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE)
+          visit url
+          expect(page.status_code).to eq(200)
+        end
+      end
+    end
+
+    context 'admin user' do
+      before do
+        non_member.update_attribute(:admin, true)
+        login_as(non_member)
+      end
+
+      it 'renders 404 if feature is disabled' do
+        @tools.each do |method_name, url|
+          project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED)
+          visit url
+          expect(page.status_code).to eq(404)
+        end
+      end
+
+      it 'renders 200 if feature is enabled only for team members' do
+        project.team.truncate
+
+        @tools.each do |method_name, url|
+          project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE)
+          visit url
+          expect(page.status_code).to eq(200)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
new file mode 100644
index 0000000..d7c29a7
--- /dev/null
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+feature 'Download buttons in files tree', feature: true do
+  given(:user) { create(:user) }
+  given(:role) { :developer }
+  given(:status) { 'success' }
+  given(:project) { create(:project) }
+
+  given(:pipeline) do
+    create(:ci_pipeline,
+           project: project,
+           sha: project.commit.sha,
+           ref: project.default_branch,
+           status: status)
+  end
+
+  given!(:build) do
+    create(:ci_build, :success, :artifacts,
+           pipeline: pipeline,
+           status: pipeline.status,
+           name: 'build')
+  end
+
+  background do
+    login_as(user)
+    project.team << [user, role]
+  end
+
+  describe 'when files tree' do
+    context 'with artifacts' do
+      before do
+        visit namespace_project_tree_path(
+          project.namespace, project, project.default_branch)
+      end
+
+      scenario 'shows download artifacts button' do
+        href = latest_succeeded_namespace_project_artifacts_path(
+          project.namespace, project, "#{project.default_branch}/download",
+          job: 'build')
+
+        expect(page).to have_link "Download '#{build.name}'", href: href
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index dbd0746..a521ce5 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -23,7 +23,7 @@ feature 'project owner creates a license file', feature: true, js: true do
 
     select_template('MIT License')
 
-    file_content = find('.file-content')
+    file_content = first('.file-editor')
     expect(file_content).to have_content('The MIT License (MIT)')
     expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
 
@@ -47,7 +47,7 @@ feature 'project owner creates a license file', feature: true, js: true do
 
     select_template('MIT License')
 
-    file_content = find('.file-content')
+    file_content = first('.file-editor')
     expect(file_content).to have_content('The MIT License (MIT)')
     expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
 
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 45bf0c0..4453b6d 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -23,7 +23,7 @@ feature 'project owner sees a link to create a license file in empty project', f
 
     select_template('MIT License')
 
-    file_content = find('.file-content')
+    file_content = first('.file-editor')
     expect(file_content).to have_content('The MIT License (MIT)')
     expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
 
diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb
new file mode 100644
index 0000000..1921ea6
--- /dev/null
+++ b/spec/features/projects/gfm_autocomplete_load_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe 'GFM autocomplete loading', feature: true, js: true do
+  let(:project)   { create(:project) }
+
+  before do
+    login_as :admin
+
+    visit namespace_project_path(project.namespace, project)
+  end
+
+  it 'does not load on project#show' do
+    expect(evaluate_script('GitLab.GfmAutoComplete.dataSource')).to eq('')
+  end
+
+  it 'loads on new issue page' do
+    visit new_namespace_project_issue_path(project.namespace, project)
+
+    expect(evaluate_script('GitLab.GfmAutoComplete.dataSource')).not_to eq('')
+  end
+end
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
new file mode 100644
index 0000000..27c986c
--- /dev/null
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+# Integration test that exports a file using the Import/Export feature
+# It looks up for any sensitive word inside the JSON, so if a sensitive word is found
+# we''l have to either include it adding the model that includes it to the +safe_list+
+# or make sure the attribute is blacklisted in the +import_export.yml+ configuration
+feature 'Import/Export - project export integration test', feature: true, js: true do
+  include Select2Helper
+  include ExportFileHelper
+
+  let(:user) { create(:admin) }
+  let(:export_path) { "#{Dir::tmpdir}/import_file_spec" }
+  let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
+
+  let(:sensitive_words) { %w[pass secret token key] }
+  let(:safe_list) do
+    {
+      token: [ProjectHook, Ci::Trigger, CommitStatus],
+      key: [Project, Ci::Variable, :yaml_variables]
+    }
+  end
+  let(:safe_hashes) { { yaml_variables: %w[key value public] } }
+
+  let(:project) { setup_project }
+
+  background do
+    allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+  end
+
+  after do
+    FileUtils.rm_rf(export_path, secure: true)
+  end
+
+  context 'admin user' do
+    before do
+      login_as(user)
+    end
+
+    scenario 'exports a project successfully' do
+      visit edit_namespace_project_path(project.namespace, project)
+
+      expect(page).to have_content('Export project')
+
+      click_link 'Export project'
+
+      visit edit_namespace_project_path(project.namespace, project)
+
+      expect(page).to have_content('Download export')
+
+      in_directory_with_expanded_export(project) do |exit_status, tmpdir|
+        expect(exit_status).to eq(0)
+
+        project_json_path = File.join(tmpdir, 'project.json')
+        expect(File).to exist(project_json_path)
+
+        project_hash = JSON.parse(IO.read(project_json_path))
+
+        sensitive_words.each do |sensitive_word|
+          found = find_sensitive_attributes(sensitive_word, project_hash)
+
+          expect(found).to be_nil, failure_message(found.try(:key_found), found.try(:parent), sensitive_word)
+        end
+      end
+    end
+
+    def failure_message(key_found, parent, sensitive_word)
+      <<-MSG
+        Found a new sensitive word <#{key_found}>, which is part of the hash #{parent.inspect}
+
+        If you think this information shouldn't get exported, please exclude the model or attribute in IMPORT_EXPORT_CONFIG.
+
+        Otherwise, please add the exception to +safe_list+ in CURRENT_SPEC using #{sensitive_word} as the key and the
+        correspondent hash or model as the value.
+
+        IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+        CURRENT_SPEC: #{__FILE__}
+      MSG
+    end
+  end
+end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index f707ccf..09cd636 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-feature 'project import', feature: true, js: true do
+feature 'Import/Export - project import integration test', feature: true, js: true do
   include Select2Helper
 
   let(:admin) { create(:admin) }
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 7bb0d26..d04bdea 100644
Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index 4a83740..f76c4fe 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -13,10 +13,12 @@ feature 'issuable templates', feature: true, js: true do
 
   context 'user creates an issue using templates' do
     let(:template_content) { 'this is a test "bug" template' }
+    let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) }
     let(:issue) { create(:issue, author: user, assignee: user, project: project) }
 
     background do
       project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false)
+      project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false)
       visit edit_namespace_project_issue_path project.namespace, project, issue
       fill_in :'issue[title]', with: 'test issue title'
     end
@@ -27,6 +29,17 @@ feature 'issuable templates', feature: true, js: true do
       preview_template
       save_changes
     end
+
+    it 'updates height of markdown textarea' do
+      start_height = page.evaluate_script('$(".markdown-area").outerHeight()')
+
+      select_template 'test'
+      wait_for_ajax
+
+      end_height = page.evaluate_script('$(".markdown-area").outerHeight()')
+      
+      expect(end_height).not_to eq(start_height)
+    end
   end
 
   context 'user creates a merge request using templates' do
@@ -51,7 +64,7 @@ feature 'issuable templates', feature: true, js: true do
     let(:template_content) { 'this is a test "feature-proposal" template' }
     let(:fork_user) { create(:user) }
     let(:fork_project) { create(:project, :public) }
-    let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project) }
+    let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) }
 
     background do
       logout
@@ -59,16 +72,20 @@ feature 'issuable templates', feature: true, js: true do
       fork_project.team << [fork_user, :master]
       create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
       login_as fork_user
-      fork_project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false)
-      visit edit_namespace_project_merge_request_path fork_project.namespace, fork_project, merge_request
+      project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false)
+      visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
       fill_in :'merge_request[title]', with: 'test merge request title'
     end
 
-    scenario 'user selects "feature-proposal" template' do
-      select_template 'feature-proposal'
-      wait_for_ajax
-      preview_template
-      save_changes
+    context 'feature proposal template' do
+      context 'template exists in target project' do
+        scenario 'user selects template' do
+          select_template 'feature-proposal'
+          wait_for_ajax
+          preview_template
+          save_changes
+        end
+      end
     end
   end
 
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
new file mode 100644
index 0000000..227ccf9
--- /dev/null
+++ b/spec/features/projects/main/download_buttons_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+feature 'Download buttons in project main page', feature: true do
+  given(:user) { create(:user) }
+  given(:role) { :developer }
+  given(:status) { 'success' }
+  given(:project) { create(:project) }
+
+  given(:pipeline) do
+    create(:ci_pipeline,
+           project: project,
+           sha: project.commit.sha,
+           ref: project.default_branch,
+           status: status)
+  end
+
+  given!(:build) do
+    create(:ci_build, :success, :artifacts,
+           pipeline: pipeline,
+           status: pipeline.status,
+           name: 'build')
+  end
+
+  background do
+    login_as(user)
+    project.team << [user, role]
+  end
+
+  describe 'when checking project main page' do
+    context 'with artifacts' do
+      before do
+        visit namespace_project_path(project.namespace, project)
+      end
+
+      scenario 'shows download artifacts button' do
+        href = latest_succeeded_namespace_project_artifacts_path(
+          project.namespace, project, "#{project.default_branch}/download",
+          job: 'build')
+
+        expect(page).to have_link "Download '#{build.name}'", href: href
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
new file mode 100644
index 0000000..dd93d25
--- /dev/null
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+feature 'Download buttons in tags page', feature: true do
+  given(:user) { create(:user) }
+  given(:role) { :developer }
+  given(:status) { 'success' }
+  given(:tag) { 'v1.0.0' }
+  given(:project) { create(:project) }
+
+  given(:pipeline) do
+    create(:ci_pipeline,
+           project: project,
+           sha: project.commit(tag).sha,
+           ref: tag,
+           status: status)
+  end
+
+  given!(:build) do
+    create(:ci_build, :success, :artifacts,
+           pipeline: pipeline,
+           status: pipeline.status,
+           name: 'build')
+  end
+
+  background do
+    login_as(user)
+    project.team << [user, role]
+  end
+
+  describe 'when checking tags' do
+    context 'with artifacts' do
+      before do
+        visit namespace_project_tags_path(project.namespace, project)
+      end
+
+      scenario 'shows download artifacts button' do
+        href = latest_succeeded_namespace_project_artifacts_path(
+          project.namespace, project, "#{tag}/download",
+          job: 'build')
+
+        expect(page).to have_link "Download '#{build.name}'", href: href
+      end
+    end
+  end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index e00d859..2242cb6 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -57,7 +57,7 @@ feature 'Project', feature: true do
 
   describe 'removal', js: true do
     let(:user)    { create(:user) }
-    let(:project) { create(:project, namespace: user.namespace) }
+    let(:project) { create(:project, namespace: user.namespace, name: 'project1') }
 
     before do
       login_with(user)
@@ -65,8 +65,12 @@ feature 'Project', feature: true do
       visit edit_namespace_project_path(project.namespace, project)
     end
 
-    it 'removes project' do
+    it 'removes a project' do
       expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1)
+      expect(page).to have_content "Project 'project1' will be deleted."
+      expect(Project.all.count).to be_zero
+      expect(project.issues).to be_empty
+      expect(project.merge_requests).to be_empty
     end
   end
 
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index dcd3a2f..1806200 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe "Search", feature: true  do
+  include WaitForAjax
+
   let(:user) { create(:user) }
   let(:project) { create(:project, namespace: user.namespace) }
   let!(:issue) { create(:issue, project: project, assignee: user) }
@@ -16,6 +18,36 @@ describe "Search", feature: true  do
     expect(page).not_to have_selector('.search')
   end
 
+  context 'search filters', js: true do
+    let(:group) { create(:group) }
+
+    before do
+      group.add_owner(user)
+    end
+
+    it 'shows group name after filtering' do
+      find('.js-search-group-dropdown').click
+      wait_for_ajax
+
+      page.within '.search-holder' do
+        click_link group.name
+      end
+
+      expect(find('.js-search-group-dropdown')).to have_content(group.name)
+    end
+
+    it 'shows project name after filtering' do
+      page.within('.project-filter') do
+        find('.js-search-project-dropdown').click
+        wait_for_ajax
+
+        click_link project.name_with_namespace
+      end
+
+      expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace)
+    end
+  end
+
   describe 'searching for Projects' do
     it 'finds a project' do
       page.within '.search-holder' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 6ed279e..abb27c9 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -20,6 +20,22 @@ feature 'Task Lists', feature: true do
     MARKDOWN
   end
 
+  let(:singleIncompleteMarkdown) do
+    <<-MARKDOWN.strip_heredoc
+    This is a task list:
+
+    - [ ] Incomplete entry 1
+    MARKDOWN
+  end
+
+  let(:singleCompleteMarkdown) do
+    <<-MARKDOWN.strip_heredoc
+    This is a task list:
+
+    - [x] Incomplete entry 1
+    MARKDOWN
+  end
+
   before do
     Warden.test_mode!
 
@@ -34,77 +50,145 @@ feature 'Task Lists', feature: true do
   end
 
   describe 'for Issues' do
-    let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
+    describe 'multiple tasks' do
+      let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
 
-    it 'renders' do
-      visit_issue(project, issue)
+      it 'renders' do
+        visit_issue(project, issue)
 
-      expect(page).to have_selector('ul.task-list',      count: 1)
-      expect(page).to have_selector('li.task-list-item', count: 6)
-      expect(page).to have_selector('ul input[checked]', count: 2)
-    end
+        expect(page).to have_selector('ul.task-list',      count: 1)
+        expect(page).to have_selector('li.task-list-item', count: 6)
+        expect(page).to have_selector('ul input[checked]', count: 2)
+      end
+
+      it 'contains the required selectors' do
+        visit_issue(project, issue)
+
+        container = '.detail-page-description .description.js-task-list-container'
 
-    it 'contains the required selectors' do
-      visit_issue(project, issue)
+        expect(page).to have_selector(container)
+        expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
+        expect(page).to have_selector("#{container} .js-task-list-field")
+        expect(page).to have_selector('form.js-issuable-update')
+        expect(page).to have_selector('a.btn-close')
+      end
 
-      container = '.detail-page-description .description.js-task-list-container'
+      it 'is only editable by author' do
+        visit_issue(project, issue)
+        expect(page).to have_selector('.js-task-list-container')
 
-      expect(page).to have_selector(container)
-      expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
-      expect(page).to have_selector("#{container} .js-task-list-field")
-      expect(page).to have_selector('form.js-issuable-update')
-      expect(page).to have_selector('a.btn-close')
+        logout(:user)
+
+        login_as(user2)
+        visit current_path
+        expect(page).not_to have_selector('.js-task-list-container')
+      end
+
+      it 'provides a summary on Issues#index' do
+        visit namespace_project_issues_path(project.namespace, project)
+        expect(page).to have_content("2 of 6 tasks completed")
+      end
     end
 
-    it 'is only editable by author' do
-      visit_issue(project, issue)
-      expect(page).to have_selector('.js-task-list-container')
+    describe 'single incomplete task' do
+      let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) }
 
-      logout(:user)
+      it 'renders' do
+        visit_issue(project, issue)
 
-      login_as(user2)
-      visit current_path
-      expect(page).not_to have_selector('.js-task-list-container')
+        expect(page).to have_selector('ul.task-list',      count: 1)
+        expect(page).to have_selector('li.task-list-item', count: 1)
+        expect(page).to have_selector('ul input[checked]', count: 0)
+      end
+
+      it 'provides a summary on Issues#index' do
+        visit namespace_project_issues_path(project.namespace, project)
+        expect(page).to have_content("0 of 1 task completed")
+      end
     end
 
-    it 'provides a summary on Issues#index' do
-      visit namespace_project_issues_path(project.namespace, project)
-      expect(page).to have_content("6 tasks (2 completed, 4 remaining)")
+    describe 'single complete task' do
+      let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) }
+
+      it 'renders' do
+        visit_issue(project, issue)
+
+        expect(page).to have_selector('ul.task-list',      count: 1)
+        expect(page).to have_selector('li.task-list-item', count: 1)
+        expect(page).to have_selector('ul input[checked]', count: 1)
+      end
+
+      it 'provides a summary on Issues#index' do
+        visit namespace_project_issues_path(project.namespace, project)
+        expect(page).to have_content("1 of 1 task completed")
+      end
     end
   end
 
   describe 'for Notes' do
     let!(:issue) { create(:issue, author: user, project: project) }
-    let!(:note) do
-      create(:note, note: markdown, noteable: issue,
-                    project: project, author: user)
+    describe 'multiple tasks' do
+      let!(:note) do
+        create(:note, note: markdown, noteable: issue,
+                      project: project, author: user)
+      end
+
+      it 'renders for note body' do
+        visit_issue(project, issue)
+
+        expect(page).to have_selector('.note ul.task-list',      count: 1)
+        expect(page).to have_selector('.note li.task-list-item', count: 6)
+        expect(page).to have_selector('.note ul input[checked]', count: 2)
+      end
+
+      it 'contains the required selectors' do
+        visit_issue(project, issue)
+
+        expect(page).to have_selector('.note .js-task-list-container')
+        expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox')
+        expect(page).to have_selector('.note .js-task-list-container .js-task-list-field')
+      end
+
+      it 'is only editable by author' do
+        visit_issue(project, issue)
+        expect(page).to have_selector('.js-task-list-container')
+
+        logout(:user)
+
+        login_as(user2)
+        visit current_path
+        expect(page).not_to have_selector('.js-task-list-container')
+      end
     end
 
-    it 'renders for note body' do
-      visit_issue(project, issue)
-
-      expect(page).to have_selector('.note ul.task-list',      count: 1)
-      expect(page).to have_selector('.note li.task-list-item', count: 6)
-      expect(page).to have_selector('.note ul input[checked]', count: 2)
-    end
+    describe 'single incomplete task' do
+      let!(:note) do
+        create(:note, note: singleIncompleteMarkdown, noteable: issue,
+                      project: project, author: user)
+      end
 
-    it 'contains the required selectors' do
-      visit_issue(project, issue)
+      it 'renders for note body' do
+        visit_issue(project, issue)
 
-      expect(page).to have_selector('.note .js-task-list-container')
-      expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox')
-      expect(page).to have_selector('.note .js-task-list-container .js-task-list-field')
+        expect(page).to have_selector('.note ul.task-list',      count: 1)
+        expect(page).to have_selector('.note li.task-list-item', count: 1)
+        expect(page).to have_selector('.note ul input[checked]', count: 0)
+      end
     end
 
-    it 'is only editable by author' do
-      visit_issue(project, issue)
-      expect(page).to have_selector('.js-task-list-container')
+    describe 'single complete task' do
+      let!(:note) do
+        create(:note, note: singleCompleteMarkdown, noteable: issue,
+                      project: project, author: user)
+      end
 
-      logout(:user)
+      it 'renders for note body' do
+        visit_issue(project, issue)
 
-      login_as(user2)
-      visit current_path
-      expect(page).not_to have_selector('.js-task-list-container')
+        expect(page).to have_selector('.note ul.task-list',      count: 1)
+        expect(page).to have_selector('.note li.task-list-item', count: 1)
+        expect(page).to have_selector('.note ul input[checked]', count: 1)
+      end
     end
   end
 
@@ -113,42 +197,78 @@ feature 'Task Lists', feature: true do
       visit namespace_project_merge_request_path(project.namespace, project, merge)
     end
 
-    let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
+    describe 'multiple tasks' do
+      let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
 
-    it 'renders for description' do
-      visit_merge_request(project, merge)
+      it 'renders for description' do
+        visit_merge_request(project, merge)
 
-      expect(page).to have_selector('ul.task-list',      count: 1)
-      expect(page).to have_selector('li.task-list-item', count: 6)
-      expect(page).to have_selector('ul input[checked]', count: 2)
-    end
+        expect(page).to have_selector('ul.task-list',      count: 1)
+        expect(page).to have_selector('li.task-list-item', count: 6)
+        expect(page).to have_selector('ul input[checked]', count: 2)
+      end
 
-    it 'contains the required selectors' do
-      visit_merge_request(project, merge)
+      it 'contains the required selectors' do
+        visit_merge_request(project, merge)
 
-      container = '.detail-page-description .description.js-task-list-container'
+        container = '.detail-page-description .description.js-task-list-container'
 
-      expect(page).to have_selector(container)
-      expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
-      expect(page).to have_selector("#{container} .js-task-list-field")
-      expect(page).to have_selector('form.js-issuable-update')
-      expect(page).to have_selector('a.btn-close')
-    end
+        expect(page).to have_selector(container)
+        expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
+        expect(page).to have_selector("#{container} .js-task-list-field")
+        expect(page).to have_selector('form.js-issuable-update')
+        expect(page).to have_selector('a.btn-close')
+      end
 
-    it 'is only editable by author' do
-      visit_merge_request(project, merge)
-      expect(page).to have_selector('.js-task-list-container')
+      it 'is only editable by author' do
+        visit_merge_request(project, merge)
+        expect(page).to have_selector('.js-task-list-container')
 
-      logout(:user)
+        logout(:user)
 
-      login_as(user2)
-      visit current_path
-      expect(page).not_to have_selector('.js-task-list-container')
+        login_as(user2)
+        visit current_path
+        expect(page).not_to have_selector('.js-task-list-container')
+      end
+
+      it 'provides a summary on MergeRequests#index' do
+        visit namespace_project_merge_requests_path(project.namespace, project)
+        expect(page).to have_content("2 of 6 tasks completed")
+      end
+    end
+    
+    describe 'single incomplete task' do
+      let!(:merge) { create(:merge_request, :simple, description: singleIncompleteMarkdown, author: user, source_project: project) }
+
+      it 'renders for description' do
+        visit_merge_request(project, merge)
+
+        expect(page).to have_selector('ul.task-list',      count: 1)
+        expect(page).to have_selector('li.task-list-item', count: 1)
+        expect(page).to have_selector('ul input[checked]', count: 0)
+      end
+
+      it 'provides a summary on MergeRequests#index' do
+        visit namespace_project_merge_requests_path(project.namespace, project)
+        expect(page).to have_content("0 of 1 task completed")
+      end
     end
 
-    it 'provides a summary on MergeRequests#index' do
-      visit namespace_project_merge_requests_path(project.namespace, project)
-      expect(page).to have_content("6 tasks (2 completed, 4 remaining)")
+    describe 'single complete task' do
+      let!(:merge) { create(:merge_request, :simple, description: singleCompleteMarkdown, author: user, source_project: project) }
+
+      it 'renders for description' do
+        visit_merge_request(project, merge)
+
+        expect(page).to have_selector('ul.task-list',      count: 1)
+        expect(page).to have_selector('li.task-list-item', count: 1)
+        expect(page).to have_selector('ul input[checked]', count: 1)
+      end
+
+      it 'provides a summary on MergeRequests#index' do
+        visit namespace_project_merge_requests_path(project.namespace, project)
+        expect(page).to have_content("1 of 1 task completed")
+      end
     end
   end
 end
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb
new file mode 100644
index 0000000..b9e6624
--- /dev/null
+++ b/spec/features/todos/todos_filtering_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe 'Dashboard > User filters todos', feature: true, js: true do
+  include WaitForAjax
+
+  let(:user_1)    { create(:user, username: 'user_1', name: 'user_1') }
+  let(:user_2)    { create(:user, username: 'user_2', name: 'user_2') }
+
+  let(:project_1) { create(:empty_project, name: 'project_1') }
+  let(:project_2) { create(:empty_project, name: 'project_2') }
+
+  let(:issue) { create(:issue, title: 'issue', project: project_1) }
+
+  let!(:merge_request) { create(:merge_request, source_project: project_2, title: 'merge_request') }
+
+  before do
+    create(:todo, user: user_1, author: user_2, project: project_1, target: issue, action: 1)
+    create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2)
+
+    project_1.team << [user_1, :developer]
+    project_2.team << [user_1, :developer]
+    login_as(user_1)
+    visit dashboard_todos_path
+  end
+
+  it 'filters by project' do
+    click_button 'Project'
+    within '.dropdown-menu-project' do
+      fill_in 'Search projects', with: project_1.name_with_namespace
+      click_link project_1.name_with_namespace
+    end
+
+    wait_for_ajax
+
+    expect(page).to     have_content project_1.name_with_namespace
+    expect(page).not_to have_content project_2.name_with_namespace
+  end
+
+  it 'filters by author' do
+    click_button 'Author'
+    within '.dropdown-menu-author' do
+      fill_in 'Search authors', with: user_1.name
+      click_link user_1.name
+    end
+
+    wait_for_ajax
+
+    expect(find('.todos-list')).to     have_content user_1.name
+    expect(find('.todos-list')).not_to have_content user_2.name
+  end
+
+  it 'filters by type' do
+    click_button 'Type'
+    within '.dropdown-menu-type' do
+      click_link 'Issue'
+    end
+
+    wait_for_ajax
+
+    expect(find('.todos-list')).to     have_content issue.to_reference
+    expect(find('.todos-list')).not_to have_content merge_request.to_reference
+  end
+
+  it 'filters by action' do
+    click_button 'Action'
+    within '.dropdown-menu-action' do
+      click_link 'Assigned'
+    end
+
+    wait_for_ajax
+
+    expect(find('.todos-list')).to     have_content ' assigned you '
+    expect(find('.todos-list')).not_to have_content ' mentioned '
+  end
+end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 0342f4f..fc555a7 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -41,6 +41,27 @@ describe 'Dashboard Todos', feature: true do
           expect(page).to have_content("You're all done!")
         end
       end
+
+      context 'todo is stale on the page' do
+        before do
+          todos = TodosFinder.new(user, state: :pending).execute
+          TodoService.new.mark_todos_as_done(todos, user)
+        end
+
+        describe 'deleting the todo' do
+          before do
+            first('.done-todo').click
+          end
+
+          it 'is removed from the list' do
+            expect(page).not_to have_selector('.todos-list .todo')
+          end
+
+          it 'shows "All done" message' do
+            expect(page).to have_content("You're all done!")
+          end
+        end
+      end
     end
 
     context 'User has Todos with labels spanning multiple projects' do
@@ -97,6 +118,20 @@ describe 'Dashboard Todos', feature: true do
           expect(page).to have_css("#todo_#{Todo.first.id}")
         end
       end
+
+      describe 'mark all as done', js: true do
+        before do
+          visit dashboard_todos_path
+          click_link('Mark all as done')
+        end
+
+        it 'shows "All done" message!' do
+          within('.todos-pending-count') { expect(page).to have_content '0' }
+          expect(page).to have_content 'To do 0'
+          expect(page).to have_content "You're all done!"
+          expect(page).not_to have_selector('.gl-pagination')
+        end
+      end
     end
 
     context 'User has a Todo in a project pending deletion' do
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 3cbc825..7235483 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -12,7 +12,7 @@ describe 'Triggers' do
 
   context 'create a trigger' do
     before do
-      click_on 'Add Trigger'
+      click_on 'Add trigger'
       expect(@project.triggers.count).to eq(1)
     end
 
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index a46e48c..ff6933d 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -156,6 +156,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
 
     describe "when 2FA via OTP is disabled" do
       it "allows logging in with the U2F device" do
+        user.update_attribute(:otp_required_for_login, false)
         login_with(user)
 
         @u2f_device.respond_to_u2f_authentication
@@ -181,6 +182,19 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
       end
     end
 
+    it 'persists remember_me value via hidden field' do
+      login_with(user, remember: true)
+
+      @u2f_device.respond_to_u2f_authentication
+      click_on "Login Via U2F Device"
+      expect(page.body).to match('We heard back from your U2F device')
+
+      within 'div#js-authenticate-u2f' do
+        field = first('input#user_remember_me', visible: false)
+        expect(field.value).to eq '1'
+      end
+    end
+
     describe "when a given U2F device has already been registered by another user" do
       describe "but not the current user" do
         it "does not allow logging in with that particular device" do
diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb
new file mode 100644
index 0000000..cc40671
--- /dev/null
+++ b/spec/features/unsubscribe_links_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe 'Unsubscribe links', feature: true do
+  include Warden::Test::Helpers
+
+  let(:recipient) { create(:user) }
+  let(:author) { create(:user) }
+  let(:project) { create(:empty_project, :public) }
+  let(:params) { { title: 'A bug!', description: 'Fix it!', assignee: recipient } }
+  let(:issue) { Issues::CreateService.new(project, author, params).execute }
+
+  let(:mail) { ActionMailer::Base.deliveries.last }
+  let(:body) { Capybara::Node::Simple.new(mail.default_part_body.to_s) }
+  let(:header_link) { mail.header['List-Unsubscribe'] }
+  let(:body_link) { body.find_link('unsubscribe')['href'] }
+
+  before do
+    perform_enqueued_jobs { issue }
+  end
+
+  context 'when logged out' do
+    context 'when visiting the link from the body' do
+      it 'shows the unsubscribe confirmation page and redirects to root path when confirming' do
+        visit body_link
+
+        expect(current_path).to eq unsubscribe_sent_notification_path(SentNotification.last)
+        expect(page).to have_text(%(Unsubscribe from issue #{issue.title} (#{issue.to_reference})))
+        expect(page).to have_text(%(Are you sure you want to unsubscribe from issue #{issue.title} (#{issue.to_reference})?))
+        expect(issue.subscribed?(recipient)).to be_truthy
+
+        click_link 'Unsubscribe'
+
+        expect(issue.subscribed?(recipient)).to be_falsey
+        expect(current_path).to eq new_user_session_path
+      end
+
+      it 'shows the unsubscribe confirmation page and redirects to root path when canceling' do
+        visit body_link
+
+        expect(current_path).to eq unsubscribe_sent_notification_path(SentNotification.last)
+        expect(issue.subscribed?(recipient)).to be_truthy
+
+        click_link 'Cancel'
+
+        expect(issue.subscribed?(recipient)).to be_truthy
+        expect(current_path).to eq new_user_session_path
+      end
+    end
+
+    it 'unsubscribes from the issue when visiting the link from the header' do
+      visit header_link
+
+      expect(page).to have_text('unsubscribed')
+      expect(issue.subscribed?(recipient)).to be_falsey
+    end
+  end
+
+  context 'when logged in' do
+    before { login_as(recipient) }
+
+    it 'unsubscribes from the issue when visiting the link from the email body' do
+      visit body_link
+
+      expect(page).to have_text('unsubscribed')
+      expect(issue.subscribed?(recipient)).to be_falsey
+    end
+
+    it 'unsubscribes from the issue when visiting the link from the header' do
+      visit header_link
+
+      expect(page).to have_text('unsubscribed')
+      expect(issue.subscribed?(recipient)).to be_falsey
+    end
+  end
+end
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
new file mode 100644
index 0000000..f00abd8
--- /dev/null
+++ b/spec/features/users/snippets_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'Snippets tab on a user profile', feature: true, js: true do
+  include WaitForAjax
+
+  let(:user) { create(:user) }
+
+  context 'when the user has snippets' do
+    before do
+      create_list(:snippet, 25, :public, author: user)
+
+      visit user_path(user)
+      page.within('.user-profile-nav') { click_link 'Snippets' }
+      wait_for_ajax
+    end
+
+    it 'is limited to 20 items per page' do
+      expect(page.all('.snippets-list-holder .snippet-row').count).to eq(20)
+    end
+
+    context 'clicking on the link to the second page' do
+      before do
+        click_link('2')
+        wait_for_ajax
+      end
+
+      it 'shows the remaining snippets' do
+        expect(page.all('.snippets-list-holder .snippet-row').count).to eq(5)
+      end
+    end
+  end
+end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index ec8809e..40bccb8 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -7,8 +7,8 @@ describe IssuesFinder do
   let(:project2) { create(:empty_project) }
   let(:milestone) { create(:milestone, project: project1) }
   let(:label) { create(:label, project: project2) }
-  let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) }
-  let(:issue2) { create(:issue, author: user, assignee: user, project: project2) }
+  let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone, title: 'gitlab') }
+  let(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'gitlab') }
   let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) }
   let!(:label_link) { create(:label_link, label: label, target: issue2) }
 
@@ -127,6 +127,22 @@ describe IssuesFinder do
         end
       end
 
+      context 'filtering by issue term' do
+        let(:params) { { search: 'git' } }
+
+        it 'returns issues with title and description match for search term' do
+          expect(issues).to contain_exactly(issue1, issue2)
+        end
+      end
+
+      context 'filtering by issue iid' do
+        let(:params) { { search: issue3.to_reference } }
+
+        it 'returns issue with iid match' do
+          expect(issues).to contain_exactly(issue3)
+        end
+      end
+
       context 'when the user is unauthorized' do
         let(:search_user) { nil }
 
diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb
index 4f3304f..fdce4e7 100644
--- a/spec/finders/move_to_project_finder_spec.rb
+++ b/spec/finders/move_to_project_finder_spec.rb
@@ -51,6 +51,28 @@ describe MoveToProjectFinder do
 
         expect(subject.execute(project).to_a).to eq([other_reporter_project])
       end
+
+      it 'returns a page of projects ordered by id in descending order' do
+        stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
+
+        reporter_project.team << [user, :reporter]
+        developer_project.team << [user, :developer]
+        master_project.team << [user, :master]
+
+        expect(subject.execute(project).to_a).to eq([master_project, developer_project])
+      end
+
+      it 'returns projects after the given offset id' do
+        stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
+
+        reporter_project.team << [user, :reporter]
+        developer_project.team << [user, :developer]
+        master_project.team << [user, :master]
+
+        expect(subject.execute(project, search: nil, offset_id: master_project.id).to_a).to eq([developer_project, reporter_project])
+        expect(subject.execute(project, search: nil, offset_id: developer_project.id).to_a).to eq([reporter_project])
+        expect(subject.execute(project, search: nil, offset_id: reporter_project.id).to_a).to be_empty
+      end
     end
 
     context 'search' do
diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb
new file mode 100644
index 0000000..b0811d1
--- /dev/null
+++ b/spec/finders/pipelines_finder_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe PipelinesFinder do
+  let(:project) { create(:project) }
+
+  let!(:tag_pipeline)    { create(:ci_pipeline, project: project, ref: 'v1.0.0') }
+  let!(:branch_pipeline) { create(:ci_pipeline, project: project) }
+
+  subject { described_class.new(project).execute(params) }
+
+  describe "#execute" do
+    context 'when a scope is passed' do
+      context 'when scope is nil' do
+        let(:params) { { scope: nil } }
+
+        it 'selects all pipelines' do
+          expect(subject.count).to be 2
+          expect(subject).to include tag_pipeline
+          expect(subject).to include branch_pipeline
+        end
+      end
+
+      context 'when selecting branches' do
+        let(:params) { { scope: 'branches' } }
+
+        it 'excludes tags' do
+          expect(subject).not_to include tag_pipeline
+          expect(subject).to     include branch_pipeline
+        end
+      end
+
+      context 'when selecting tags' do
+        let(:params) { { scope: 'tags' } }
+
+        it 'excludes branches' do
+          expect(subject).to     include tag_pipeline
+          expect(subject).not_to include branch_pipeline
+        end
+      end
+    end
+
+    # Scoping to running will speed up the test as it doesn't hit the FS
+    let(:params) { { scope: 'running' } }
+
+    it 'orders in descending order on ID' do
+      feature_pipeline = create(:ci_pipeline, project: project, ref: 'feature')
+
+      expected_ids = [feature_pipeline.id, branch_pipeline.id, tag_pipeline.id].sort.reverse
+      expect(subject.map(&:id)).to eq expected_ids
+    end
+  end
+end
diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb
new file mode 100644
index 0000000..2ac810e
--- /dev/null
+++ b/spec/finders/tags_finder_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe TagsFinder do
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+  let(:repository) { project.repository }
+
+  describe '#execute' do
+    context 'sort only' do
+      it 'sorts by name' do
+        tags_finder = described_class.new(repository, {})
+
+        result = tags_finder.execute
+
+        expect(result.first.name).to eq("v1.0.0")
+      end
+
+      it 'sorts by recently_updated' do
+        tags_finder = described_class.new(repository, { sort: 'updated_desc' })
+
+        result = tags_finder.execute
+        recently_updated_tag = repository.tags.max do |a, b|
+          repository.commit(a.target).committed_date <=> repository.commit(b.target).committed_date
+        end
+
+        expect(result.first.name).to eq(recently_updated_tag.name)
+      end
+
+      it 'sorts by last_updated' do
+        tags_finder = described_class.new(repository, { sort: 'updated_asc' })
+
+        result = tags_finder.execute
+
+        expect(result.first.name).to eq('v1.0.0')
+      end
+    end
+
+    context 'filter only' do
+      it 'filters tags by name' do
+        tags_finder = described_class.new(repository, { search: '1.0.0' })
+
+        result = tags_finder.execute
+
+        expect(result.first.name).to eq('v1.0.0')
+        expect(result.count).to eq(1)
+      end
+
+      it 'does not find any tags with that name' do
+        tags_finder = described_class.new(repository, { search: 'hey' })
+
+        result = tags_finder.execute
+
+        expect(result.count).to eq(0)
+      end
+    end
+
+    context 'filter and sort' do
+      it 'filters tags by name and sorts by recently_updated' do
+        params = { sort: 'updated_desc', search: 'v1' }
+        tags_finder = described_class.new(repository, params)
+
+        result = tags_finder.execute
+
+        expect(result.first.name).to eq('v1.1.0')
+        expect(result.count).to eq(2)
+      end
+
+      it 'filters tags by name and sorts by last_updated' do
+        params = { sort: 'updated_asc', search: 'v1' }
+        tags_finder = described_class.new(repository, params)
+
+        result = tags_finder.execute
+
+        expect(result.first.name).to eq('v1.0.0')
+        expect(result.count).to eq(2)
+      end
+    end
+  end
+end
diff --git a/spec/fixtures/api/schemas/issues.json b/spec/fixtures/api/schemas/issues.json
index 0d2067f..70771b2 100644
--- a/spec/fixtures/api/schemas/issues.json
+++ b/spec/fixtures/api/schemas/issues.json
@@ -1,4 +1,15 @@
 {
-  "type": "array",
-  "items": { "$ref": "issue.json" }
+  "type": "object",
+  "required" : [
+    "issues",
+    "size"
+  ],
+  "properties" : {
+    "issues": {
+      "type": "array",
+      "items": { "$ref": "issue.json" }
+    },
+    "size": { "type": "integer" }
+  },
+  "additionalProperties": false
 }
diff --git a/spec/helpers/git_helper_spec.rb b/spec/helpers/git_helper_spec.rb
new file mode 100644
index 0000000..9b1ef1e
--- /dev/null
+++ b/spec/helpers/git_helper_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+describe GitHelper do
+  describe '#short_sha' do
+    let(:short_sha) { helper.short_sha('d4e043f6c20749a3ab3f4b8e23f2a8979f4b9100') }
+
+    it { expect(short_sha).to eq('d4e043f6') }
+  end
+end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 0807534..233d005 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -18,4 +18,67 @@ describe GroupsHelper do
       expect(group_icon(group.path)).to match('group_avatar.png')
     end
   end
+
+  describe 'group_lfs_status' do
+    let(:group) { create(:group) }
+    let!(:project) { create(:empty_project, namespace_id: group.id) }
+
+    before do
+      allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+    end
+
+    context 'only one project in group' do
+      before do
+        group.update_attribute(:lfs_enabled, true)
+      end
+
+      it 'returns all projects as enabled' do
+        expect(group_lfs_status(group)).to include('Enabled for all projects')
+      end
+
+      it 'returns all projects as disabled' do
+        project.update_attribute(:lfs_enabled, false)
+
+        expect(group_lfs_status(group)).to include('Enabled for 0 out of 1 project')
+      end
+    end
+
+    context 'more than one project in group' do
+      before do
+        create(:empty_project, namespace_id: group.id)
+      end
+
+      context 'LFS enabled in group' do
+        before do
+          group.update_attribute(:lfs_enabled, true)
+        end
+
+        it 'returns both projects as enabled' do
+          expect(group_lfs_status(group)).to include('Enabled for all projects')
+        end
+
+        it 'returns only one as enabled' do
+          project.update_attribute(:lfs_enabled, false)
+
+          expect(group_lfs_status(group)).to include('Enabled for 1 out of 2 projects')
+        end
+      end
+
+      context 'LFS disabled in group' do
+        before do
+          group.update_attribute(:lfs_enabled, false)
+        end
+
+        it 'returns both projects as disabled' do
+          expect(group_lfs_status(group)).to include('Disabled for all projects')
+        end
+
+        it 'returns only one as disabled' do
+          project.update_attribute(:lfs_enabled, true)
+
+          expect(group_lfs_status(group)).to include('Disabled for 1 out of 2 projects')
+        end
+      end
+    end
+  end
 end
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index 3391234..187b891 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -1,6 +1,30 @@
 require 'rails_helper'
 
 describe ImportHelper do
+  describe '#import_project_target' do
+    let(:user) { create(:user) }
+
+    before do
+      allow(helper).to receive(:current_user).and_return(user)
+    end
+
+    context 'when current user can create namespaces' do
+      it 'returns project namespace' do
+        user.update_attribute(:can_create_group, true)
+
+        expect(helper.import_project_target('asd', 'vim')).to eq 'asd/vim'
+      end
+    end
+
+    context 'when current user can not create namespaces' do
+      it "takes the current user's namespace" do
+        user.update_attribute(:can_create_group, false)
+
+        expect(helper.import_project_target('asd', 'vim')).to eq "#{user.namespace_path}/vim"
+      end
+    end
+  end
+
   describe '#github_project_link' do
     context 'when provider does not specify a custom URL' do
       it 'uses default GitHub URL' do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 5e4655d..67bac78 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -62,6 +62,32 @@ describe IssuesHelper do
     it { is_expected.to eq("!1, !2, or !3") }
   end
 
+  describe '#award_user_list' do
+    let!(:awards) { build_list(:award_emoji, 15) }
+
+    it "returns a comma seperated list of 1-9 users" do
+      expect(award_user_list(awards.first(9), nil)).to eq(awards.first(9).map { |a| a.user.name }.to_sentence)
+    end
+
+    it "displays the current user's name as 'You'" do
+      expect(award_user_list(awards.first(1), awards[0].user)).to eq('You')
+    end
+
+    it "truncates lists of larger than 9 users" do
+      expect(award_user_list(awards, nil)).to eq(awards.first(9).map { |a| a.user.name }.join(', ') + ", and 6 more.")
+    end
+
+    it "displays the current user in front of 0-9 other users" do
+      expect(award_user_list(awards, awards[0].user)).
+        to eq("You, " + awards[1..9].map { |a| a.user.name }.join(', ') + ", and 5 more.")
+    end
+
+    it "displays the current user in front regardless of position in the list" do
+      expect(award_user_list(awards, awards[12].user)).
+        to eq("You, " + awards[0..8].map { |a| a.user.name }.join(', ') + ", and 5 more.")
+    end
+  end
+
   describe '#award_active_class' do
     let!(:upvote) { create(:award_emoji) }
 
diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb
deleted file mode 100644
index e4d18d8..0000000
--- a/spec/helpers/nav_helper_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'spec_helper'
-
-# Specs in this file have access to a helper object that includes
-# the NavHelper. For example:
-#
-# describe NavHelper do
-#   describe "string concat" do
-#     it "concats two strings with spaces" do
-#       expect(helper.concat_strings("this","that")).to eq("this that")
-#     end
-#   end
-# end
-describe NavHelper do
-  describe '#nav_menu_collapsed?' do
-    it 'returns true when the nav is collapsed in the cookie' do
-      helper.request.cookies[:collapsed_nav] = 'true'
-      expect(helper.nav_menu_collapsed?).to eq true
-    end
-
-    it 'returns false when the nav is not collapsed in the cookie' do
-      helper.request.cookies[:collapsed_nav] = 'false'
-      expect(helper.nav_menu_collapsed?).to eq false
-    end
-  end
-end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 284b58d..70032e7 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -174,4 +174,48 @@ describe ProjectsHelper do
       end
     end
   end
+
+  describe "#project_feature_access_select" do
+    let(:project) { create(:empty_project, :public) }
+    let(:user)    { create(:user) }
+
+    context "when project is internal or public" do
+      it "shows all options" do
+        helper.instance_variable_set(:@project, project)
+        result = helper.project_feature_access_select(:issues_access_level)
+        expect(result).to include("Disabled")
+        expect(result).to include("Only team members")
+        expect(result).to include("Everyone with access")
+      end
+    end
+
+    context "when project is private" do
+      before { project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
+
+      it "shows only allowed options" do
+        helper.instance_variable_set(:@project, project)
+        result = helper.project_feature_access_select(:issues_access_level)
+        expect(result).to include("Disabled")
+        expect(result).to include("Only team members")
+        expect(result).not_to include("Everyone with access")
+      end
+    end
+
+    context "when project moves from public to private" do
+      before do
+        project.project_feature.update_attributes(issues_access_level: ProjectFeature::ENABLED)
+        project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+      end
+
+      it "shows the highest allowed level selected" do
+        helper.instance_variable_set(:@project, project)
+        result = helper.project_feature_access_select(:issues_access_level)
+
+        expect(result).to include("Disabled")
+        expect(result).to include("Only team members")
+        expect(result).not_to include("Everyone with access")
+        expect(result).to have_selector('option[selected]', text: "Only team members")
+      end
+    end
+  end
 end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index b0bb991..c5b5aa8 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -6,6 +6,38 @@ describe SearchHelper do
     str
   end
 
+  describe 'parsing result' do
+    let(:project) { create(:project) }
+    let(:repository) { project.repository }
+    let(:results) { repository.search_files('feature', 'master') }
+    let(:search_result) { results.first }
+
+    subject { helper.parse_search_result(search_result) }
+
+    it "returns a valid OpenStruct object" do
+      is_expected.to be_an OpenStruct
+      expect(subject.filename).to eq('CHANGELOG')
+      expect(subject.basename).to eq('CHANGELOG')
+      expect(subject.ref).to eq('master')
+      expect(subject.startline).to eq(186)
+      expect(subject.data.lines[2]).to eq("  - Feature: Replace teams with group membership\n")
+    end
+
+    context "when filename has extension" do
+      let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
+
+      it { expect(subject.filename).to eq('CONTRIBUTE.md') }
+      it { expect(subject.basename).to eq('CONTRIBUTE') }
+    end
+
+    context "when file under directory" do
+      let(:search_result) { "master:a/b/c.md:5:a b c\n" }
+
+      it { expect(subject.filename).to eq('a/b/c.md') }
+      it { expect(subject.basename).to eq('a/b/c') }
+    end
+  end
+
   describe 'search_autocomplete_source' do
     context "with no current user" do
       before do
@@ -32,6 +64,10 @@ describe SearchHelper do
         expect(search_autocomplete_opts("adm").size).to eq(1)
       end
 
+      it "does not allow regular expression in search term" do
+        expect(search_autocomplete_opts("(webhooks|api)").size).to eq(0)
+      end
+
       it "includes the user's groups" do
         create(:group).add_owner(user)
         expect(search_autocomplete_opts("gro").size).to eq(1)
diff --git a/spec/helpers/sidekiq_helper_spec.rb b/spec/helpers/sidekiq_helper_spec.rb
new file mode 100644
index 0000000..d60839b
--- /dev/null
+++ b/spec/helpers/sidekiq_helper_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe SidekiqHelper do
+  describe 'parse_sidekiq_ps' do
+    it 'parses line with time' do
+      line = '55137	10,0	2,1	S+	2:30pm	sidekiq 4.1.4 gitlab [0 of 25 busy]   '
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['55137', '10,0', '2,1', 'S+', '2:30pm', 'sidekiq 4.1.4 gitlab [0 of 25 busy]'])
+    end
+
+    it 'parses line with date' do
+      line = '55137	10,0	2,1	S+	Aug 4	sidekiq 4.1.4 gitlab [0 of 25 busy]   '
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['55137', '10,0', '2,1', 'S+', 'Aug 4', 'sidekiq 4.1.4 gitlab [0 of 25 busy]'])
+    end
+
+    it 'parses line with two digit date' do
+      line = '55137	10,0	2,1	S+	Aug 04	sidekiq 4.1.4 gitlab [0 of 25 busy]   '
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['55137', '10,0', '2,1', 'S+', 'Aug 04', 'sidekiq 4.1.4 gitlab [0 of 25 busy]'])
+    end
+
+    it 'parses line with dot as float separator' do
+      line = '55137	10.0	2.1	S+	2:30pm	sidekiq 4.1.4 gitlab [0 of 25 busy]   '
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['55137', '10.0', '2.1', 'S+', '2:30pm', 'sidekiq 4.1.4 gitlab [0 of 25 busy]'])
+    end
+
+    it 'does fail gracefully on line not matching the format' do
+      line = '55137	10.0	2.1	S+	2:30pm	something'
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['?', '?', '?', '?', '?', '?'])
+    end
+  end
+end
diff --git a/spec/javascripts/abuse_reports_spec.js.es6 b/spec/javascripts/abuse_reports_spec.js.es6
new file mode 100644
index 0000000..6bcfdf1
--- /dev/null
+++ b/spec/javascripts/abuse_reports_spec.js.es6
@@ -0,0 +1,41 @@
+/*= require abuse_reports */
+
+/*= require jquery */
+
+((global) => {
+  const FIXTURE = 'abuse_reports.html';
+  const MAX_MESSAGE_LENGTH = 500;
+
+  function assertMaxLength($message) {
+    expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH);
+  }
+
+  describe('Abuse Reports', function() {
+    fixture.preload(FIXTURE);
+
+    beforeEach(function() {
+      fixture.load(FIXTURE);
+      new global.AbuseReports();
+    });
+
+    it('should truncate long messages', function() {
+      const $longMessage = $('#long');
+      expect($longMessage.data('original-message')).toEqual(jasmine.anything());
+      assertMaxLength($longMessage);
+    });
+
+    it('should not truncate short messages', function() {
+      const $shortMessage = $('#short');
+      expect($shortMessage.data('original-message')).not.toEqual(jasmine.anything());
+    });
+
+    it('should allow clicking a truncated message to expand and collapse the full message', function() {
+      const $longMessage = $('#long');
+      $longMessage.click();
+      expect($longMessage.data('original-message').length).toEqual($longMessage.text().length);
+      $longMessage.click();
+      assertMaxLength($longMessage);
+    });
+  });
+
+})(window.gl);
diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js
index b48026c..56b9885 100644
--- a/spec/javascripts/application_spec.js
+++ b/spec/javascripts/application_spec.js
@@ -13,17 +13,21 @@
         gl.utils.preventDisabledButtons();
         isClicked = false;
         $button = $('#test-button');
+        expect($button).toExist();
         $button.click(function() {
           return isClicked = true;
         });
         $button.trigger('click');
         return expect(isClicked).toBe(false);
       });
-      return it('should be on the same page if a disabled link clicked', function() {
-        var locationBeforeLinkClick;
+
+      it('should be on the same page if a disabled link clicked', function() {
+        var locationBeforeLinkClick, $link;
         locationBeforeLinkClick = window.location.href;
         gl.utils.preventDisabledButtons();
-        $('#test-link').click();
+        $link = $('#test-link');
+        expect($link).toExist();
+        $link.click();
         return expect(window.location.href).toBe(locationBeforeLinkClick);
       });
     });
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 3ddc163..019ce3b 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,17 +1,11 @@
 
 /*= require awards_handler */
-
-
 /*= require jquery */
-
-
 /*= require jquery.cookie */
-
-
 /*= require ./fixtures/emoji_menu */
 
 (function() {
-  var awardsHandler, lazyAssert;
+  var awardsHandler, lazyAssert, urlRoot;
 
   awardsHandler = null;
 
@@ -27,11 +21,13 @@
   };
 
   gon.award_menu_url = '/emojis';
+  urlRoot = gon.relative_url_root;
 
   lazyAssert = function(done, assertFn) {
     return setTimeout(function() {
       assertFn();
       return done();
+    // Maybe jasmine.clock here?
     }, 333);
   };
 
@@ -45,9 +41,14 @@
           return cb();
         };
       })(this));
-      return spyOn(jQuery, 'get').and.callFake(function(req, cb) {
+      spyOn(jQuery, 'get').and.callFake(function(req, cb) {
         return cb(window.emojiMenu);
       });
+      spyOn(jQuery, 'cookie');
+    });
+    afterEach(function() {
+      // restore original url root value
+      gon.relative_url_root = urlRoot;
     });
     describe('::showEmojiMenu', function() {
       it('should show emoji menu when Add emoji button clicked', function(done) {
@@ -143,6 +144,74 @@
         return expect($votesBlock.find('[data-emoji=fire]').length).toBe(0);
       });
     });
+    describe('::addYouToUserList', function() {
+      it('should prepend "You" to the award tooltip', function() {
+        var $thumbsUpEmoji, $votesBlock, awardUrl;
+        awardUrl = awardsHandler.getAwardUrl();
+        $votesBlock = $('.js-awards-block').eq(0);
+        $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+        $thumbsUpEmoji.attr('data-title', 'sam, jerry, max, and andy');
+        awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
+        $thumbsUpEmoji.tooltip();
+        return expect($thumbsUpEmoji.data("original-title")).toBe('You, sam, jerry, max, and andy');
+      });
+      return it('handles the special case where "You" is not cleanly comma seperated', function() {
+        var $thumbsUpEmoji, $votesBlock, awardUrl;
+        awardUrl = awardsHandler.getAwardUrl();
+        $votesBlock = $('.js-awards-block').eq(0);
+        $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+        $thumbsUpEmoji.attr('data-title', 'sam');
+        awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
+        $thumbsUpEmoji.tooltip();
+        return expect($thumbsUpEmoji.data("original-title")).toBe('You and sam');
+      });
+    });
+    describe('::removeYouToUserList', function() {
+      it('removes "You" from the front of the tooltip', function() {
+        var $thumbsUpEmoji, $votesBlock, awardUrl;
+        awardUrl = awardsHandler.getAwardUrl();
+        $votesBlock = $('.js-awards-block').eq(0);
+        $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+        $thumbsUpEmoji.attr('data-title', 'You, sam, jerry, max, and andy');
+        $thumbsUpEmoji.addClass('active');
+        awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
+        $thumbsUpEmoji.tooltip();
+        return expect($thumbsUpEmoji.data("original-title")).toBe('sam, jerry, max, and andy');
+      });
+      return it('handles the special case where "You" is not cleanly comma seperated', function() {
+        var $thumbsUpEmoji, $votesBlock, awardUrl;
+        awardUrl = awardsHandler.getAwardUrl();
+        $votesBlock = $('.js-awards-block').eq(0);
+        $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+        $thumbsUpEmoji.attr('data-title', 'You and sam');
+        $thumbsUpEmoji.addClass('active');
+        awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
+        $thumbsUpEmoji.tooltip();
+        return expect($thumbsUpEmoji.data("original-title")).toBe('sam');
+      });
+    });
+    describe('::addEmojiToFrequentlyUsedList', function() {
+      it('should set a cookie with the correct default path', function() {
+        gon.relative_url_root = '';
+        awardsHandler.addEmojiToFrequentlyUsedList('sunglasses');
+        expect(jQuery.cookie)
+          .toHaveBeenCalledWith('frequently_used_emojis', 'sunglasses', {
+            path: '/',
+            expires: 365
+          })
+        ;
+      });
+      it('should set a cookie with the correct custom root path', function() {
+        gon.relative_url_root = '/gitlab/subdir';
+        awardsHandler.addEmojiToFrequentlyUsedList('alien');
+        expect(jQuery.cookie)
+          .toHaveBeenCalledWith('frequently_used_emojis', 'alien', {
+            path: '/gitlab/subdir',
+            expires: 365
+          })
+        ;
+      });
+    });
     describe('search', function() {
       return it('should filter the emoji', function() {
         $('.js-add-award').eq(0).click();
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index 4c52ecd..13babb5 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -8,6 +8,7 @@
     beforeEach(function() {
       fixture.load('behaviors/quick_submit.html');
       $('form').submit(function(e) {
+        // Prevent a form submit from moving us off the testing page
         return e.preventDefault();
       });
       return this.spies = {
@@ -38,6 +39,8 @@
       expect($('input[type=submit]')).toBeDisabled();
       return expect($('button[type=submit]')).toBeDisabled();
     });
+    // We cannot stub `navigator.userAgent` for CI's `rake teaspoon` task, so we'll
+    // only run the tests that apply to the current platform
     if (navigator.userAgent.match(/Macintosh/)) {
       it('responds to Meta+Enter', function() {
         $('input.quick-submit-input').trigger(keydownEvent());
diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6
index c206b79..1688b99 100644
--- a/spec/javascripts/boards/list_spec.js.es6
+++ b/spec/javascripts/boards/list_spec.js.es6
@@ -60,15 +60,6 @@ describe('List model', () => {
     }, 0);
   });
 
-  it('can\'t search when not backlog', () => {
-    expect(list.canSearch()).toBe(false);
-  });
-
-  it('can search when backlog', () => {
-    list.type = 'backlog';
-    expect(list.canSearch()).toBe(true);
-  });
-
   it('gets issue from list', (done) => {
     setTimeout(() => {
       const issue = list.findIssue(1);
diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6
index 0c37ec8..f3797ed 100644
--- a/spec/javascripts/boards/mock_data.js.es6
+++ b/spec/javascripts/boards/mock_data.js.es6
@@ -26,12 +26,15 @@ const listObjDuplicate = {
 
 const BoardsMockData = {
   'GET': {
-    '/test/issue-boards/board/lists{/id}/issues': [{
-      title: 'Testing',
-      iid: 1,
-      confidential: false,
-      labels: []
-    }]
+    '/test/issue-boards/board/lists{/id}/issues': {
+      issues: [{
+        title: 'Testing',
+        iid: 1,
+        confidential: false,
+        labels: []
+      }],
+      size: 1
+    }
   },
   'POST': {
     '/test/issue-boards/board/lists{/id}': listObj
diff --git a/spec/javascripts/datetime_utility_spec.js.coffee b/spec/javascripts/datetime_utility_spec.js.coffee
deleted file mode 100644
index 6b96173..0000000
--- a/spec/javascripts/datetime_utility_spec.js.coffee
+++ /dev/null
@@ -1,31 +0,0 @@
-#= require lib/utils/datetime_utility
-
-describe 'Date time utils', ->
-  describe 'get day name', ->
-    it 'should return Sunday', ->
-      day = gl.utils.getDayName(new Date('07/17/2016'))
-      expect(day).toBe('Sunday')
-
-    it 'should return Monday', ->
-      day = gl.utils.getDayName(new Date('07/18/2016'))
-      expect(day).toBe('Monday')
-
-    it 'should return Tuesday', ->
-      day = gl.utils.getDayName(new Date('07/19/2016'))
-      expect(day).toBe('Tuesday')
-
-    it 'should return Wednesday', ->
-      day = gl.utils.getDayName(new Date('07/20/2016'))
-      expect(day).toBe('Wednesday')
-
-    it 'should return Thursday', ->
-      day = gl.utils.getDayName(new Date('07/21/2016'))
-      expect(day).toBe('Thursday')
-
-    it 'should return Friday', ->
-      day = gl.utils.getDayName(new Date('07/22/2016'))
-      expect(day).toBe('Friday')
-
-    it 'should return Saturday', ->
-      day = gl.utils.getDayName(new Date('07/23/2016'))
-      expect(day).toBe('Saturday')
diff --git a/spec/javascripts/datetime_utility_spec.js.es6 b/spec/javascripts/datetime_utility_spec.js.es6
new file mode 100644
index 0000000..a2d1b0a
--- /dev/null
+++ b/spec/javascripts/datetime_utility_spec.js.es6
@@ -0,0 +1,64 @@
+//= require lib/utils/datetime_utility
+(() => {
+  describe('Date time utils', () => {
+    describe('get day name', () => {
+      it('should return Sunday', () => {
+        const day = gl.utils.getDayName(new Date('07/17/2016'));
+        expect(day).toBe('Sunday');
+      });
+
+      it('should return Monday', () => {
+        const day = gl.utils.getDayName(new Date('07/18/2016'));
+        expect(day).toBe('Monday');
+      });
+
+      it('should return Tuesday', () => {
+        const day = gl.utils.getDayName(new Date('07/19/2016'));
+        expect(day).toBe('Tuesday');
+      });
+
+      it('should return Wednesday', () => {
+        const day = gl.utils.getDayName(new Date('07/20/2016'));
+        expect(day).toBe('Wednesday');
+      });
+
+      it('should return Thursday', () => {
+        const day = gl.utils.getDayName(new Date('07/21/2016'));
+        expect(day).toBe('Thursday');
+      });
+
+      it('should return Friday', () => {
+        const day = gl.utils.getDayName(new Date('07/22/2016'));
+        expect(day).toBe('Friday');
+      });
+
+      it('should return Saturday', () => {
+        const day = gl.utils.getDayName(new Date('07/23/2016'));
+        expect(day).toBe('Saturday');
+      });
+    });
+
+    describe('get day difference', () => {
+      it('should return 7', () => {
+        const firstDay = new Date('07/01/2016');
+        const secondDay = new Date('07/08/2016');
+        const difference = gl.utils.getDayDifference(firstDay, secondDay);
+        expect(difference).toBe(7);
+      });
+
+      it('should return 31', () => {
+        const firstDay = new Date('07/01/2016');
+        const secondDay = new Date('08/01/2016');
+        const difference = gl.utils.getDayDifference(firstDay, secondDay);
+        expect(difference).toBe(31);
+      });
+
+      it('should return 365', () => {
+        const firstDay = new Date('07/02/2015');
+        const secondDay = new Date('07/01/2016');
+        const difference = gl.utils.getDayDifference(firstDay, secondDay);
+        expect(difference).toBe(365);
+      });
+    });
+  });
+})();
diff --git a/spec/javascripts/fixtures/abuse_reports.html.haml b/spec/javascripts/fixtures/abuse_reports.html.haml
new file mode 100644
index 0000000..2ec302a
--- /dev/null
+++ b/spec/javascripts/fixtures/abuse_reports.html.haml
@@ -0,0 +1,16 @@
+.abuse-reports
+  .message#long
+    Cat ipsum dolor sit amet, hide head under blanket so no one can see.
+    Gate keepers of hell eat and than sleep on your face but hunt by meowing
+    loudly at 5am next to human slave food dispenser cats go for world
+    domination or chase laser, yet poop on grasses chirp at birds. Cat is love,
+    cat is life chase after silly colored fish toys around the house climb a
+    tree, wait for a fireman jump to fireman then scratch his face fall asleep
+    on the washing machine lies down always hungry so caticus cuteicus. Sit on
+    human. Spot something, big eyes, big eyes, crouch, shake butt, prepare to
+    pounce sleep in the bathroom sink hiss at vacuum cleaner hide head under
+    blanket so no one can see throwup on your pillow.
+  .message#short
+    Cat ipsum dolor sit amet, groom yourself 4 hours - checked, have your
+    beauty sleep 18 hours - checked, be fabulous for the rest of the day -
+    checked! for shake treat bag.
diff --git a/spec/javascripts/fixtures/awards_handler.html.haml b/spec/javascripts/fixtures/awards_handler.html.haml
index d55936e..1ef2e8f 100644
--- a/spec/javascripts/fixtures/awards_handler.html.haml
+++ b/spec/javascripts/fixtures/awards_handler.html.haml
@@ -39,7 +39,7 @@
                     %span.note-role Reporter
                     %a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"}
                       %i.fa.fa-spinner.fa-spin
-                      %i.fa.fa-smile-o
+                      %i.fa.fa-smile-o.link-highlight
                 .js-task-list-container.note-body.is-task-list-enabled
                   .note-text
                     %p Suscipit sunt quia quisquam sed eveniet ipsam.
diff --git a/spec/javascripts/fixtures/comments.html.haml b/spec/javascripts/fixtures/comments.html.haml
new file mode 100644
index 0000000..cc1f8f1
--- /dev/null
+++ b/spec/javascripts/fixtures/comments.html.haml
@@ -0,0 +1,21 @@
+.flash-container.timeline-content
+.timeline-icon.hidden-xs.hidden-sm
+  %a.author_link
+    %img
+.timeline-content.timeline-content-form
+  %form.new-note.js-quick-submit.common-note-form.gfm-form.js-main-target-form
+    .md-area
+      .md-header
+      .md-write-holder
+        .zen-backdrop.div-dropzone-wrapper
+          .div-dropzone-wrapper
+            .div-dropzone.dz-clickable
+              %textarea.note-textarea.js-note-text.js-gfm-input.js-autosize.markdown-area
+    .note-form-actions.clearfix
+      %input.btn.btn-nr.btn-create.append-right-10.comment-btn.js-comment-button{ type: 'submit' }
+      %a.btn.btn-nr.btn-reopen.btn-comment.js-note-target-reopen
+        Reopen issue
+      %a.btn.btn-nr.btn-close.btn-comment.js-note-target-close
+        Close issue
+      %a.btn.btn-cancel.js-note-discard
+        Discard draft
\ No newline at end of file
diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json
index 84e8d0b..4919d77 100644
--- a/spec/javascripts/fixtures/projects.json
+++ b/spec/javascripts/fixtures/projects.json
@@ -1 +1 @@
-[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil at localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"te [...]
+[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil at localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"te [...]
diff --git a/spec/javascripts/fixtures/u2f/authenticate.html.haml b/spec/javascripts/fixtures/u2f/authenticate.html.haml
index 859e79a..779d642 100644
--- a/spec/javascripts/fixtures/u2f/authenticate.html.haml
+++ b/spec/javascripts/fixtures/u2f/authenticate.html.haml
@@ -1 +1 @@
-= render partial: "u2f/authenticate", locals: { new_user_session_path: "/users/sign_in" }
+= render partial: "u2f/authenticate", locals: { new_user_session_path: "/users/sign_in", params: {}, resource_name: "user" }
diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
index 82ee195..d5401fb 100644
--- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
@@ -7,7 +7,7 @@ describe("ContributorsGraph", function () {
      expect(ContributorsGraph.prototype.x_domain).toEqual(20)
     })
   })
-  
+
   describe("#set_y_domain", function () {
     it("sets the y_domain", function () {
       ContributorsGraph.set_y_domain([{commits: 30}])
@@ -89,7 +89,7 @@ describe("ContributorsGraph", function () {
 })
 
 describe("ContributorsMasterGraph", function () {
-  
+
   // TODO: fix or remove
   //describe("#process_dates", function () {
     //it("gets and parses dates", function () {
@@ -103,7 +103,7 @@ describe("ContributorsMasterGraph", function () {
       //expect(graph.get_dates).toHaveBeenCalledWith(data)
       //expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get")
     //})
-  //}) 
+  //})
 
   describe("#get_dates", function () {
     it("plucks the date field from data collection", function () {
@@ -124,5 +124,5 @@ describe("ContributorsMasterGraph", function () {
     })
   })
 
-  
+
 })
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index dc6231e..33690c7 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,7 +1,5 @@
 
 /*= require lib/utils/text_utility */
-
-
 /*= require issue */
 
 (function() {
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 25d3f5b..f09596b 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -1,7 +1,5 @@
 
 /*= require jquery-ui/autocomplete */
-
-
 /*= require new_branch_form */
 
 (function() {
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 14dc6bf..a588f40 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -1,8 +1,7 @@
-
 /*= require notes */
-
-
+/*= require autosize */
 /*= require gl_form */
+/*= require lib/utils/text_utility */
 
 (function() {
   window.gon || (window.gon = {});
@@ -12,29 +11,63 @@
   };
 
   describe('Notes', function() {
-    return describe('task lists', function() {
+    describe('task lists', function() {
       fixture.preload('issue_note.html');
+
       beforeEach(function() {
         fixture.load('issue_note.html');
         $('form').on('submit', function(e) {
-          return e.preventDefault();
+          e.preventDefault();
         });
-        return this.notes = new Notes();
+        this.notes = new Notes();
       });
+
       it('modifies the Markdown field', function() {
         $('input[type=checkbox]').attr('checked', true).trigger('change');
-        return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
+        expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
       });
-      return it('submits the form on tasklist:changed', function() {
-        var submitted;
-        submitted = false;
+
+      it('submits the form on tasklist:changed', function() {
+        var submitted = false;
         $('form').on('submit', function(e) {
           submitted = true;
-          return e.preventDefault();
+          e.preventDefault();
         });
+
         $('.js-task-list-field').trigger('tasklist:changed');
-        return expect(submitted).toBe(true);
+        expect(submitted).toBe(true);
+      });
+    });
+
+    describe('comments', function() {
+      var commentsTemplate = 'comments.html';
+      var textarea = '.js-note-text';
+      fixture.preload(commentsTemplate);
+
+      beforeEach(function() {
+        fixture.load(commentsTemplate);
+        this.notes = new Notes();
+
+        this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update');
+        spyOn(this.notes, 'renderNote').and.stub();
+
+        $(textarea).data('autosave', {
+          reset: function() {}
+        });
+
+        $('form').on('submit', function(e) {
+          e.preventDefault();
+          $('.js-main-target-form').trigger('ajax:success');
+        });
       });
+
+      it('autosizes after comment submission', function() {
+        $(textarea).text('This is an example comment note');
+        expect(this.autoSizeSpy).not.toHaveBeenTriggered();
+
+        $('.js-comment-button').click();
+        expect(this.autoSizeSpy).toHaveBeenTriggered();
+      })
     });
   });
 
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index ffe4982..51eb12b 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -1,22 +1,10 @@
 
 /*= require bootstrap */
-
-
 /*= require select2 */
-
-
 /*= require lib/utils/type_utility */
-
-
 /*= require gl_dropdown */
-
-
 /*= require api */
-
-
 /*= require project_select */
-
-
 /*= require project */
 
 (function() {
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 38b3b26..c937a47 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -1,10 +1,6 @@
 
 /*= require right_sidebar */
-
-
 /*= require jquery */
-
-
 /*= require jquery.cookie */
 
 (function() {
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 324f515..00d9fc1 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -1,19 +1,9 @@
 
 /*= require gl_dropdown */
-
-
 /*= require search_autocomplete */
-
-
 /*= require jquery */
-
-
 /*= require lib/utils/common_utils */
-
-
 /*= require lib/utils/type_utility */
-
-
 /*= require fuzzaldrin-plus */
 
 (function() {
@@ -43,6 +33,8 @@
 
   groupName = 'Gitlab Org';
 
+  // Add required attributes to body before starting the test.
+  // section would be dashboard|group|project
   addBodyAttributes = function(section) {
     var $body;
     if (section == null) {
@@ -64,6 +56,7 @@
     }
   };
 
+  // Mock `gl` object in window for dashboard specific page. App code will need it.
   mockDashboardOptions = function() {
     window.gl || (window.gl = {});
     return window.gl.dashboardOptions = {
@@ -72,6 +65,7 @@
     };
   };
 
+  // Mock `gl` object in window for project specific page. App code will need it.
   mockProjectOptions = function() {
     window.gl || (window.gl = {});
     return window.gl.projectOptions = {
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 7b6b55f..04ccf24 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -10,6 +10,7 @@
     });
     return describe('#replyWithSelectedText', function() {
       var stubSelection;
+      // Stub window.getSelection to return the provided String.
       stubSelection = function(text) {
         return window.getSelection = function() {
           return text;
diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js
index 7d91ed0..8801c29 100644
--- a/spec/javascripts/spec_helper.js
+++ b/spec/javascripts/spec_helper.js
@@ -1,21 +1,41 @@
-
+// PhantomJS (Teaspoons default driver) doesn't have support for
+// Function.prototype.bind, which has caused confusion.  Use this polyfill to
+// avoid the confusion.
 /*= require support/bind-poly */
 
-
+// You can require your own javascript files here. By default this will include
+// everything in application, however you may get better load performance if you
+// require the specific files that are being used in the spec that tests them.
 /*= require jquery */
-
-
 /*= require jquery.turbolinks */
-
-
 /*= require bootstrap */
-
-
 /*= require underscore */
 
-
+// Teaspoon includes some support files, but you can use anything from your own
+// support path too.
+// require support/jasmine-jquery-1.7.0
+// require support/jasmine-jquery-2.0.0
 /*= require support/jasmine-jquery-2.1.0 */
 
+// require support/sinon
+// require support/your-support-file
+// Deferring execution
+// If you're using CommonJS, RequireJS or some other asynchronous library you can
+// defer execution. Call Teaspoon.execute() after everything has been loaded.
+// Simple example of a timeout:
+// Teaspoon.defer = true
+// setTimeout(Teaspoon.execute, 1000)
+// Matching files
+// By default Teaspoon will look for files that match
+// _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your spec path
+// and it'll be included in the default suite automatically. If you want to
+// customize suites, check out the configuration in teaspoon_env.rb
+// Manifest
+// If you'd rather require your spec files manually (to control order for
+// instance) you can disable the suite matcher in the configuration and use this
+// file as a manifest.
+// For more information: http://github.com/modeset/teaspoon
+
 (function() {
 
 
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index e008ce9..7ce3884 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -1,16 +1,8 @@
 
 /*= require u2f/authenticate */
-
-
 /*= require u2f/util */
-
-
 /*= require u2f/error */
-
-
 /*= require u2f */
-
-
 /*= require ./mock_u2f_device */
 
 (function() {
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index 21c5266..01d6b7a 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -1,16 +1,8 @@
 
 /*= require u2f/register */
-
-
 /*= require u2f/util */
-
-
 /*= require u2f/error */
-
-
 /*= require u2f */
-
-
 /*= require ./mock_u2f_device */
 
 (function() {
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 3d680ec..0c12668 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -14,8 +14,10 @@
             return true;
           }
         };
+      // Stub Dropzone.forElement(...).enable()
       });
       this.zen = new ZenMode();
+      // Set this manually because we can't actually scroll the window
       return this.zen.scroll_position = 456;
     });
     describe('on enter', function() {
@@ -60,7 +62,7 @@
     return $('a.js-zen-enter').click();
   };
 
-  exitZen = function() {
+  exitZen = function() { // Ohmmmmmmm
     return $('a.js-zen-leave').click();
   };
 
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index 593bd6d..e6c90ad 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -65,14 +65,14 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
       expect(reference_filter(act).to_html).to eq exp
     end
 
-    it 'includes a title attribute' do
+    it 'includes no title attribute' do
       doc = reference_filter("See #{reference}")
-      expect(doc.css('a').first.attr('title')).to eq range.reference_title
+      expect(doc.css('a').first.attr('title')).to eq ""
     end
 
     it 'includes default classes' do
       doc = reference_filter("See #{reference}")
-      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range has-tooltip'
     end
 
     it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index d46d3f1..e0f0828 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -55,7 +55,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
 
     it 'includes a title attribute' do
       doc = reference_filter("See #{reference}")
-      expect(doc.css('a').first.attr('title')).to eq commit.link_title
+      expect(doc.css('a').first.attr('title')).to eq commit.title
     end
 
     it 'escapes the title attribute' do
@@ -67,7 +67,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
 
     it 'includes default classes' do
       doc = reference_filter("See #{reference}")
-      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit has-tooltip'
     end
 
     it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index 9534666..7116c09 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -64,7 +64,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
 
     it 'includes default classes' do
       doc = filter("Issue #{reference}")
-      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
     end
 
     it 'supports an :only_path context' do
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index a005b49..fce86a9 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -54,7 +54,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
 
     it 'includes a title attribute' do
       doc = reference_filter("Issue #{reference}")
-      expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
+      expect(doc.css('a').first.attr('title')).to eq issue.title
     end
 
     it 'escapes the title attribute' do
@@ -66,7 +66,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
 
     it 'includes default classes' do
       doc = reference_filter("Issue #{reference}")
-      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
     end
 
     it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 9276a15..908cceb 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -21,7 +21,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
 
   it 'includes default classes' do
     doc = reference_filter("Label #{reference}")
-    expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
+    expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label has-tooltip'
   end
 
   it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 805acf1..274258a 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -46,7 +46,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
 
     it 'includes a title attribute' do
       doc = reference_filter("Merge #{reference}")
-      expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
+      expect(doc.css('a').first.attr('title')).to eq merge.title
     end
 
     it 'escapes the title attribute' do
@@ -58,7 +58,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
 
     it 'includes default classes' do
       doc = reference_filter("Merge #{reference}")
-      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request has-tooltip'
     end
 
     it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 9424f23..7419863 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -20,7 +20,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
 
   it 'includes default classes' do
     doc = reference_filter("Milestone #{reference}")
-    expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone'
+    expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip'
   end
 
   it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 5068ddd..9b92d1a 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -39,7 +39,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
 
     it 'includes a title attribute' do
       doc = reference_filter("Snippet #{reference}")
-      expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
+      expect(doc.css('a').first.attr('title')).to eq snippet.title
     end
 
     it 'escapes the title attribute' do
@@ -51,7 +51,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
 
     it 'includes default classes' do
       doc = reference_filter("Snippet #{reference}")
-      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet has-tooltip'
     end
 
     it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 108b36a..fdbdb21 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -104,7 +104,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
 
   it 'includes default classes' do
     doc = reference_filter("Hey #{reference}")
-    expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
+    expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip'
   end
 
   it 'supports an :only_path context' do
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 51c89ac..ac9bde6 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -127,6 +127,13 @@ describe Banzai::Pipeline::WikiPipeline do
 
               expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page.md\"")
             end
+
+            it 'rewrites links with anchor' do
+              markdown = '[Link to Header](start-page#title)'
+              output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+              expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/start-page#title\"")
+            end
           end
 
           describe "when creating root links" do
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index ac9c66e..9095d2b 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
       it 'returns the nodes if the attribute value equals the current project ID' do
         link['data-project'] = project.id.to_s
 
-        expect(Ability.abilities).not_to receive(:allowed?)
+        expect(Ability).not_to receive(:allowed?)
         expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
       end
 
@@ -39,7 +39,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
 
         link['data-project'] = other_project.id.to_s
 
-        expect(Ability.abilities).to receive(:allowed?).
+        expect(Ability).to receive(:allowed?).
           with(user, :read_project, other_project).
           and_return(true)
 
@@ -57,7 +57,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
 
         link['data-project'] = other_project.id.to_s
 
-        expect(Ability.abilities).to receive(:allowed?).
+        expect(Ability).to receive(:allowed?).
           with(user, :read_project, other_project).
           and_return(false)
 
@@ -221,7 +221,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
     it 'delegates the permissions check to the Ability class' do
       user = double(:user)
 
-      expect(Ability.abilities).to receive(:allowed?).
+      expect(Ability).to receive(:allowed?).
         with(user, :read_project, project)
 
       subject.can?(user, :read_project, project)
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 9a82891..4e7f82a 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -82,7 +82,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
         end
 
         it 'returns the nodes if the user can read the group' do
-          expect(Ability.abilities).to receive(:allowed?).
+          expect(Ability).to receive(:allowed?).
             with(user, :read_group, group).
             and_return(true)
 
@@ -90,7 +90,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
         end
 
         it 'returns an empty Array if the user can not read the group' do
-          expect(Ability.abilities).to receive(:allowed?).
+          expect(Ability).to receive(:allowed?).
             with(user, :read_group, group).
             and_return(false)
 
@@ -103,7 +103,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
           it 'returns the nodes if the attribute value equals the current project ID' do
             link['data-project'] = project.id.to_s
 
-            expect(Ability.abilities).not_to receive(:allowed?)
+            expect(Ability).not_to receive(:allowed?)
 
             expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
           end
@@ -113,7 +113,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
 
             link['data-project'] = other_project.id.to_s
 
-            expect(Ability.abilities).to receive(:allowed?).
+            expect(Ability).to receive(:allowed?).
               with(user, :read_project, other_project).
               and_return(true)
 
@@ -125,7 +125,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
 
             link['data-project'] = other_project.id.to_s
 
-            expect(Ability.abilities).to receive(:allowed?).
+            expect(Ability).to receive(:allowed?).
               with(user, :read_project, other_project).
               and_return(false)
 
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index be51d94..6dedd25 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -754,6 +754,20 @@ module Ci
         it 'does return production' do
           expect(builds.size).to eq(1)
           expect(builds.first[:environment]).to eq(environment)
+          expect(builds.first[:options]).to include(environment: { name: environment })
+        end
+      end
+
+      context 'when hash is specified' do
+        let(:environment) do
+          { name: 'production',
+            url: 'http://production.gitlab.com' }
+        end
+
+        it 'does return production and URL' do
+          expect(builds.size).to eq(1)
+          expect(builds.first[:environment]).to eq(environment[:name])
+          expect(builds.first[:options]).to include(environment: environment)
         end
       end
 
@@ -770,15 +784,16 @@ module Ci
         let(:environment) { 1 }
 
         it 'raises error' do
-          expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
+          expect { builds }.to raise_error(
+            'jobs:deploy_to_production:environment config should be a hash or a string')
         end
       end
 
       context 'is not a valid string' do
-        let(:environment) { 'production staging' }
+        let(:environment) { 'production:staging' }
 
         it 'raises error' do
-          expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
+          expect { builds }.to raise_error("jobs:deploy_to_production:environment name #{Gitlab::Regex.environment_name_regex_message}")
         end
       end
     end
@@ -1250,5 +1265,40 @@ EOT
         end
       end
     end
+
+    describe "#validation_message" do
+      context "when the YAML could not be parsed" do
+        it "returns an error about invalid configutaion" do
+          content = YAML.dump("invalid: yaml: test")
+
+          expect(GitlabCiYamlProcessor.validation_message(content))
+            .to eq "Invalid configuration format"
+        end
+      end
+
+      context "when the tags parameter is invalid" do
+        it "returns an error about invalid tags" do
+          content = YAML.dump({ rspec: { script: "test", tags: "mysql" } })
+
+          expect(GitlabCiYamlProcessor.validation_message(content))
+            .to eq "jobs:rspec tags should be an array of strings"
+        end
+      end
+
+      context "when YAML content is empty" do
+        it "returns an error about missing content" do
+          expect(GitlabCiYamlProcessor.validation_message(''))
+            .to eq "Please provide content of .gitlab-ci.yml"
+        end
+      end
+
+      context "when the YAML is valid" do
+        it "does not return any errors" do
+          content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+
+          expect(GitlabCiYamlProcessor.validation_message(content)).to be_nil
+        end
+      end
+    end
   end
 end
diff --git a/spec/lib/ci/mask_secret_spec.rb b/spec/lib/ci/mask_secret_spec.rb
new file mode 100644
index 0000000..3101bed
--- /dev/null
+++ b/spec/lib/ci/mask_secret_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Ci::MaskSecret, lib: true do
+  subject { described_class }
+
+  describe '#mask' do
+    it 'masks exact number of characters' do
+      expect(mask('token', 'oke')).to eq('txxxn')
+    end
+
+    it 'masks multiple occurrences' do
+      expect(mask('token token token', 'oke')).to eq('txxxn txxxn txxxn')
+    end
+
+    it 'does not mask if not found' do
+      expect(mask('token', 'not')).to eq('token')
+    end
+
+    it 'does support null token' do
+      expect(mask('token', nil)).to eq('token')
+    end
+
+    def mask(value, token)
+      subject.mask!(value.dup, token)
+    end
+  end
+end
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
new file mode 100644
index 0000000..90bc7da
--- /dev/null
+++ b/spec/lib/expand_variables_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe ExpandVariables do
+  describe '#expand' do
+    subject { described_class.expand(value, variables) }
+
+    tests = [
+      { value: 'key',
+        result: 'key',
+        variables: []
+      },
+      { value: 'key$variable',
+        result: 'key',
+        variables: []
+      },
+      { value: 'key$variable',
+        result: 'keyvalue',
+        variables: [
+          { key: 'variable', value: 'value' }
+        ]
+      },
+      { value: 'key${variable}',
+        result: 'keyvalue',
+        variables: [
+          { key: 'variable', value: 'value' }
+        ]
+      },
+      { value: 'key$variable$variable2',
+        result: 'keyvalueresult',
+        variables: [
+          { key: 'variable', value: 'value' },
+          { key: 'variable2', value: 'result' },
+        ]
+      },
+      { value: 'key${variable}${variable2}',
+        result: 'keyvalueresult',
+        variables: [
+          { key: 'variable', value: 'value' },
+          { key: 'variable2', value: 'result' }
+        ]
+      },
+      { value: 'key$variable2$variable',
+        result: 'keyresultvalue',
+        variables: [
+          { key: 'variable', value: 'value' },
+          { key: 'variable2', value: 'result' },
+        ]
+      },
+      { value: 'key${variable2}${variable}',
+        result: 'keyresultvalue',
+        variables: [
+          { key: 'variable', value: 'value' },
+          { key: 'variable2', value: 'result' }
+        ]
+      },
+      { value: 'review/$CI_BUILD_REF_NAME',
+        result: 'review/feature/add-review-apps',
+        variables: [
+          { key: 'CI_BUILD_REF_NAME', value: 'feature/add-review-apps' }
+        ]
+      },
+    ]
+
+    tests.each do |test|
+      context "#{test[:value]} resolves to #{test[:result]}" do
+        let(:value) { test[:value] }
+        let(:variables) { test[:variables] }
+
+        it { is_expected.to eq(test[:result]) }
+      end
+    end
+  end
+end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 86d04ec..e10c1f5 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -51,6 +51,16 @@ describe ExtractsPath, lib: true do
         expect(@path).to eq(params[:path])
       end
     end
+
+    context 'subclass overrides get_id' do
+      it 'uses ref returned by get_id' do
+        allow_any_instance_of(self.class).to receive(:get_id){ '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' }
+
+        assign_ref_vars
+
+        expect(@id).to eq(get_id)
+      end
+    end
   end
 
   describe '#extract_ref' do
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index b0772ca..745fbc0 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -4,14 +4,53 @@ describe Gitlab::Auth, lib: true do
   let(:gl_auth) { described_class }
 
   describe 'find_for_git_client' do
-    it 'recognizes CI' do
-      token = '123'
+    context 'build token' do
+      subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') }
+
+      context 'for running build' do
+        let!(:build) { create(:ci_build, :running) }
+        let(:project) { build.project }
+
+        before do
+          expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token')
+        end
+
+        it 'recognises user-less build' do
+          expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities))
+        end
+
+        it 'recognises user token' do
+          build.update(user: create(:user))
+
+          expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities))
+        end
+      end
+
+      (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
+        context "for #{build_status} build" do
+          let!(:build) { create(:ci_build, status: build_status) }
+          let(:project) { build.project }
+
+          before do
+            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token')
+          end
+
+          it 'denies authentication' do
+            expect(subject).to eq(Gitlab::Auth::Result.new)
+          end
+        end
+      end
+    end
+
+    it 'recognizes other ci services' do
       project = create(:empty_project)
-      project.update_attributes(runners_token: token, builds_enabled: true)
+      project.create_drone_ci_service(active: true)
+      project.drone_ci_service.update(token: 'token')
+
       ip = 'ip'
 
-      expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token')
-      expect(gl_auth.find_for_git_client('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci))
+      expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'drone-ci-token')
+      expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities))
     end
 
     it 'recognizes master passwords' do
@@ -19,7 +58,25 @@ describe Gitlab::Auth, lib: true do
       ip = 'ip'
 
       expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
-      expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap))
+      expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+    end
+
+    it 'recognizes user lfs tokens' do
+      user = create(:user)
+      ip = 'ip'
+      token = Gitlab::LfsToken.new(user).generate
+
+      expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
+      expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
+    end
+
+    it 'recognizes deploy key lfs tokens' do
+      key = create(:deploy_key)
+      ip = 'ip'
+      token = Gitlab::LfsToken.new(key).generate
+
+      expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}")
+      expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
     end
 
     it 'recognizes OAuth tokens' do
@@ -29,7 +86,7 @@ describe Gitlab::Auth, lib: true do
       ip = 'ip'
 
       expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2')
-      expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth))
+      expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
     end
 
     it 'returns double nil for invalid credentials' do
@@ -91,4 +148,30 @@ describe Gitlab::Auth, lib: true do
       end
     end
   end
+
+  private
+
+  def build_authentication_abilities
+    [
+      :read_project,
+      :build_download_code,
+      :build_read_container_image,
+      :build_create_container_image
+    ]
+  end
+
+  def read_authentication_abilities
+    [
+      :read_project,
+      :download_code,
+      :read_container_image
+    ]
+  end
+
+  def full_authentication_abilities
+    read_authentication_abilities + [
+      :push_code,
+      :create_container_image
+    ]
+  end
 end
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index 6e5ba21..07407f2 100644
--- a/spec/lib/gitlab/backend/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -1,4 +1,5 @@
 require 'spec_helper'
+require 'stringio'
 
 describe Gitlab::Shell, lib: true do
   let(:project) { double('Project', id: 7, path: 'diaspora') }
@@ -44,15 +45,38 @@ describe Gitlab::Shell, lib: true do
     end
   end
 
+  describe '#add_key' do
+    it 'removes trailing garbage' do
+      allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+      expect(Gitlab::Utils).to receive(:system_silent).with(
+        [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
+      )
+
+      gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+    end
+  end
+
   describe Gitlab::Shell::KeyAdder, lib: true do
     describe '#add_key' do
-      it 'normalizes space characters in the key' do
-        io = spy
+      it 'removes trailing garbage' do
+        io = spy(:io)
         adder = described_class.new(io)
 
-        adder.add_key('key-42', "sha-rsa foo\tbar\tbaz")
+        adder.add_key('key-42', "ssh-rsa foo bar\tbaz")
+
+        expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
+      end
+
+      it 'raises an exception if the key contains a tab' do
+        expect do
+          described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar")
+        end.to raise_error(Gitlab::Shell::Error)
+      end
 
-        expect(io).to have_received(:puts).with("key-42\tsha-rsa foo bar baz")
+      it 'raises an exception if the key contains a newline' do
+        expect do
+          described_class.new(StringIO.new).add_key('key-42', "ssh-rsa foobar\nssh-rsa pawned")
+        end.to raise_error(Gitlab::Shell::Error)
       end
     end
   end
diff --git a/spec/lib/gitlab/ci/config/node/cache_spec.rb b/spec/lib/gitlab/ci/config/node/cache_spec.rb
index 50f619c..e251210 100644
--- a/spec/lib/gitlab/ci/config/node/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/cache_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Cache do
   let(:entry) { described_class.new(config) }
 
   describe 'validations' do
-    before { entry.process! }
+    before { entry.compose! }
 
     context 'when entry config value is correct' do
       let(:config) do
diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb
new file mode 100644
index 0000000..df45322
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb
@@ -0,0 +1,155 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Environment do
+  let(:entry) { described_class.new(config) }
+
+  before { entry.compose! }
+
+  context 'when configuration is a string' do
+    let(:config) { 'production' }
+
+    describe '#string?' do
+      it 'is string configuration' do
+        expect(entry).to be_string
+      end
+    end
+
+    describe '#hash?' do
+      it 'is not hash configuration' do
+        expect(entry).not_to be_hash
+      end
+    end
+
+    describe '#valid?' do
+      it 'is valid' do
+        expect(entry).to be_valid
+      end
+    end
+
+    describe '#value' do
+      it 'returns valid hash' do
+        expect(entry.value).to eq(name: 'production')
+      end
+    end
+
+    describe '#name' do
+      it 'returns environment name' do
+        expect(entry.name).to eq 'production'
+      end
+    end
+
+    describe '#url' do
+      it 'returns environment url' do
+        expect(entry.url).to be_nil
+      end
+    end
+  end
+
+  context 'when configuration is a hash' do
+    let(:config) do
+      { name: 'development', url: 'https://example.gitlab.com' }
+    end
+
+    describe '#string?' do
+      it 'is not string configuration' do
+        expect(entry).not_to be_string
+      end
+    end
+
+    describe '#hash?' do
+      it 'is hash configuration' do
+        expect(entry).to be_hash
+      end
+    end
+
+    describe '#valid?' do
+      it 'is valid' do
+        expect(entry).to be_valid
+      end
+    end
+
+    describe '#value' do
+      it 'returns valid hash' do
+        expect(entry.value).to eq config
+      end
+    end
+
+    describe '#name' do
+      it 'returns environment name' do
+        expect(entry.name).to eq 'development'
+      end
+    end
+
+    describe '#url' do
+      it 'returns environment url' do
+        expect(entry.url).to eq 'https://example.gitlab.com'
+      end
+    end
+  end
+
+  context 'when variables are used for environment' do
+    let(:config) do
+      { name: 'review/$CI_BUILD_REF_NAME',
+        url: 'https://$CI_BUILD_REF_NAME.review.gitlab.com' }
+    end
+
+    describe '#valid?' do
+      it 'is valid' do
+        expect(entry).to be_valid
+      end
+    end
+  end
+
+  context 'when configuration is invalid' do
+    context 'when configuration is an array' do
+      let(:config) { ['env'] }
+
+      describe '#valid?' do
+        it 'is not valid' do
+          expect(entry).not_to be_valid
+        end
+      end
+
+      describe '#errors' do
+        it 'contains error about invalid type' do
+          expect(entry.errors)
+            .to include 'environment config should be a hash or a string'
+        end
+      end
+    end
+
+    context 'when environment name is not present' do
+      let(:config) { { url: 'https://example.gitlab.com' } }
+
+      describe '#valid?' do
+        it 'is not valid' do
+          expect(entry).not_to be_valid
+        end
+      end
+
+      describe '#errors?' do
+        it 'contains error about missing environment name' do
+          expect(entry.errors)
+            .to include "environment name can't be blank"
+        end
+      end
+    end
+
+    context 'when invalid URL is used' do
+      let(:config) { { name: 'test', url: 'invalid-example.gitlab.com' } }
+
+      describe '#valid?' do
+        it 'is not valid' do
+          expect(entry).not_to be_valid
+        end
+      end
+
+      describe '#errors?' do
+        it 'contains error about invalid URL' do
+          expect(entry.errors)
+            .to include "environment url must be a valid url"
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb
index d26185b..a699089 100644
--- a/spec/lib/gitlab/ci/config/node/factory_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb
@@ -65,7 +65,8 @@ describe Gitlab::Ci::Config::Node::Factory do
           .value(nil)
           .create!
 
-        expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
+        expect(entry)
+          .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified
       end
     end
 
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index 2f87d27..12232ff 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Config::Node::Global do
   end
 
   context 'when hash is valid' do
-    context 'when all entries defined' do
+    context 'when some entries defined' do
       let(:hash) do
         { before_script: ['ls', 'pwd'],
           image: 'ruby:2.2',
@@ -24,11 +24,11 @@ describe Gitlab::Ci::Config::Node::Global do
           stages: ['build', 'pages'],
           cache: { key: 'k', untracked: true, paths: ['public/'] },
           rspec: { script: %w[rspec ls] },
-          spinach: { script: 'spinach' } }
+          spinach: { before_script: [], variables: {}, script: 'spinach' } }
       end
 
-      describe '#process!' do
-        before { global.process! }
+      describe '#compose!' do
+        before { global.compose! }
 
         it 'creates nodes hash' do
           expect(global.descendants).to be_an Array
@@ -59,7 +59,7 @@ describe Gitlab::Ci::Config::Node::Global do
         end
       end
 
-      context 'when not processed' do
+      context 'when not composed' do
         describe '#before_script' do
           it 'returns nil' do
             expect(global.before_script).to be nil
@@ -73,8 +73,14 @@ describe Gitlab::Ci::Config::Node::Global do
         end
       end
 
-      context 'when processed' do
-        before { global.process! }
+      context 'when composed' do
+        before { global.compose! }
+
+        describe '#errors' do
+          it 'has no errors' do
+            expect(global.errors).to be_empty
+          end
+        end
 
         describe '#before_script' do
           it 'returns correct script' do
@@ -137,10 +143,24 @@ describe Gitlab::Ci::Config::Node::Global do
             expect(global.jobs).to eq(
               rspec: { name: :rspec,
                        script: %w[rspec ls],
-                       stage: 'test' },
+                       before_script: ['ls', 'pwd'],
+                       commands: "ls\npwd\nrspec\nls",
+                       image: 'ruby:2.2',
+                       services: ['postgres:9.1', 'mysql:5.5'],
+                       stage: 'test',
+                       cache: { key: 'k', untracked: true, paths: ['public/'] },
+                       variables: { VAR: 'value' },
+                       after_script: ['make clean'] },
               spinach: { name: :spinach,
+                         before_script: [],
                          script: %w[spinach],
-                         stage: 'test' }
+                         commands: 'spinach',
+                         image: 'ruby:2.2',
+                         services: ['postgres:9.1', 'mysql:5.5'],
+                         stage: 'test',
+                         cache: { key: 'k', untracked: true, paths: ['public/'] },
+                         variables: {},
+                         after_script: ['make clean'] },
             )
           end
         end
@@ -148,17 +168,20 @@ describe Gitlab::Ci::Config::Node::Global do
     end
 
     context 'when most of entires not defined' do
-      let(:hash) { { cache: { key: 'a' }, rspec: { script: %w[ls] } } }
-      before { global.process! }
+      before { global.compose! }
+
+      let(:hash) do
+        { cache: { key: 'a' }, rspec: { script: %w[ls] } }
+      end
 
       describe '#nodes' do
         it 'instantizes all nodes' do
           expect(global.descendants.count).to eq 8
         end
 
-        it 'contains undefined nodes' do
+        it 'contains unspecified nodes' do
           expect(global.descendants.first)
-            .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
+            .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified
         end
       end
 
@@ -188,8 +211,11 @@ describe Gitlab::Ci::Config::Node::Global do
     # details.
     #
     context 'when entires specified but not defined' do
-      let(:hash) { { variables: nil, rspec: { script: 'rspec' } } }
-      before { global.process! }
+      before { global.compose! }
+
+      let(:hash) do
+        { variables: nil, rspec: { script: 'rspec' } }
+      end
 
       describe '#variables' do
         it 'undefined entry returns a default value' do
@@ -200,7 +226,7 @@ describe Gitlab::Ci::Config::Node::Global do
   end
 
   context 'when hash is not valid' do
-    before { global.process! }
+    before { global.compose! }
 
     let(:hash) do
       { before_script: 'ls' }
@@ -247,4 +273,27 @@ describe Gitlab::Ci::Config::Node::Global do
       expect(global.specified?).to be true
     end
   end
+
+  describe '#[]' do
+    before { global.compose! }
+
+    let(:hash) do
+      { cache: { key: 'a' }, rspec: { script: 'ls' } }
+    end
+
+    context 'when node exists' do
+      it 'returns correct entry' do
+        expect(global[:cache])
+          .to be_an_instance_of Gitlab::Ci::Config::Node::Cache
+        expect(global[:jobs][:rspec][:script].value).to eq ['ls']
+      end
+    end
+
+    context 'when node does not exist' do
+      it 'always return unspecified node' do
+        expect(global[:some][:unknown][:node])
+          .not_to be_specified
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb b/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb
deleted file mode 100644
index cc44e2c..0000000
--- a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Ci::Config::Node::HiddenJob do
-  let(:entry) { described_class.new(config) }
-
-  describe 'validations' do
-    context 'when entry config value is correct' do
-      let(:config) { { image: 'ruby:2.2' } }
-
-      describe '#value' do
-        it 'returns key value' do
-          expect(entry.value).to eq(image: 'ruby:2.2')
-        end
-      end
-
-      describe '#valid?' do
-        it 'is valid' do
-          expect(entry).to be_valid
-        end
-      end
-    end
-
-    context 'when entry value is not correct' do
-      context 'incorrect config value type' do
-        let(:config) { ['incorrect'] }
-
-        describe '#errors' do
-          it 'saves errors' do
-            expect(entry.errors)
-              .to include 'hidden job config should be a hash'
-          end
-        end
-      end
-
-      context 'when config is empty' do
-        let(:config) { {} }
-
-        describe '#valid' do
-          it 'is invalid' do
-            expect(entry).not_to be_valid
-          end
-        end
-      end
-    end
-  end
-
-  describe '#leaf?' do
-    it 'is a leaf' do
-      expect(entry).to be_leaf
-    end
-  end
-
-  describe '#relevant?' do
-    it 'is not a relevant entry' do
-      expect(entry).not_to be_relevant
-    end
-  end
-end
diff --git a/spec/lib/gitlab/ci/config/node/hidden_spec.rb b/spec/lib/gitlab/ci/config/node/hidden_spec.rb
new file mode 100644
index 0000000..61e2a55
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/hidden_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Hidden do
+  let(:entry) { described_class.new(config) }
+
+  describe 'validations' do
+    context 'when entry config value is correct' do
+      let(:config) { [:some, :array] }
+
+      describe '#value' do
+        it 'returns key value' do
+          expect(entry.value).to eq [:some, :array]
+        end
+      end
+
+      describe '#valid?' do
+        it 'is valid' do
+          expect(entry).to be_valid
+        end
+      end
+    end
+
+    context 'when entry value is not correct' do
+      context 'when config is empty' do
+        let(:config) { {} }
+
+        describe '#valid' do
+          it 'is invalid' do
+            expect(entry).not_to be_valid
+          end
+        end
+      end
+    end
+  end
+
+  describe '#leaf?' do
+    it 'is a leaf' do
+      expect(entry).to be_leaf
+    end
+  end
+
+  describe '#relevant?' do
+    it 'is not a relevant entry' do
+      expect(entry).not_to be_relevant
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb
index 1484fb6..91f676d 100644
--- a/spec/lib/gitlab/ci/config/node/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/job_spec.rb
@@ -3,9 +3,9 @@ require 'spec_helper'
 describe Gitlab::Ci::Config::Node::Job do
   let(:entry) { described_class.new(config, name: :rspec) }
 
-  before { entry.process! }
-
   describe 'validations' do
+    before { entry.compose! }
+
     context 'when entry config value is correct' do
       let(:config) { { script: 'rspec' } }
 
@@ -59,28 +59,82 @@ describe Gitlab::Ci::Config::Node::Job do
     end
   end
 
-  describe '#value' do
-    context 'when entry is correct' do
+  describe '#relevant?' do
+    it 'is a relevant entry' do
+      expect(entry).to be_relevant
+    end
+  end
+
+  describe '#compose!' do
+    let(:unspecified) { double('unspecified', 'specified?' => false) }
+
+    let(:specified) do
+      double('specified', 'specified?' => true, value: 'specified')
+    end
+
+    let(:deps) { double('deps', '[]' => unspecified) }
+
+    context 'when job config overrides global config' do
+      before { entry.compose!(deps) }
+
       let(:config) do
-        { before_script: %w[ls pwd],
-          script: 'rspec',
-          after_script: %w[cleanup] }
+        { image: 'some_image', cache: { key: 'test' } }
+      end
+
+      it 'overrides global config' do
+        expect(entry[:image].value).to eq 'some_image'
+        expect(entry[:cache].value).to eq(key: 'test')
+      end
+    end
+
+    context 'when job config does not override global config' do
+      before do
+        allow(deps).to receive('[]').with(:image).and_return(specified)
+        entry.compose!(deps)
       end
 
-      it 'returns correct value' do
-        expect(entry.value)
-          .to eq(name: :rspec,
-                 before_script: %w[ls pwd],
-                 script: %w[rspec],
-                 stage: 'test',
-                 after_script: %w[cleanup])
+      let(:config) { { script: 'ls', cache: { key: 'test' } } }
+
+      it 'uses config from global entry' do
+        expect(entry[:image].value).to eq 'specified'
+        expect(entry[:cache].value).to eq(key: 'test')
       end
     end
   end
 
-  describe '#relevant?' do
-    it 'is a relevant entry' do
-      expect(entry).to be_relevant
+  context 'when composed' do
+    before { entry.compose! }
+
+    describe '#value' do
+      before { entry.compose! }
+
+      context 'when entry is correct' do
+        let(:config) do
+          { before_script: %w[ls pwd],
+            script: 'rspec',
+            after_script: %w[cleanup] }
+        end
+
+        it 'returns correct value' do
+          expect(entry.value)
+            .to eq(name: :rspec,
+                   before_script: %w[ls pwd],
+                   script: %w[rspec],
+                   commands: "ls\npwd\nrspec",
+                   stage: 'test',
+                   after_script: %w[cleanup])
+        end
+      end
+    end
+
+    describe '#commands' do
+      let(:config) do
+        { before_script: %w[ls pwd], script: 'rspec' }
+      end
+
+      it 'returns a string of commands concatenated with new line character' do
+        expect(entry.commands).to eq "ls\npwd\nrspec"
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
index b8d9c70..9298093 100644
--- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
   let(:entry) { described_class.new(config) }
 
   describe 'validations' do
-    before { entry.process! }
+    before { entry.compose! }
 
     context 'when entry config value is correct' do
       let(:config) { { rspec: { script: 'rspec' } } }
@@ -47,8 +47,8 @@ describe Gitlab::Ci::Config::Node::Jobs do
     end
   end
 
-  context 'when valid job entries processed' do
-    before { entry.process! }
+  context 'when valid job entries composed' do
+    before { entry.compose! }
 
     let(:config) do
       { rspec: { script: 'rspec' },
@@ -61,9 +61,11 @@ describe Gitlab::Ci::Config::Node::Jobs do
         expect(entry.value).to eq(
           rspec: { name: :rspec,
                    script: %w[rspec],
+                   commands: 'rspec',
                    stage: 'test' },
           spinach: { name: :spinach,
                      script: %w[spinach],
+                     commands: 'spinach',
                      stage: 'test' })
       end
     end
@@ -74,7 +76,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
         expect(entry.descendants.first(2))
           .to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job))
         expect(entry.descendants.last)
-          .to be_an_instance_of(Gitlab::Ci::Config::Node::HiddenJob)
+          .to be_an_instance_of(Gitlab::Ci::Config::Node::Hidden)
       end
     end
 
diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb
deleted file mode 100644
index 1ab5478..0000000
--- a/spec/lib/gitlab/ci/config/node/null_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Ci::Config::Node::Null do
-  let(:null) { described_class.new(nil) }
-
-  describe '#leaf?' do
-    it 'is leaf node' do
-      expect(null).to be_leaf
-    end
-  end
-
-  describe '#valid?' do
-    it 'is always valid' do
-      expect(null).to be_valid
-    end
-  end
-
-  describe '#errors' do
-    it 'is does not contain errors' do
-      expect(null.errors).to be_empty
-    end
-  end
-
-  describe '#value' do
-    it 'returns nil' do
-      expect(null.value).to eq nil
-    end
-  end
-
-  describe '#relevant?' do
-    it 'is not relevant' do
-      expect(null.relevant?).to eq false
-    end
-  end
-
-  describe '#specified?' do
-    it 'is not defined' do
-      expect(null.specified?).to eq false
-    end
-  end
-end
diff --git a/spec/lib/gitlab/ci/config/node/script_spec.rb b/spec/lib/gitlab/ci/config/node/script_spec.rb
index ee73953..219a7e9 100644
--- a/spec/lib/gitlab/ci/config/node/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/script_spec.rb
@@ -3,9 +3,7 @@ require 'spec_helper'
 describe Gitlab::Ci::Config::Node::Script do
   let(:entry) { described_class.new(config) }
 
-  describe '#process!' do
-    before { entry.process! }
-
+  describe 'validations' do
     context 'when entry config value is correct' do
       let(:config) { ['ls', 'pwd'] }
 
diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
index 2d43e1c..6bde860 100644
--- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
@@ -1,32 +1,41 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Config::Node::Undefined do
-  let(:undefined) { described_class.new(entry) }
-  let(:entry) { spy('Entry') }
+  let(:entry) { described_class.new }
+
+  describe '#leaf?' do
+    it 'is leaf node' do
+      expect(entry).to be_leaf
+    end
+  end
 
   describe '#valid?' do
-    it 'delegates method to entry' do
-      expect(undefined.valid).to eq entry
+    it 'is always valid' do
+      expect(entry).to be_valid
     end
   end
 
   describe '#errors' do
-    it 'delegates method to entry' do
-      expect(undefined.errors).to eq entry
+    it 'is does not contain errors' do
+      expect(entry.errors).to be_empty
     end
   end
 
   describe '#value' do
-    it 'delegates method to entry' do
-      expect(undefined.value).to eq entry
+    it 'returns nil' do
+      expect(entry.value).to eq nil
     end
   end
 
-  describe '#specified?' do
-    it 'is always false' do
-      allow(entry).to receive(:specified?).and_return(true)
+  describe '#relevant?' do
+    it 'is not relevant' do
+      expect(entry.relevant?).to eq false
+    end
+  end
 
-      expect(undefined.specified?).to be false
+  describe '#specified?' do
+    it 'is not defined' do
+      expect(entry.specified?).to eq false
     end
   end
 end
diff --git a/spec/lib/gitlab/ci/config/node/unspecified_spec.rb b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb
new file mode 100644
index 0000000..ba3ceef
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Unspecified do
+  let(:unspecified) { described_class.new(entry) }
+  let(:entry) { spy('Entry') }
+
+  describe '#valid?' do
+    it 'delegates method to entry' do
+      expect(unspecified.valid?).to eq entry
+    end
+  end
+
+  describe '#errors' do
+    it 'delegates method to entry' do
+      expect(unspecified.errors).to eq entry
+    end
+  end
+
+  describe '#value' do
+    it 'delegates method to entry' do
+      expect(unspecified.value).to eq entry
+    end
+  end
+
+  describe '#specified?' do
+    it 'is always false' do
+      allow(entry).to receive(:specified?).and_return(true)
+
+      expect(unspecified.specified?).to be false
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb
new file mode 100644
index 0000000..b26728a
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb
@@ -0,0 +1,115 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::PipelineDuration do
+  let(:calculated_duration) { calculate(data) }
+
+  shared_examples 'calculating duration' do
+    it do
+      expect(calculated_duration).to eq(duration)
+    end
+  end
+
+  context 'test sample A' do
+    let(:data) do
+      [[0, 1],
+       [1, 2],
+       [3, 4],
+       [5, 6]]
+    end
+
+    let(:duration) { 4 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample B' do
+    let(:data) do
+      [[0, 1],
+       [1, 2],
+       [2, 3],
+       [3, 4],
+       [0, 4]]
+    end
+
+    let(:duration) { 4 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample C' do
+    let(:data) do
+      [[0, 4],
+       [2, 6],
+       [5, 7],
+       [8, 9]]
+    end
+
+    let(:duration) { 8 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample D' do
+    let(:data) do
+      [[0, 1],
+       [2, 3],
+       [4, 5],
+       [6, 7]]
+    end
+
+    let(:duration) { 4 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample E' do
+    let(:data) do
+      [[0, 1],
+       [3, 9],
+       [3, 4],
+       [3, 5],
+       [3, 8],
+       [4, 5],
+       [4, 7],
+       [5, 8]]
+    end
+
+    let(:duration) { 7 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample F' do
+    let(:data) do
+      [[1, 3],
+       [2, 4],
+       [2, 4],
+       [2, 4],
+       [5, 8]]
+    end
+
+    let(:duration) { 6 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample G' do
+    let(:data) do
+      [[1, 3],
+       [2, 4],
+       [6, 7]]
+    end
+
+    let(:duration) { 4 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  def calculate(data)
+    periods = data.shuffle.map do |(first, last)|
+      Gitlab::Ci::PipelineDuration::Period.new(first, last)
+    end
+
+    Gitlab::Ci::PipelineDuration.from_periods(periods.sort_by(&:first))
+  end
+end
diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb
index a1d2ca1..16eb376 100644
--- a/spec/lib/gitlab/conflict/parser_spec.rb
+++ b/spec/lib/gitlab/conflict/parser_spec.rb
@@ -179,8 +179,8 @@ CONFLICT
           to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
       end
 
-      it 'raises UnmergeableFile when the file is over 100 KB' do
-        expect { parse_text('a' * 102401) }.
+      it 'raises UnmergeableFile when the file is over 200 KB' do
+        expect { parse_text('a' * 204801) }.
           to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
       end
 
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 4ec3f19..7fd25b9 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -91,63 +91,80 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
 
   describe '#add_column_with_default' do
     context 'outside of a transaction' do
-      before do
-        expect(model).to receive(:transaction_open?).and_return(false)
+      context 'when a column limit is not set' do
+        before do
+          expect(model).to receive(:transaction_open?).and_return(false)
 
-        expect(model).to receive(:transaction).and_yield
+          expect(model).to receive(:transaction).and_yield
 
-        expect(model).to receive(:add_column).
-          with(:projects, :foo, :integer, default: nil)
+          expect(model).to receive(:add_column).
+            with(:projects, :foo, :integer, default: nil)
 
-        expect(model).to receive(:change_column_default).
-          with(:projects, :foo, 10)
-      end
+          expect(model).to receive(:change_column_default).
+            with(:projects, :foo, 10)
+        end
 
-      it 'adds the column while allowing NULL values' do
-        expect(model).to receive(:update_column_in_batches).
-          with(:projects, :foo, 10)
+        it 'adds the column while allowing NULL values' do
+          expect(model).to receive(:update_column_in_batches).
+            with(:projects, :foo, 10)
 
-        expect(model).not_to receive(:change_column_null)
+          expect(model).not_to receive(:change_column_null)
 
-        model.add_column_with_default(:projects, :foo, :integer,
-                                      default: 10,
-                                      allow_null: true)
-      end
+          model.add_column_with_default(:projects, :foo, :integer,
+                                        default: 10,
+                                        allow_null: true)
+        end
 
-      it 'adds the column while not allowing NULL values' do
-        expect(model).to receive(:update_column_in_batches).
-          with(:projects, :foo, 10)
+        it 'adds the column while not allowing NULL values' do
+          expect(model).to receive(:update_column_in_batches).
+            with(:projects, :foo, 10)
 
-        expect(model).to receive(:change_column_null).
-          with(:projects, :foo, false)
+          expect(model).to receive(:change_column_null).
+            with(:projects, :foo, false)
 
-        model.add_column_with_default(:projects, :foo, :integer, default: 10)
-      end
+          model.add_column_with_default(:projects, :foo, :integer, default: 10)
+        end
 
-      it 'removes the added column whenever updating the rows fails' do
-        expect(model).to receive(:update_column_in_batches).
-          with(:projects, :foo, 10).
-          and_raise(RuntimeError)
+        it 'removes the added column whenever updating the rows fails' do
+          expect(model).to receive(:update_column_in_batches).
+            with(:projects, :foo, 10).
+            and_raise(RuntimeError)
 
-        expect(model).to receive(:remove_column).
-          with(:projects, :foo)
+          expect(model).to receive(:remove_column).
+            with(:projects, :foo)
 
-        expect do
-          model.add_column_with_default(:projects, :foo, :integer, default: 10)
-        end.to raise_error(RuntimeError)
+          expect do
+            model.add_column_with_default(:projects, :foo, :integer, default: 10)
+          end.to raise_error(RuntimeError)
+        end
+
+        it 'removes the added column whenever changing a column NULL constraint fails' do
+          expect(model).to receive(:change_column_null).
+            with(:projects, :foo, false).
+            and_raise(RuntimeError)
+
+          expect(model).to receive(:remove_column).
+            with(:projects, :foo)
+
+          expect do
+            model.add_column_with_default(:projects, :foo, :integer, default: 10)
+          end.to raise_error(RuntimeError)
+        end
       end
 
-      it 'removes the added column whenever changing a column NULL constraint fails' do
-        expect(model).to receive(:change_column_null).
-          with(:projects, :foo, false).
-          and_raise(RuntimeError)
+      context 'when a column limit is set' do
+        it 'adds the column with a limit' do
+          allow(model).to receive(:transaction_open?).and_return(false)
+          allow(model).to receive(:transaction).and_yield
+          allow(model).to receive(:update_column_in_batches).with(:projects, :foo, 10)
+          allow(model).to receive(:change_column_null).with(:projects, :foo, false)
+          allow(model).to receive(:change_column_default).with(:projects, :foo, 10)
 
-        expect(model).to receive(:remove_column).
-          with(:projects, :foo)
+          expect(model).to receive(:add_column).
+            with(:projects, :foo, :integer, default: nil, limit: 8)
 
-        expect do
-          model.add_column_with_default(:projects, :foo, :integer, default: 10)
-        end.to raise_error(RuntimeError)
+          model.add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
+        end
       end
     end
 
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index f12c9a3..de68e32 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -1,10 +1,17 @@
 require 'spec_helper'
 
 describe Gitlab::GitAccess, lib: true do
-  let(:access) { Gitlab::GitAccess.new(actor, project, 'web') }
+  let(:access) { Gitlab::GitAccess.new(actor, project, 'web', authentication_abilities: authentication_abilities) }
   let(:project) { create(:project) }
   let(:user) { create(:user) }
   let(:actor) { user }
+  let(:authentication_abilities) do
+    [
+      :read_project,
+      :download_code,
+      :push_code
+    ]
+  end
 
   describe '#check with single protocols allowed' do
     def disable_protocol(protocol)
@@ -15,7 +22,7 @@ describe Gitlab::GitAccess, lib: true do
     context 'ssh disabled' do
       before do
         disable_protocol('ssh')
-        @acc = Gitlab::GitAccess.new(actor, project, 'ssh')
+        @acc = Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities)
       end
 
       it 'blocks ssh git push' do
@@ -30,7 +37,7 @@ describe Gitlab::GitAccess, lib: true do
     context 'http disabled' do
       before do
         disable_protocol('http')
-        @acc = Gitlab::GitAccess.new(actor, project, 'http')
+        @acc = Gitlab::GitAccess.new(actor, project, 'http', authentication_abilities: authentication_abilities)
       end
 
       it 'blocks http push' do
@@ -111,6 +118,36 @@ describe Gitlab::GitAccess, lib: true do
         end
       end
     end
+
+    describe 'build authentication_abilities permissions' do
+      let(:authentication_abilities) { build_authentication_abilities }
+
+      describe 'reporter user' do
+        before { project.team << [user, :reporter] }
+
+        context 'pull code' do
+          it { expect(subject).to be_allowed }
+        end
+      end
+
+      describe 'admin user' do
+        let(:user) { create(:admin) }
+
+        context 'when member of the project' do
+          before { project.team << [user, :reporter] }
+
+          context 'pull code' do
+            it { expect(subject).to be_allowed }
+          end
+        end
+
+        context 'when is not member of the project' do
+          context 'pull code' do
+            it { expect(subject).not_to be_allowed }
+          end
+        end
+      end
+    end
   end
 
   describe 'push_access_check' do
@@ -283,38 +320,71 @@ describe Gitlab::GitAccess, lib: true do
     end
   end
 
-  describe 'deploy key permissions' do
-    let(:key) { create(:deploy_key) }
-    let(:actor) { key }
+  shared_examples 'can not push code' do
+    subject { access.check('git-receive-pack', '_any') }
+
+    context 'when project is authorized' do
+      before { authorize }
 
-    context 'push code' do
-      subject { access.check('git-receive-pack', '_any') }
+      it { expect(subject).not_to be_allowed }
+    end
 
-      context 'when project is authorized' do
-        before { key.projects << project }
+    context 'when unauthorized' do
+      context 'to public project' do
+        let(:project) { create(:project, :public) }
 
         it { expect(subject).not_to be_allowed }
       end
 
-      context 'when unauthorized' do
-        context 'to public project' do
-          let(:project) { create(:project, :public) }
+      context 'to internal project' do
+        let(:project) { create(:project, :internal) }
 
-          it { expect(subject).not_to be_allowed }
-        end
+        it { expect(subject).not_to be_allowed }
+      end
 
-        context 'to internal project' do
-          let(:project) { create(:project, :internal) }
+      context 'to private project' do
+        let(:project) { create(:project) }
 
-          it { expect(subject).not_to be_allowed }
-        end
+        it { expect(subject).not_to be_allowed }
+      end
+    end
+  end
 
-        context 'to private project' do
-          let(:project) { create(:project, :internal) }
+  describe 'build authentication abilities' do
+    let(:authentication_abilities) { build_authentication_abilities }
 
-          it { expect(subject).not_to be_allowed }
-        end
+    it_behaves_like 'can not push code' do
+      def authorize
+        project.team << [user, :reporter]
       end
     end
   end
+
+  describe 'deploy key permissions' do
+    let(:key) { create(:deploy_key) }
+    let(:actor) { key }
+
+    it_behaves_like 'can not push code' do
+      def authorize
+        key.projects << project
+      end
+    end
+  end
+
+  private
+
+  def build_authentication_abilities
+    [
+      :read_project,
+      :build_download_code
+    ]
+  end
+
+  def full_authentication_abilities
+    [
+      :read_project,
+      :download_code,
+      :push_code
+    ]
+  end
 end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 4244b80..576cda5 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -1,9 +1,16 @@
 require 'spec_helper'
 
 describe Gitlab::GitAccessWiki, lib: true do
-  let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web') }
+  let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) }
   let(:project) { create(:project) }
   let(:user) { create(:user) }
+  let(:authentication_abilities) do
+    [
+      :read_project,
+      :download_code,
+      :push_code
+    ]
+  end
 
   describe 'push_allowed?' do
     before do
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
new file mode 100644
index 0000000..219198e
--- /dev/null
+++ b/spec/lib/gitlab/git_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::Git, lib: true do
+  let(:committer_email) { FFaker::Internet.email }
+
+  # I have to remove periods from the end of the name
+  # This happened when the user's name had a suffix (i.e. "Sr.")
+  # This seems to be what git does under the hood. For example, this commit:
+  #
+  # $ git commit --author='Foo Sr. <foo at example.com>' -m 'Where's my trailing period?'
+  #
+  # results in this:
+  #
+  # $ git show --pretty
+  # ...
+  # Author: Foo Sr <foo at example.com>
+  # ...
+  let(:committer_name) { FFaker::Name.name.chomp("\.") }
+
+  describe 'committer_hash' do
+    it "returns a hash containing the given email and name" do
+      committer_hash = Gitlab::Git::committer_hash(email: committer_email, name: committer_name)
+
+      expect(committer_hash[:email]).to eq(committer_email)
+      expect(committer_hash[:name]).to eq(committer_name)
+      expect(committer_hash[:time]).to be_a(Time)
+    end
+
+    context 'when email is nil' do
+      it "returns nil" do
+        committer_hash = Gitlab::Git::committer_hash(email: nil, name: committer_name)
+
+        expect(committer_hash).to be_nil
+      end
+    end
+
+    context 'when name is nil' do
+      it "returns nil" do
+        committer_hash = Gitlab::Git::committer_hash(email: committer_email, name: nil)
+
+        expect(committer_hash).to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
index 9ae02a6..c520a9c 100644
--- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -73,6 +73,12 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
         gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
         expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
       end
+
+      it 'returns note without created at tag line' do
+        create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+        expect(comment.attributes.fetch(:note)).to eq("I'm having a problem with this.")
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index b7c3bc4..553c849 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::GithubImport::Importer, lib: true do
   describe '#execute' do
     context 'when an error occurs' do
-      let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_enabled: false) }
+      let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_access_level: ProjectFeature::DISABLED) }
       let(:octocat) { double(id: 123456, login: 'octocat') }
       let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
       let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
@@ -13,7 +13,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
       let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
       let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
 
-      let(:label) do
+      let(:label1) do
         double(
           name: 'Bug',
           color: 'ff0000',
@@ -21,6 +21,14 @@ describe Gitlab::GithubImport::Importer, lib: true do
         )
       end
 
+      let(:label2) do
+        double(
+          name: nil,
+          color: 'ff0000',
+          url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug'
+        )
+      end
+
       let(:milestone) do
         double(
           number: 1347,
@@ -90,14 +98,39 @@ describe Gitlab::GithubImport::Importer, lib: true do
         )
       end
 
+      let(:release1) do
+        double(
+          tag_name: 'v1.0.0',
+          name: 'First release',
+          body: 'Release v1.0.0',
+          draft: false,
+          created_at: created_at,
+          updated_at: updated_at,
+          url: 'https://api.github.com/repos/octocat/Hello-World/releases/1'
+        )
+      end
+
+      let(:release2) do
+        double(
+          tag_name: 'v2.0.0',
+          name: 'Second release',
+          body: nil,
+          draft: false,
+          created_at: created_at,
+          updated_at: updated_at,
+          url: 'https://api.github.com/repos/octocat/Hello-World/releases/2'
+        )
+      end
+
       before do
         allow(project).to receive(:import_data).and_return(double.as_null_object)
         allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
-        allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label, label])
+        allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
         allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
         allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
         allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
         allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
+        allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
         allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
       end
 
@@ -113,14 +146,15 @@ describe Gitlab::GithubImport::Importer, lib: true do
         error = {
           message: 'The remote data could not be fully imported.',
           errors: [
-            { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title has already been taken" },
+            { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
             { type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" },
             { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." },
             { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" },
             { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." },
             { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" },
-            { type: :wiki, errors: "Gitlab::Shell::Error" }
-          ]
+            { type: :wiki, errors: "Gitlab::Shell::Error" },
+            { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" }
+        ]
         }
 
         described_class.new(project).execute
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
index 0e7ffbe..c2f1f6b 100644
--- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -48,8 +48,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
     end
 
     context 'when issue is closed' do
-      let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
-      let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
+      let(:raw_data) { double(base_data.merge(state: 'closed')) }
 
       it 'returns formatted attributes' do
         expected = {
@@ -62,7 +61,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
           author_id: project.creator_id,
           assignee_id: nil,
           created_at: created_at,
-          updated_at: closed_at
+          updated_at: updated_at
         }
 
         expect(issue.attributes).to eq(expected)
@@ -110,6 +109,12 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
 
         expect(issue.attributes.fetch(:author_id)).to eq gl_user.id
       end
+
+      it 'returns description without created at tag line' do
+        create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+        expect(issue.attributes.fetch(:description)).to eq("I'm having a problem with this.")
+      end
     end
   end
 
diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb
index 87593e3..8098754 100644
--- a/spec/lib/gitlab/github_import/label_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb
@@ -1,18 +1,34 @@
 require 'spec_helper'
 
 describe Gitlab::GithubImport::LabelFormatter, lib: true do
-  describe '#attributes' do
-    it 'returns formatted attributes' do
-      project = create(:project)
-      raw = double(name: 'improvements', color: 'e6e6e6')
+  let(:project) { create(:project) }
+  let(:raw) { double(name: 'improvements', color: 'e6e6e6') }
 
-      formatter = described_class.new(project, raw)
+  subject { described_class.new(project, raw) }
 
-      expect(formatter.attributes).to eq({
+  describe '#attributes' do
+    it 'returns formatted attributes' do
+      expect(subject.attributes).to eq({
         project: project,
         title: 'improvements',
         color: '#e6e6e6'
       })
     end
   end
+
+  describe '#create!' do
+    context 'when label does not exist' do
+      it 'creates a new label' do
+        expect { subject.create! }.to change(Label, :count).by(1)
+      end
+    end
+
+    context 'when label exists' do
+      it 'does not create a new label' do
+        project.labels.create(name: raw.name)
+
+        expect { subject.create! }.not_to change(Label, :count)
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
index 5a421e5..09337c9 100644
--- a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
@@ -40,8 +40,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
     end
 
     context 'when milestone is closed' do
-      let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
-      let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
+      let(:raw_data) { double(base_data.merge(state: 'closed')) }
 
       it 'returns formatted attributes' do
         expected = {
@@ -52,7 +51,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
           state: 'closed',
           due_date: nil,
           created_at: created_at,
-          updated_at: closed_at
+          updated_at: updated_at
         }
 
         expect(formatter.attributes).to eq(expected)
diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb
index 0f363b8..ab06b7b 100644
--- a/spec/lib/gitlab/github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/github_import/project_creator_spec.rb
@@ -2,33 +2,59 @@ require 'spec_helper'
 
 describe Gitlab::GithubImport::ProjectCreator, lib: true do
   let(:user) { create(:user) }
+  let(:namespace) { create(:group, owner: user) }
+
   let(:repo) do
     OpenStruct.new(
       login: 'vim',
       name: 'vim',
-      private: true,
       full_name: 'asd/vim',
-      clone_url: "https://gitlab.com/asd/vim.git",
-      owner: OpenStruct.new(login: "john")
+      clone_url: 'https://gitlab.com/asd/vim.git'
     )
   end
-  let(:namespace) { create(:group, owner: user) }
-  let(:token) { "asdffg" }
-  let(:access_params) { { github_access_token: token } }
+
+  subject(:service) { described_class.new(repo, repo.name, namespace, user, github_access_token: 'asdffg') }
 
   before do
     namespace.add_owner(user)
+    allow_any_instance_of(Project).to receive(:add_import_job)
   end
 
-  it 'creates project' do
-    allow_any_instance_of(Project).to receive(:add_import_job)
+  describe '#execute' do
+    it 'creates a project' do
+      expect { service.execute }.to change(Project, :count).by(1)
+    end
+
+    it 'handle GitHub credentials' do
+      project = service.execute
+
+      expect(project.import_url).to eq('https://asdffg@gitlab.com/asd/vim.git')
+      expect(project.safe_import_url).to eq('https://*****@gitlab.com/asd/vim.git')
+      expect(project.import_data.credentials).to eq(user: 'asdffg', password: nil)
+    end
+
+    context 'when Github project is private' do
+      it 'sets project visibility to private' do
+        repo.private = true
+
+        project = service.execute
+
+        expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+      end
+    end
+
+    context 'when Github project is public' do
+      before do
+        allow_any_instance_of(ApplicationSetting).to receive(:default_project_visibility).and_return(Gitlab::VisibilityLevel::INTERNAL)
+      end
+
+      it 'sets project visibility to the default project visibility' do
+        repo.private = false
 
-    project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user, access_params)
-    project = project_creator.execute
+        project = service.execute
 
-    expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git")
-    expect(project.safe_import_url).to eq("https://*****@gitlab.com/asd/vim.git")
-    expect(project.import_data.credentials).to eq(user: "asdffg", password: nil)
-    expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+        expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+      end
+    end
   end
 end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index b667abf..302f0fc 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -62,8 +62,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
     end
 
     context 'when pull request is closed' do
-      let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
-      let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
+      let(:raw_data) { double(base_data.merge(state: 'closed')) }
 
       it 'returns formatted attributes' do
         expected = {
@@ -81,7 +80,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
           author_id: project.creator_id,
           assignee_id: nil,
           created_at: created_at,
-          updated_at: closed_at
+          updated_at: updated_at
         }
 
         expect(pull_request.attributes).to eq(expected)
@@ -108,7 +107,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
           author_id: project.creator_id,
           assignee_id: nil,
           created_at: created_at,
-          updated_at: merged_at
+          updated_at: updated_at
         }
 
         expect(pull_request.attributes).to eq(expected)
@@ -141,6 +140,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
 
         expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
       end
+
+      it 'returns description without created at tag line' do
+        create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+        expect(pull_request.attributes.fetch(:description)).to eq('Please pull these awesome changes')
+      end
     end
 
     context 'when it has a milestone' do
diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb
new file mode 100644
index 0000000..793128c
--- /dev/null
+++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::ReleaseFormatter, lib: true do
+  let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
+  let(:octocat) { double(id: 123456, login: 'octocat') }
+  let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+
+  let(:base_data) do
+    {
+      tag_name: 'v1.0.0',
+      name: 'First release',
+      draft: false,
+      created_at: created_at,
+      published_at: created_at,
+      body: 'Release v1.0.0'
+    }
+  end
+
+  subject(:release) { described_class.new(project, raw_data) }
+
+  describe '#attributes' do
+    let(:raw_data) { double(base_data) }
+
+    it 'returns formatted attributes' do
+      expected = {
+        project: project,
+        tag: 'v1.0.0',
+        description: 'Release v1.0.0',
+        created_at: created_at,
+        updated_at: created_at
+      }
+
+      expect(release.attributes).to eq(expected)
+    end
+  end
+
+  describe '#valid' do
+    context 'when release is not a draft' do
+      let(:raw_data) { double(base_data) }
+
+      it 'returns true' do
+        expect(release.valid?).to eq true
+      end
+    end
+
+    context 'when release is draft' do
+      let(:raw_data) { double(base_data.merge(draft: true)) }
+
+      it 'returns false' do
+        expect(release.valid?).to eq false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb
index d3f1deb..9b499b5 100644
--- a/spec/lib/gitlab/gitlab_import/importer_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb
@@ -13,6 +13,7 @@ describe Gitlab::GitlabImport::Importer, lib: true do
           'title' => 'Issue',
           'description' => 'Lorem ipsum',
           'state' => 'opened',
+          'confidential' => true,
           'author' => {
             'id' => 283999,
             'name' => 'John Doe'
@@ -34,6 +35,7 @@ describe Gitlab::GitlabImport::Importer, lib: true do
         title: 'Issue',
         description: "*Created by: John Doe*\n\nLorem ipsum",
         state: 'opened',
+        confidential: true,
         author_id: project.creator_id
       }
 
diff --git a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb
deleted file mode 100644
index 946712c..0000000
--- a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::GitoriousImport::ProjectCreator, lib: true do
-  let(:user) { create(:user) }
-  let(:repo) { Gitlab::GitoriousImport::Repository.new('foo/bar-baz-qux') }
-  let(:namespace){ create(:group, owner: user) }
-
-  before do
-    namespace.add_owner(user)
-  end
-
-  it 'creates project' do
-    allow_any_instance_of(Project).to receive(:add_import_job)
-
-    project_creator = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, user)
-    project = project_creator.execute
-
-    expect(project.name).to eq("Bar Baz Qux")
-    expect(project.path).to eq("bar-baz-qux")
-    expect(project.namespace).to eq(namespace)
-    expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
-    expect(project.import_type).to eq("gitorious")
-    expect(project.import_source).to eq("foo/bar-baz-qux")
-    expect(project.import_url).to eq("https://gitorious.org/foo/bar-baz-qux.git")
-  end
-end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
new file mode 100644
index 0000000..0065692
--- /dev/null
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -0,0 +1,187 @@
+---
+issues:
+- subscriptions
+- award_emoji
+- author
+- assignee
+- updated_by
+- milestone
+- notes
+- label_links
+- labels
+- todos
+- user_agent_detail
+- moved_to
+- events
+- merge_requests_closing_issues
+- metrics
+events:
+- author
+- project
+- target
+notes:
+- award_emoji
+- project
+- noteable
+- author
+- updated_by
+- resolved_by
+- todos
+- events
+label_links:
+- target
+- label
+label:
+- subscriptions
+- project
+- lists
+- label_links
+- issues
+- merge_requests
+milestone:
+- project
+- issues
+- labels
+- merge_requests
+- participants
+- events
+snippets:
+- author
+- project
+- notes
+- award_emoji
+releases:
+- project
+project_members:
+- created_by
+- user
+- source
+- project
+merge_requests:
+- subscriptions
+- award_emoji
+- author
+- assignee
+- updated_by
+- milestone
+- notes
+- label_links
+- labels
+- todos
+- target_project
+- source_project
+- merge_user
+- merge_request_diffs
+- merge_request_diff
+- events
+- merge_requests_closing_issues
+- metrics
+merge_request_diff:
+- merge_request
+pipelines:
+- project
+- user
+- statuses
+- builds
+- trigger_requests
+statuses:
+- project
+- pipeline
+- user
+variables:
+- project
+triggers:
+- project
+- trigger_requests
+deploy_keys:
+- user
+- deploy_keys_projects
+- projects
+services:
+- project
+- service_hook
+hooks:
+- project
+protected_branches:
+- project
+- merge_access_levels
+- push_access_levels
+merge_access_levels:
+- protected_branch
+push_access_levels:
+- protected_branch
+project:
+- taggings
+- base_tags
+- tag_taggings
+- tags
+- creator
+- group
+- namespace
+- board
+- last_event
+- services
+- campfire_service
+- drone_ci_service
+- emails_on_push_service
+- builds_email_service
+- irker_service
+- pivotaltracker_service
+- hipchat_service
+- flowdock_service
+- assembla_service
+- asana_service
+- gemnasium_service
+- slack_service
+- buildkite_service
+- bamboo_service
+- teamcity_service
+- pushover_service
+- jira_service
+- redmine_service
+- custom_issue_tracker_service
+- bugzilla_service
+- gitlab_issue_tracker_service
+- external_wiki_service
+- forked_project_link
+- forked_from_project
+- forked_project_links
+- forks
+- merge_requests
+- fork_merge_requests
+- issues
+- labels
+- events
+- milestones
+- notes
+- snippets
+- hooks
+- protected_branches
+- project_members
+- users
+- requesters
+- deploy_keys_projects
+- deploy_keys
+- users_star_projects
+- starrers
+- releases
+- lfs_objects_projects
+- lfs_objects
+- project_group_links
+- invited_groups
+- todos
+- notification_settings
+- import_data
+- commit_statuses
+- pipelines
+- builds
+- runner_projects
+- runners
+- variables
+- triggers
+- environments
+- deployments
+- project_feature
+award_emoji:
+- awardable
+- user
\ No newline at end of file
diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
new file mode 100644
index 0000000..2ba3440
--- /dev/null
+++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+# Part of the test security suite for the Import/Export feature
+# Checks whether there are new attributes in models that are currently being exported as part of the
+# project Import/Export feature.
+# If there are new attributes, these will have to either be added to this spec in case we want them
+# to be included as part of the export, or blacklist them using the import_export.yml configuration file.
+# Likewise, new models added to import_export.yml, will need to be added with their correspondent attributes
+# to this spec.
+describe 'Import/Export attribute configuration', lib: true do
+  include ConfigurationHelper
+
+  let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
+  let(:relation_names) do
+    names = names_from_tree(config_hash['project_tree'])
+
+    # Remove duplicated or add missing models
+    # - project is not part of the tree, so it has to be added manually.
+    # - milestone, labels have both singular and plural versions in the tree, so remove the duplicates.
+    names.flatten.uniq - ['milestones', 'labels'] + ['project']
+  end
+
+  let(:safe_attributes_file) { 'spec/lib/gitlab/import_export/safe_model_attributes.yml' }
+  let(:safe_model_attributes) { YAML.load_file(safe_attributes_file) }
+
+  it 'has no new columns' do
+    relation_names.each do |relation_name|
+      relation_class = relation_class_for_name(relation_name)
+
+      expect(safe_model_attributes[relation_class.to_s]).not_to be_nil, "Expected exported class #{relation_class.to_s} to exist in safe_model_attributes"
+
+      current_attributes = parsed_attributes(relation_name, relation_class.attribute_names)
+      safe_attributes = safe_model_attributes[relation_class.to_s]
+      new_attributes = current_attributes - safe_attributes
+
+      expect(new_attributes).to be_empty, failure_message(relation_class.to_s, new_attributes)
+    end
+  end
+
+  def failure_message(relation_class, new_attributes)
+    <<-MSG
+      It looks like #{relation_class}, which is exported using the project Import/Export, has new attributes: #{new_attributes.join(',')}
+
+      Please add the attribute(s) to SAFE_MODEL_ATTRIBUTES if you consider this can be exported.
+      Otherwise, please blacklist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its correspondent
+      model in the +excluded_attributes+ section.
+
+      SAFE_MODEL_ATTRIBUTES: #{File.expand_path(safe_attributes_file)}
+      IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+    MSG
+  end
+
+  class Author < User
+  end
+end
diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb
new file mode 100644
index 0000000..9b492d1
--- /dev/null
+++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+# Part of the test security suite for the Import/Export feature
+# Finds if a new model has been added that can potentially be part of the Import/Export
+# If it finds a new model, it will show a +failure_message+ with the options available.
+describe 'Import/Export model configuration', lib: true do
+  include ConfigurationHelper
+
+  let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
+  let(:model_names) do
+    names = names_from_tree(config_hash['project_tree'])
+
+    # Remove duplicated or add missing models
+    # - project is not part of the tree, so it has to be added manually.
+    # - milestone, labels have both singular and plural versions in the tree, so remove the duplicates.
+    # - User, Author... Models we do not care about for checking models
+    names.flatten.uniq - ['milestones', 'labels', 'user', 'author'] + ['project']
+  end
+
+  let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' }
+  let(:all_models) { YAML.load_file(all_models_yml) }
+  let(:current_models) { setup_models }
+
+  it 'has no new models' do
+    model_names.each do |model_name|
+      new_models = Array(current_models[model_name]) - Array(all_models[model_name])
+      expect(new_models).to be_empty, failure_message(model_name.classify, new_models)
+    end
+  end
+
+  # List of current models between models, in the format of
+  # {model: [model_2, model3], ...}
+  def setup_models
+    all_models_hash = {}
+
+    model_names.each do |model_name|
+      model_class = relation_class_for_name(model_name)
+
+      all_models_hash[model_name] = associations_for(model_class) - ['project']
+    end
+
+    all_models_hash
+  end
+
+  def failure_message(parent_model_name, new_models)
+    <<-MSG
+      New model(s) <#{new_models.join(',')}> have been added, related to #{parent_model_name}, which is exported by
+      the Import/Export feature.
+
+      If you think this model should be included in the export, please add it to IMPORT_EXPORT_CONFIG.
+      Definitely add it to MODELS_JSON to signal that you've handled this error and to prevent it from showing up in the future.
+
+      MODELS_JSON: #{File.expand_path(all_models_yml)}
+      IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+    MSG
+  end
+end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index cbbf98d..281f6cf 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -1,9 +1,5 @@
 {
   "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
-  "issues_enabled": true,
-  "merge_requests_enabled": true,
-  "wiki_enabled": true,
-  "snippets_enabled": false,
   "visibility_level": 10,
   "archived": false,
   "issues": [
@@ -28,7 +24,7 @@
       "test_ee_field": "test",
       "milestone": {
         "id": 1,
-        "title": "v0.0",
+        "title": "test milestone",
         "project_id": 8,
         "description": "test milestone",
         "due_date": null,
@@ -55,7 +51,7 @@
         {
           "id": 2,
           "label_id": 2,
-          "target_id": 3,
+          "target_id": 40,
           "target_type": "Issue",
           "created_at": "2016-07-22T08:57:02.840Z",
           "updated_at": "2016-07-22T08:57:02.840Z",
@@ -285,6 +281,31 @@
       "deleted_at": null,
       "due_date": null,
       "moved_to_id": null,
+      "milestone": {
+        "id": 1,
+        "title": "test milestone",
+        "project_id": 8,
+        "description": "test milestone",
+        "due_date": null,
+        "created_at": "2016-06-14T15:02:04.415Z",
+        "updated_at": "2016-06-14T15:02:04.415Z",
+        "state": "active",
+        "iid": 1,
+        "events": [
+          {
+            "id": 487,
+            "target_type": "Milestone",
+            "target_id": 1,
+            "title": null,
+            "data": null,
+            "project_id": 46,
+            "created_at": "2016-06-14T15:02:04.418Z",
+            "updated_at": "2016-06-14T15:02:04.418Z",
+            "action": 1,
+            "author_id": 18
+          }
+        ]
+      },
       "notes": [
         {
           "id": 359,
@@ -498,6 +519,27 @@
       "deleted_at": null,
       "due_date": null,
       "moved_to_id": null,
+      "label_links": [
+        {
+          "id": 99,
+          "label_id": 2,
+          "target_id": 38,
+          "target_type": "Issue",
+          "created_at": "2016-07-22T08:57:02.840Z",
+          "updated_at": "2016-07-22T08:57:02.840Z",
+          "label": {
+            "id": 2,
+            "title": "test2",
+            "color": "#428bca",
+            "project_id": 8,
+            "created_at": "2016-07-22T08:55:44.161Z",
+            "updated_at": "2016-07-22T08:55:44.161Z",
+            "template": false,
+            "description": "",
+            "priority": null
+          }
+        }
+      ],
       "notes": [
         {
           "id": 367,
@@ -6482,7 +6524,7 @@
     {
       "id": 37,
       "project_id": 5,
-      "ref": "master",
+      "ref": null,
       "sha": "048721d90c449b244b7b4c53a9186b04330174ec",
       "before_sha": null,
       "push_data": null,
@@ -7305,6 +7347,30 @@
 
   ],
   "protected_branches": [
-
+    {
+      "id": 1,
+      "project_id": 9,
+      "name": "master",
+      "created_at": "2016-08-30T07:32:52.426Z",
+      "updated_at": "2016-08-30T07:32:52.426Z",
+      "merge_access_levels": [
+        {
+          "id": 1,
+          "protected_branch_id": 1,
+          "access_level": 40,
+          "created_at": "2016-08-30T07:32:52.458Z",
+          "updated_at": "2016-08-30T07:32:52.458Z"
+        }
+      ],
+      "push_access_levels": [
+        {
+          "id": 1,
+          "protected_branch_id": 1,
+          "access_level": 40,
+          "created_at": "2016-08-30T07:32:52.490Z",
+          "updated_at": "2016-08-30T07:32:52.490Z"
+        }
+      ]
+    }
   ]
 }
\ No newline at end of file
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 4d85794..feacb29 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
     let(:user) { create(:user) }
     let(:namespace) { create(:namespace, owner: user) }
     let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
-    let(:project) { create(:empty_project, name: 'project', path: 'project') }
+    let!(:project) { create(:empty_project, name: 'project', path: 'project', builds_access_level: ProjectFeature::DISABLED, issues_access_level: ProjectFeature::DISABLED) }
     let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
     let(:restored_project_json) { project_tree_restorer.restore }
 
@@ -18,12 +18,41 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
         expect(restored_project_json).to be true
       end
 
+      it 'restore correct project features' do
+        restored_project_json
+        project = Project.find_by_path('project')
+
+        expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
+        expect(project.project_feature.builds_access_level).to eq(ProjectFeature::DISABLED)
+        expect(project.project_feature.snippets_access_level).to eq(ProjectFeature::ENABLED)
+        expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::ENABLED)
+        expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED)
+      end
+
+      it 'has the same label associated to two issues' do
+        restored_project_json
+
+        expect(Label.first.issues.count).to eq(2)
+      end
+
+      it 'has milestones associated to two separate issues' do
+        restored_project_json
+
+        expect(Milestone.find_by_description('test milestone').issues.count).to eq(2)
+      end
+
       it 'creates a valid pipeline note' do
         restored_project_json
 
         expect(Ci::Pipeline.first.notes).not_to be_empty
       end
 
+      it 'restores pipelines with missing ref' do
+        restored_project_json
+
+        expect(Ci::Pipeline.where(ref: nil)).not_to be_empty
+      end
+
       it 'restores the correct event with symbolised data' do
         restored_project_json
 
@@ -38,6 +67,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
         expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
       end
 
+      it 'contains the merge access levels on a protected branch' do
+        restored_project_json
+
+        expect(ProtectedBranch.first.merge_access_levels).not_to be_empty
+      end
+
+      it 'contains the push access levels on a protected branch' do
+        restored_project_json
+
+        expect(ProtectedBranch.first.push_access_levels).not_to be_empty
+      end
+
       context 'event at forth level of the tree' do
         let(:event) { Event.where(title: 'test levels').first }
 
@@ -66,12 +107,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
         expect(Label.first.label_links.first.target).not_to be_nil
       end
 
-      it 'has milestones associated to issues' do
-        restored_project_json
-
-        expect(Milestone.find_by_description('test milestone').issues).not_to be_empty
-      end
-
       context 'Merge requests' do
         before do
           restored_project_json
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 3a86a4c..d891c2d 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -111,6 +111,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
         expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
       end
 
+      it 'has project feature' do
+        project_feature = saved_project_json['project_feature']
+        expect(project_feature).not_to be_empty
+        expect(project_feature["issues_access_level"]).to eq(ProjectFeature::DISABLED)
+        expect(project_feature["wiki_access_level"]).to eq(ProjectFeature::ENABLED)
+        expect(project_feature["builds_access_level"]).to eq(ProjectFeature::PRIVATE)
+      end
+
       it 'does not complain about non UTF-8 characters in MR diffs' do
         ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n    LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n    KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n    YXR'")
 
@@ -154,6 +162,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
 
     create(:event, target: milestone, project: project, action: Event::CREATED, author: user)
 
+    project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+    project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
+    project.project_feature.update_attribute(:builds_access_level, ProjectFeature::PRIVATE)
+
     project
   end
 
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index b6dec41..3ceb1e7 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -32,6 +32,12 @@ describe Gitlab::ImportExport::Reader, lib: true  do
       expect(described_class.new(shared: shared).project_tree).to match(include: [:issues])
     end
 
+    it 'generates the correct hash for a single project feature relation' do
+      setup_yaml(project_tree: [:project_feature])
+
+      expect(described_class.new(shared: shared).project_tree).to match(include: [:project_feature])
+    end
+
     it 'generates the correct hash for a multiple project relation' do
       setup_yaml(project_tree: [:issues, :snippets])
 
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
new file mode 100644
index 0000000..8bccd31
--- /dev/null
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -0,0 +1,330 @@
+---
+Issue:
+- id
+- title
+- assignee_id
+- author_id
+- project_id
+- created_at
+- updated_at
+- position
+- branch_name
+- description
+- state
+- iid
+- updated_by_id
+- confidential
+- deleted_at
+- due_date
+- moved_to_id
+- lock_version
+- milestone_id
+- weight
+Event:
+- id
+- target_type
+- target_id
+- title
+- data
+- project_id
+- created_at
+- updated_at
+- action
+- author_id
+Note:
+- id
+- note
+- noteable_type
+- author_id
+- created_at
+- updated_at
+- project_id
+- attachment
+- line_code
+- commit_id
+- noteable_id
+- system
+- st_diff
+- updated_by_id
+- type
+- position
+- original_position
+- resolved_at
+- resolved_by_id
+- discussion_id
+- original_discussion_id
+LabelLink:
+- id
+- label_id
+- target_id
+- target_type
+- created_at
+- updated_at
+Label:
+- id
+- title
+- color
+- project_id
+- created_at
+- updated_at
+- template
+- description
+- priority
+Milestone:
+- id
+- title
+- project_id
+- description
+- due_date
+- created_at
+- updated_at
+- state
+- iid
+ProjectSnippet:
+- id
+- title
+- content
+- author_id
+- project_id
+- created_at
+- updated_at
+- file_name
+- type
+- visibility_level
+Release:
+- id
+- tag
+- description
+- project_id
+- created_at
+- updated_at
+ProjectMember:
+- id
+- access_level
+- source_id
+- source_type
+- user_id
+- notification_level
+- type
+- created_at
+- updated_at
+- created_by_id
+- invite_email
+- invite_token
+- invite_accepted_at
+- requested_at
+- expires_at
+User:
+- id
+- username
+- email
+MergeRequest:
+- id
+- target_branch
+- source_branch
+- source_project_id
+- author_id
+- assignee_id
+- title
+- created_at
+- updated_at
+- state
+- merge_status
+- target_project_id
+- iid
+- description
+- position
+- locked_at
+- updated_by_id
+- merge_error
+- merge_params
+- merge_when_build_succeeds
+- merge_user_id
+- merge_commit_sha
+- deleted_at
+- in_progress_merge_commit_sha
+- lock_version
+- milestone_id
+- approvals_before_merge
+- rebase_commit_sha
+MergeRequestDiff:
+- id
+- state
+- st_commits
+- merge_request_id
+- created_at
+- updated_at
+- base_commit_sha
+- real_size
+- head_commit_sha
+- start_commit_sha
+Ci::Pipeline:
+- id
+- project_id
+- ref
+- sha
+- before_sha
+- push_data
+- created_at
+- updated_at
+- tag
+- yaml_errors
+- committed_at
+- gl_project_id
+- status
+- started_at
+- finished_at
+- duration
+- user_id
+CommitStatus:
+- id
+- project_id
+- status
+- finished_at
+- trace
+- created_at
+- updated_at
+- started_at
+- runner_id
+- coverage
+- commit_id
+- commands
+- job_id
+- name
+- deploy
+- options
+- allow_failure
+- stage
+- trigger_request_id
+- stage_idx
+- tag
+- ref
+- user_id
+- type
+- target_url
+- description
+- artifacts_file
+- gl_project_id
+- artifacts_metadata
+- erased_by_id
+- erased_at
+- artifacts_expire_at
+- environment
+- artifacts_size
+- when
+- yaml_variables
+- queued_at
+- token
+Ci::Variable:
+- id
+- project_id
+- key
+- value
+- encrypted_value
+- encrypted_value_salt
+- encrypted_value_iv
+- gl_project_id
+Ci::Trigger:
+- id
+- token
+- project_id
+- deleted_at
+- created_at
+- updated_at
+- gl_project_id
+DeployKey:
+- id
+- user_id
+- created_at
+- updated_at
+- key
+- title
+- type
+- fingerprint
+- public
+Service:
+- id
+- type
+- title
+- project_id
+- created_at
+- updated_at
+- active
+- properties
+- template
+- push_events
+- issues_events
+- merge_requests_events
+- tag_push_events
+- note_events
+- pipeline_events
+- build_events
+- category
+- default
+- wiki_page_events
+- confidential_issues_events
+ProjectHook:
+- id
+- url
+- project_id
+- created_at
+- updated_at
+- type
+- service_id
+- push_events
+- issues_events
+- merge_requests_events
+- tag_push_events
+- note_events
+- pipeline_events
+- enable_ssl_verification
+- build_events
+- wiki_page_events
+- token
+- group_id
+- confidential_issues_events
+ProtectedBranch:
+- id
+- project_id
+- name
+- created_at
+- updated_at
+Project:
+- description
+- issues_enabled
+- merge_requests_enabled
+- wiki_enabled
+- snippets_enabled
+- visibility_level
+- archived
+Author:
+- name
+ProjectFeature:
+- id
+- project_id
+- merge_requests_access_level
+- issues_access_level
+- wiki_access_level
+- snippets_access_level
+- builds_access_level
+- created_at
+- updated_at
+ProtectedBranch::MergeAccessLevel:
+- id
+- protected_branch_id
+- access_level
+- created_at
+- updated_at
+ProtectedBranch::PushAccessLevel:
+- id
+- protected_branch_id
+- access_level
+- created_at
+- updated_at
+AwardEmoji:
+- id
+- user_id
+- name
+- awardable_type
+- created_at
+- updated_at
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index 90c6d1c..c680e71 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -23,7 +23,7 @@ describe Gitlab::ImportExport::VersionChecker, services: true do
       it 'shows the correct error message' do
         described_class.check!(shared: shared)
 
-        expect(shared.errors.first).to eq("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
+        expect(shared.errors.first).to eq("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
       end
     end
   end
diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb
index 4847b5f..0600893 100644
--- a/spec/lib/gitlab/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/ldap/adapter_spec.rb
@@ -1,12 +1,77 @@
 require 'spec_helper'
 
 describe Gitlab::LDAP::Adapter, lib: true do
-  let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' }
+  include LdapHelpers
+
+  let(:ldap) { double(:ldap) }
+  let(:adapter) { ldap_adapter('ldapmain', ldap) }
+
+  describe '#users' do
+    before do
+      stub_ldap_config(base: 'dc=example,dc=com')
+    end
+
+    it 'searches with the proper options when searching by uid' do
+      # Requires this expectation style to match the filter
+      expect(adapter).to receive(:ldap_search) do |arg|
+        expect(arg[:filter].to_s).to eq('(uid=johndoe)')
+        expect(arg[:base]).to eq('dc=example,dc=com')
+        expect(arg[:attributes]).to match(%w{uid cn mail dn})
+      end.and_return({})
+
+      adapter.users('uid', 'johndoe')
+    end
+
+    it 'searches with the proper options when searching by dn' do
+      expect(adapter).to receive(:ldap_search).with(
+        base: 'uid=johndoe,ou=users,dc=example,dc=com',
+        scope: Net::LDAP::SearchScope_BaseObject,
+        attributes: %w{uid cn mail dn},
+        filter: nil
+      ).and_return({})
+
+      adapter.users('dn', 'uid=johndoe,ou=users,dc=example,dc=com')
+    end
+
+    it 'searches with the proper options when searching with a limit' do
+      expect(adapter)
+        .to receive(:ldap_search).with(hash_including(size: 100)).and_return({})
+
+      adapter.users('uid', 'johndoe', 100)
+    end
+
+    it 'returns an LDAP::Person if search returns a result' do
+      entry = ldap_user_entry('johndoe')
+      allow(adapter).to receive(:ldap_search).and_return([entry])
+
+      results = adapter.users('uid', 'johndoe')
+
+      expect(results.size).to eq(1)
+      expect(results.first.uid).to eq('johndoe')
+    end
+
+    it 'returns empty array if search entry does not respond to uid' do
+      entry = Net::LDAP::Entry.new
+      entry['dn'] = user_dn('johndoe')
+      allow(adapter).to receive(:ldap_search).and_return([entry])
+
+      results = adapter.users('uid', 'johndoe')
+
+      expect(results).to be_empty
+    end
+
+    it 'uses the right uid attribute when non-default' do
+      stub_ldap_config(uid: 'sAMAccountName')
+      expect(adapter).to receive(:ldap_search).with(
+        hash_including(attributes: %w{sAMAccountName cn mail dn})
+      ).and_return({})
+
+      adapter.users('sAMAccountName', 'johndoe')
+    end
+  end
 
   describe '#dn_matches_filter?' do
-    let(:ldap) { double(:ldap) }
     subject { adapter.dn_matches_filter?(:dn, :filter) }
-    before { allow(adapter).to receive(:ldap).and_return(ldap) }
 
     context "when the search is successful" do
       context "and the result is non-empty" do
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
new file mode 100644
index 0000000..9f04f67
--- /dev/null
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Gitlab::LfsToken, lib: true do
+  describe '#generate and #value' do
+    shared_examples 'an LFS token generator' do
+      it 'returns a randomly generated token' do
+        token = handler.generate
+
+        expect(token).not_to be_nil
+        expect(token).to be_a String
+        expect(token.length).to eq 50
+      end
+
+      it 'returns the correct token based on the key' do
+        token = handler.generate
+
+        expect(handler.value).to eq(token)
+      end
+    end
+
+    context 'when the actor is a user' do
+      let(:actor) { create(:user) }
+      let(:handler) { described_class.new(actor) }
+
+      it_behaves_like 'an LFS token generator'
+
+      it 'returns the correct username' do
+        expect(handler.actor_name).to eq(actor.username)
+      end
+
+      it 'returns the correct token type' do
+        expect(handler.type).to eq(:lfs_token)
+      end
+    end
+
+    context 'when the actor is a deploy key' do
+      let(:actor) { create(:deploy_key) }
+      let(:handler) { described_class.new(actor) }
+
+      it_behaves_like 'an LFS token generator'
+
+      it 'returns the correct username' do
+        expect(handler.actor_name).to eq("lfs+deploy-key-#{actor.id}")
+      end
+
+      it 'returns the correct token type' do
+        expect(handler.type).to eq(:lfs_deploy_token)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index a30cb2a..bcaffd2 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::Metrics::RackMiddleware do
     end
 
     it 'tags a transaction with the name and action of a controller' do
-      klass      = double(:klass, name: 'TestController')
+      klass      = double(:klass, name: 'TestController', content_type: 'text/html')
       controller = double(:controller, class: klass, action_name: 'show')
 
       env['action_controller.instance'] = controller
@@ -32,7 +32,7 @@ describe Gitlab::Metrics::RackMiddleware do
       middleware.call(env)
     end
 
-    it 'tags a transaction with the method andpath of the route in the grape endpoint' do
+    it 'tags a transaction with the method and path of the route in the grape endpoint' do
       route    = double(:route, route_method: "GET", route_path: "/:version/projects/:id/archive(.:format)")
       endpoint = double(:endpoint, route: route)
 
@@ -87,17 +87,30 @@ describe Gitlab::Metrics::RackMiddleware do
 
   describe '#tag_controller' do
     let(:transaction) { middleware.transaction_from_env(env) }
+    let(:content_type) { 'text/html' }
 
-    it 'tags a transaction with the name and action of a controller' do
+    before do
       klass      = double(:klass, name: 'TestController')
-      controller = double(:controller, class: klass, action_name: 'show')
+      controller = double(:controller, class: klass, action_name: 'show', content_type: content_type)
 
       env['action_controller.instance'] = controller
+    end
 
+    it 'tags a transaction with the name and action of a controller' do
       middleware.tag_controller(transaction, env)
 
       expect(transaction.action).to eq('TestController#show')
     end
+
+    context 'when the response content type is not :html' do
+      let(:content_type) { 'application/json' }
+
+      it 'appends the mime type to the transaction action' do
+        middleware.tag_controller(transaction, env)
+
+        expect(transaction.action).to eq('TestController#show.json')
+      end
+    end
   end
 
   describe '#tag_endpoint' do
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index e8b2364..4ae216d 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -40,4 +40,13 @@ describe 'Gitlab::Popen', lib: true, no_db: true do
     it { expect(@status).to be_zero }
     it { expect(@output).to include('spec') }
   end
+
+  context 'use stdin' do
+    before do
+      @output, @status = @klass.new.popen(%w[cat]) { |stdin| stdin.write 'hello' }
+    end
+  
+    it { expect(@status).to be_zero }
+    it { expect(@output).to eq('hello') }
+  end
 end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 8a656ab..dfbefad 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -12,12 +12,6 @@ describe Gitlab::SearchResults do
   let!(:milestone) { create(:milestone, project: project, title: 'foo') }
   let(:results) { described_class.new(user, Project.all, 'foo') }
 
-  describe '#total_count' do
-    it 'returns the total amount of search hits' do
-      expect(results.total_count).to eq(4)
-    end
-  end
-
   describe '#projects_count' do
     it 'returns the total amount of projects' do
       expect(results.projects_count).to eq(1)
@@ -42,18 +36,6 @@ describe Gitlab::SearchResults do
     end
   end
 
-  describe '#empty?' do
-    it 'returns true when there are no search results' do
-      allow(results).to receive(:total_count).and_return(0)
-
-      expect(results.empty?).to eq(true)
-    end
-
-    it 'returns false when there are search results' do
-      expect(results.empty?).to eq(false)
-    end
-  end
-
   describe 'confidential issues' do
     let(:project_1) { create(:empty_project) }
     let(:project_2) { create(:empty_project) }
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index e86b9ef..b661a89 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -5,12 +5,6 @@ describe Gitlab::SnippetSearchResults do
 
   let(:results) { described_class.new(Snippet.all, 'foo') }
 
-  describe '#total_count' do
-    it 'returns the total amount of search hits' do
-      expect(results.total_count).to eq(2)
-    end
-  end
-
   describe '#snippet_titles_count' do
     it 'returns the amount of matched snippet titles' do
       expect(results.snippet_titles_count).to eq(1)
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index c5c1402..6c7fa7e 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Workhorse, lib: true do
   let(:project) { create(:project) }
   let(:subject) { Gitlab::Workhorse }
 
-  describe "#send_git_archive" do
+  describe ".send_git_archive" do
     context "when the repository doesn't have an archive file path" do
       before do
         allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
@@ -15,4 +15,93 @@ describe Gitlab::Workhorse, lib: true do
       end
     end
   end
+
+  describe ".secret" do
+    subject { described_class.secret }
+
+    before do
+      described_class.instance_variable_set(:@secret, nil)
+      described_class.write_secret
+    end
+
+    it 'returns 32 bytes' do
+      expect(subject).to be_a(String)
+      expect(subject.length).to eq(32)
+      expect(subject.encoding).to eq(Encoding::ASCII_8BIT)
+    end
+
+    it 'accepts a trailing newline' do
+      open(described_class.secret_path, 'a') { |f| f.write "\n" }
+      expect(subject.length).to eq(32)
+    end
+
+    it 'raises an exception if the secret file cannot be read' do
+      File.delete(described_class.secret_path)
+      expect { subject }.to raise_exception(Errno::ENOENT)
+    end
+
+    it 'raises an exception if the secret file contains the wrong number of bytes' do
+      File.truncate(described_class.secret_path, 0)
+      expect { subject }.to raise_exception(RuntimeError)
+    end
+  end
+
+  describe ".write_secret" do
+    let(:secret_path) { described_class.secret_path }
+    before do
+      begin
+        File.delete(secret_path)
+      rescue Errno::ENOENT
+      end
+
+      described_class.write_secret
+    end
+
+    it 'uses mode 0600' do
+      expect(File.stat(secret_path).mode & 0777).to eq(0600)
+    end
+
+    it 'writes base64 data' do
+      bytes = Base64.strict_decode64(File.read(secret_path))
+      expect(bytes).not_to be_empty
+    end
+  end
+
+  describe '#verify_api_request!' do
+    let(:header_key) { described_class::INTERNAL_API_REQUEST_HEADER }
+    let(:payload) { { 'iss' => 'gitlab-workhorse' } }
+
+    it 'accepts a correct header' do
+      headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') }
+      expect { call_verify(headers) }.not_to raise_error
+    end
+
+    it 'raises an error when the header is not set' do
+      expect { call_verify({}) }.to raise_jwt_error
+    end
+
+    it 'raises an error when the header is not signed' do
+      headers = { header_key => JWT.encode(payload, nil, 'none') }
+      expect { call_verify(headers) }.to raise_jwt_error
+    end
+
+    it 'raises an error when the header is signed with the wrong key' do
+      headers = { header_key => JWT.encode(payload, 'wrongkey', 'HS256') }
+      expect { call_verify(headers) }.to raise_jwt_error
+    end
+
+    it 'raises an error when the issuer is incorrect' do
+      payload['iss'] = 'somebody else'
+      headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') }
+      expect { call_verify(headers) }.to raise_jwt_error
+    end
+
+    def raise_jwt_error
+      raise_error(JWT::DecodeError)
+    end
+
+    def call_verify(headers)
+      described_class.verify_api_request!(headers)
+    end
+  end
 end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index eae9c06..0363bc7 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -861,7 +861,7 @@ describe Notify do
     subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :create) }
 
     it_behaves_like 'it should not have Gmail Actions links'
-    it_behaves_like "a user cannot unsubscribe through footer link"
+    it_behaves_like 'a user cannot unsubscribe through footer link'
     it_behaves_like 'an email with X-GitLab headers containing project details'
     it_behaves_like 'an email that contains a header with author username'
 
@@ -914,7 +914,7 @@ describe Notify do
     subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :delete) }
 
     it_behaves_like 'it should not have Gmail Actions links'
-    it_behaves_like "a user cannot unsubscribe through footer link"
+    it_behaves_like 'a user cannot unsubscribe through footer link'
     it_behaves_like 'an email with X-GitLab headers containing project details'
     it_behaves_like 'an email that contains a header with author username'
 
@@ -936,7 +936,7 @@ describe Notify do
     subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
 
     it_behaves_like 'it should not have Gmail Actions links'
-    it_behaves_like "a user cannot unsubscribe through footer link"
+    it_behaves_like 'a user cannot unsubscribe through footer link'
     it_behaves_like 'an email with X-GitLab headers containing project details'
     it_behaves_like 'an email that contains a header with author username'
 
@@ -964,7 +964,7 @@ describe Notify do
     subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, diff_refs: diff_refs, send_from_committer_email: send_from_committer_email) }
 
     it_behaves_like 'it should not have Gmail Actions links'
-    it_behaves_like "a user cannot unsubscribe through footer link"
+    it_behaves_like 'a user cannot unsubscribe through footer link'
     it_behaves_like 'an email with X-GitLab headers containing project details'
     it_behaves_like 'an email that contains a header with author username'
 
@@ -1066,7 +1066,7 @@ describe Notify do
     subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) }
 
     it_behaves_like 'it should show Gmail Actions View Commit link'
-    it_behaves_like "a user cannot unsubscribe through footer link"
+    it_behaves_like 'a user cannot unsubscribe through footer link'
     it_behaves_like 'an email with X-GitLab headers containing project details'
     it_behaves_like 'an email that contains a header with author username'
 
diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb
index 93de585..56872da 100644
--- a/spec/mailers/shared/notify.rb
+++ b/spec/mailers/shared/notify.rb
@@ -169,10 +169,18 @@ shared_examples 'it should show Gmail Actions View Commit link' do
 end
 
 shared_examples 'an unsubscribeable thread' do
+  it 'has a List-Unsubscribe header' do
+    is_expected.to have_header 'List-Unsubscribe', /unsubscribe/
+  end
+
   it { is_expected.to have_body_text /unsubscribe/ }
 end
 
 shared_examples 'a user cannot unsubscribe through footer link' do
+  it 'does not have a List-Unsubscribe header' do
+    is_expected.not_to have_header 'List-Unsubscribe', /unsubscribe/
+  end
+
   it { is_expected.not_to have_body_text /unsubscribe/ }
 end
 
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index aa3b2bb..1bdf005 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -171,70 +171,6 @@ describe Ability, lib: true do
     end
   end
 
-  shared_examples_for ".project_abilities" do |enable_request_store|
-    before do
-      RequestStore.begin! if enable_request_store
-    end
-
-    after do
-      if enable_request_store
-        RequestStore.end!
-        RequestStore.clear!
-      end
-    end
-
-    describe '.project_abilities' do
-      let!(:project) { create(:empty_project, :public) }
-      let!(:user) { create(:user) }
-
-      it 'returns permissions for admin user' do
-        admin = create(:admin)
-
-        results = described_class.project_abilities(admin, project)
-
-        expect(results.count).to eq(68)
-      end
-
-      it 'returns permissions for an owner' do
-        results = described_class.project_abilities(project.owner, project)
-
-        expect(results.count).to eq(68)
-      end
-
-      it 'returns permissions for a master' do
-        project.team << [user, :master]
-
-        results = described_class.project_abilities(user, project)
-
-        expect(results.count).to eq(60)
-      end
-
-      it 'returns permissions for a developer' do
-        project.team << [user, :developer]
-
-        results = described_class.project_abilities(user, project)
-
-        expect(results.count).to eq(44)
-      end
-
-      it 'returns permissions for a guest' do
-        project.team << [user, :guest]
-
-        results = described_class.project_abilities(user, project)
-
-        expect(results.count).to eq(21)
-      end
-    end
-  end
-
-  describe '.project_abilities with RequestStore' do
-    it_behaves_like ".project_abilities", true
-  end
-
-  describe '.project_abilities without RequestStore' do
-    it_behaves_like ".project_abilities", false
-  end
-
   describe '.issues_readable_by_user' do
     context 'with an admin user' do
       it 'returns all given issues' do
@@ -282,4 +218,17 @@ describe Ability, lib: true do
       end
     end
   end
+
+  describe '.project_disabled_features_rules' do
+    let(:project) { create(:project,  wiki_access_level: ProjectFeature::DISABLED) }
+
+    subject { described_class.allowed(project.owner, project) }
+
+    context 'wiki named abilities' do
+      it 'disables wiki abilities if the project has no wiki' do
+        expect(project).to receive(:has_external_wiki?).and_return(false)
+        expect(subject).not_to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
+      end
+    end
+  end
 end
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index cee2023..03d02b4 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
 require 'rails_helper'
 
 describe Blob do
@@ -7,6 +8,25 @@ describe Blob do
     end
   end
 
+  describe '#data' do
+    context 'using a binary blob' do
+      it 'returns the data as-is' do
+        data = "\n\xFF\xB9\xC3"
+        blob = described_class.new(double(binary?: true, data: data))
+
+        expect(blob.data).to eq(data)
+      end
+    end
+
+    context 'using a text blob' do
+      it 'converts the data to UTF-8' do
+        blob = described_class.new(double(binary?: false, data: "\n\xFF\xB9\xC3"))
+
+        expect(blob.data).to eq("\n���")
+      end
+    end
+  end
+
   describe '#svg?' do
     it 'is falsey when not text' do
       git_blob = double(text?: false)
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index ee2c3d0..e7864b7 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -88,9 +88,7 @@ describe Ci::Build, models: true do
   end
 
   describe '#trace' do
-    subject { build.trace_html }
-
-    it { is_expected.to be_empty }
+    it { expect(build.trace).to be_nil }
 
     context 'when build.trace contains text' do
       let(:text) { 'example output' }
@@ -98,16 +96,80 @@ describe Ci::Build, models: true do
         build.trace = text
       end
 
-      it { is_expected.to include(text) }
-      it { expect(subject.length).to be >= text.length }
+      it { expect(build.trace).to eq(text) }
+    end
+
+    context 'when build.trace hides runners token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.update(trace: token)
+        build.project.update(runners_token: token)
+      end
+
+      it { expect(build.trace).not_to include(token) }
+      it { expect(build.raw_trace).to include(token) }
+    end
+
+    context 'when build.trace hides build token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.update(trace: token)
+        build.update(token: token)
+      end
+
+      it { expect(build.trace).not_to include(token) }
+      it { expect(build.raw_trace).to include(token) }
+    end
+  end
+
+  describe '#raw_trace' do
+    subject { build.raw_trace }
+
+    context 'when build.trace hides runners token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.project.update(runners_token: token)
+        build.update(trace: token)
+      end
+
+      it { is_expected.not_to include(token) }
     end
 
-    context 'when build.trace hides token' do
+    context 'when build.trace hides build token' do
       let(:token) { 'my_secret_token' }
 
       before do
-        build.project.update_attributes(runners_token: token)
-        build.update_attributes(trace: token)
+        build.update(token: token)
+        build.update(trace: token)
+      end
+
+      it { is_expected.not_to include(token) }
+    end
+  end
+
+  context '#append_trace' do
+    subject { build.trace_html }
+
+    context 'when build.trace hides runners token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.project.update(runners_token: token)
+        build.append_trace(token, 0)
+      end
+
+      it { is_expected.not_to include(token) }
+    end
+
+    context 'when build.trace hides build token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.update(token: token)
+        build.append_trace(token, 0)
       end
 
       it { is_expected.not_to include(token) }
@@ -231,6 +293,34 @@ describe Ci::Build, models: true do
       it { is_expected.to eq(predefined_variables) }
     end
 
+    context 'when build has user' do
+      let(:user) { create(:user, username: 'starter') }
+      let(:user_variables) do
+        [
+          { key: 'GITLAB_USER_ID',    value: user.id.to_s, public: true },
+          { key: 'GITLAB_USER_EMAIL', value: user.email,   public: true }
+        ]
+      end
+
+      before do
+        build.update_attributes(user: user)
+      end
+
+      it { user_variables.each { |v| is_expected.to include(v) } }
+    end
+
+    context 'when build started manually' do
+      before do
+        build.update_attributes(when: :manual)
+      end
+
+      let(:manual_variable) do
+        { key: 'CI_BUILD_MANUAL', value: 'true', public: true }
+      end
+
+      it { is_expected.to include(manual_variable) }
+    end
+
     context 'when build is for tag' do
       let(:tag_variable) do
         { key: 'CI_BUILD_TAG', value: 'master', public: true }
@@ -948,15 +1038,17 @@ describe Ci::Build, models: true do
       before { build.run! }
 
       it 'returns false' do
-        expect(build.retryable?).to be false
+        expect(build).not_to be_retryable
       end
     end
 
     context 'when build is finished' do
-      before { build.success! }
+      before do
+        build.success!
+      end
 
       it 'returns true' do
-        expect(build.retryable?).to be true
+        expect(build).to be_retryable
       end
     end
   end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 36d1063..a37a00f 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -8,7 +8,7 @@ describe Ci::Build, models: true do
     it 'obfuscates project runners token' do
       allow(build).to receive(:raw_trace).and_return("Test: #{build.project.runners_token}")
 
-      expect(build.trace).to eq("Test: xxxxxx")
+      expect(build.trace).to eq("Test: xxxxxxxxxxxxxxxxxxxx")
     end
 
     it 'empty project runners token' do
@@ -19,4 +19,64 @@ describe Ci::Build, models: true do
       expect(build.trace).to eq(test_trace)
     end
   end
+
+  describe '#has_trace_file?' do
+    context 'when there is no trace' do
+      it { expect(build.has_trace_file?).to be_falsey }
+      it { expect(build.trace).to be_nil }
+    end
+
+    context 'when there is a trace' do
+      context 'when trace is stored in file' do
+        let(:build_with_trace) { create(:ci_build, :trace) }
+
+        it { expect(build_with_trace.has_trace_file?).to be_truthy }
+        it { expect(build_with_trace.trace).to eq('BUILD TRACE') }
+      end
+
+      context 'when trace is stored in old file' do
+        before do
+          allow(build.project).to receive(:ci_id).and_return(999)
+          allow(File).to receive(:exist?).with(build.path_to_trace).and_return(false)
+          allow(File).to receive(:exist?).with(build.old_path_to_trace).and_return(true)
+          allow(File).to receive(:read).with(build.old_path_to_trace).and_return(test_trace)
+        end
+
+        it { expect(build.has_trace_file?).to be_truthy }
+        it { expect(build.trace).to eq(test_trace) }
+      end
+
+      context 'when trace is stored in DB' do
+        before do
+          allow(build.project).to receive(:ci_id).and_return(nil)
+          allow(build).to receive(:read_attribute).with(:trace).and_return(test_trace)
+          allow(File).to receive(:exist?).with(build.path_to_trace).and_return(false)
+          allow(File).to receive(:exist?).with(build.old_path_to_trace).and_return(false)
+        end
+
+        it { expect(build.has_trace_file?).to be_falsey }
+        it { expect(build.trace).to eq(test_trace) }
+      end
+    end
+  end
+
+  describe '#trace_file_path' do
+    context 'when trace is stored in file' do
+      before do
+        allow(build).to receive(:has_trace_file?).and_return(true)
+        allow(build).to receive(:has_old_trace_file?).and_return(false)
+      end
+
+      it { expect(build.trace_file_path).to eq(build.path_to_trace) }
+    end
+
+    context 'when trace is stored in old file' do
+      before do
+        allow(build).to receive(:has_trace_file?).and_return(true)
+        allow(build).to receive(:has_old_trace_file?).and_return(true)
+      end
+
+      it { expect(build.trace_file_path).to eq(build.old_path_to_trace) }
+    end
+  end
 end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 721b20e..550a890 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -124,21 +124,38 @@ describe Ci::Pipeline, models: true do
 
   describe 'state machine' do
     let(:current) { Time.now.change(usec: 0) }
-    let(:build) { create :ci_build, name: 'build1', pipeline: pipeline }
+    let(:build) { create_build('build1', current, 10) }
+    let(:build_b) { create_build('build2', current, 20) }
+    let(:build_c) { create_build('build3', current + 50, 10) }
 
     describe '#duration' do
       before do
-        travel_to(current - 120) do
+        pipeline.update(created_at: current)
+
+        travel_to(current + 5) do
           pipeline.run
+          pipeline.save
+        end
+
+        travel_to(current + 30) do
+          build.success
         end
 
-        travel_to(current) do
-          pipeline.succeed
+        travel_to(current + 40) do
+          build_b.drop
         end
+
+        travel_to(current + 70) do
+          build_c.success
+        end
+
+        pipeline.drop
       end
 
       it 'matches sum of builds duration' do
-        expect(pipeline.reload.duration).to eq(120)
+        pipeline.reload
+
+        expect(pipeline.duration).to eq(40)
       end
     end
 
@@ -169,6 +186,45 @@ describe Ci::Pipeline, models: true do
         expect(pipeline.reload.finished_at).to be_nil
       end
     end
+
+    describe "merge request metrics" do
+      let(:project) { FactoryGirl.create :project }
+      let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
+      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
+
+      context 'when transitioning to running' do
+        it 'records the build start time' do
+          time = Time.now
+          Timecop.freeze(time) { build.run }
+
+          expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(time)
+        end
+
+        it 'clears the build end time' do
+          build.run
+
+          expect(merge_request.reload.metrics.latest_build_finished_at).to be_nil
+        end
+      end
+
+      context 'when transitioning to success' do
+        it 'records the build end time' do
+          build.run
+          time = Time.now
+          Timecop.freeze(time) { build.success }
+
+          expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(time)
+        end
+      end
+    end
+
+    def create_build(name, queued_at = current, started_from = 0)
+      create(:ci_build,
+             name: name,
+             pipeline: pipeline,
+             queued_at: queued_at,
+             started_at: queued_at + started_from)
+    end
   end
 
   describe '#branch?' do
@@ -195,6 +251,36 @@ describe Ci::Pipeline, models: true do
     end
   end
 
+  context 'with non-empty project' do
+    let(:project) { create(:project) }
+
+    let(:pipeline) do
+      create(:ci_pipeline,
+             project: project,
+             ref: project.default_branch,
+             sha: project.commit.sha)
+    end
+
+    describe '#latest?' do
+      context 'with latest sha' do
+        it 'returns true' do
+          expect(pipeline).to be_latest
+        end
+      end
+
+      context 'with not latest sha' do
+        before do
+          pipeline.update(
+            sha: project.commit("#{project.default_branch}~1").sha)
+        end
+
+        it 'returns false' do
+          expect(pipeline).not_to be_latest
+        end
+      end
+    end
+  end
+
   describe '#manual_actions' do
     subject { pipeline.manual_actions }
 
@@ -318,8 +404,8 @@ describe Ci::Pipeline, models: true do
   end
 
   describe '#execute_hooks' do
-    let!(:build_a) { create_build('a') }
-    let!(:build_b) { create_build('b') }
+    let!(:build_a) { create_build('a', 0) }
+    let!(:build_b) { create_build('b', 1) }
 
     let!(:hook) do
       create(:project_hook, project: project, pipeline_events: enabled)
@@ -343,7 +429,7 @@ describe Ci::Pipeline, models: true do
             build_b.enqueue
           end
 
-          it 'receive a pending event once' do
+          it 'receives a pending event once' do
             expect(WebMock).to have_requested_pipeline_hook('pending').once
           end
         end
@@ -356,7 +442,7 @@ describe Ci::Pipeline, models: true do
             build_b.run
           end
 
-          it 'receive a running event once' do
+          it 'receives a running event once' do
             expect(WebMock).to have_requested_pipeline_hook('running').once
           end
         end
@@ -367,11 +453,21 @@ describe Ci::Pipeline, models: true do
             build_b.success
           end
 
-          it 'receive a success event once' do
+          it 'receives a success event once' do
             expect(WebMock).to have_requested_pipeline_hook('success').once
           end
         end
 
+        context 'when stage one failed' do
+          before do
+            build_a.drop
+          end
+
+          it 'receives a failed event once' do
+            expect(WebMock).to have_requested_pipeline_hook('failed').once
+          end
+        end
+
         def have_requested_pipeline_hook(status)
           have_requested(:post, hook.url).with do |req|
             json_body = JSON.parse(req.body)
@@ -395,8 +491,36 @@ describe Ci::Pipeline, models: true do
       end
     end
 
-    def create_build(name)
-      create(:ci_build, :created, pipeline: pipeline, name: name)
+    def create_build(name, stage_idx)
+      create(:ci_build,
+             :created,
+             pipeline: pipeline,
+             name: name,
+             stage_idx: stage_idx)
+    end
+  end
+
+  describe "#merge_requests" do
+    let(:project) { FactoryGirl.create :project }
+    let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
+
+    it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
+      merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)
+
+      expect(pipeline.merge_requests).to eq([merge_request])
+    end
+
+    it "doesn't return merge requests whose source branch doesn't match the pipeline's ref" do
+      create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master')
+
+      expect(pipeline.merge_requests).to be_empty
+    end
+
+    it "doesn't return merge requests whose `diff_head_sha` doesn't match the pipeline's SHA" do
+      create(:merge_request, source_project: project, source_branch: pipeline.ref)
+      allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { '97de212e80737a608d939f648d959671fb0a0142b' }
+
+      expect(pipeline.merge_requests).to be_empty
     end
   end
 end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 384a38e..c41359b 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -76,16 +76,6 @@ describe CommitRange, models: true do
     end
   end
 
-  describe '#reference_title' do
-    it 'returns the correct String for three-dot ranges' do
-      expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}"
-    end
-
-    it 'returns the correct String for two-dot ranges' do
-      expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}"
-    end
-  end
-
   describe '#to_param' do
     it 'includes the correct keys' do
       expect(range.to_param.keys).to eq %i(from to)
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index fcfa313..2f1baff 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -40,7 +40,7 @@ describe CommitStatus, models: true do
       it { is_expected.to be_falsey }
     end
 
-    %w(running success failed).each do |status|
+    %w[running success failed].each do |status|
       context "if commit status is #{status}" do
         before { commit_status.status = status }
 
@@ -48,7 +48,7 @@ describe CommitStatus, models: true do
       end
     end
 
-    %w(pending canceled).each do |status|
+    %w[pending canceled].each do |status|
       context "if commit status is #{status}" do
         before { commit_status.status = status }
 
@@ -60,7 +60,7 @@ describe CommitStatus, models: true do
   describe '#active?' do
     subject { commit_status.active? }
 
-    %w(pending running).each do |state|
+    %w[pending running].each do |state|
       context "if commit_status.status is #{state}" do
         before { commit_status.status = state }
 
@@ -68,7 +68,7 @@ describe CommitStatus, models: true do
       end
     end
 
-    %w(success failed canceled).each do |state|
+    %w[success failed canceled].each do |state|
       context "if commit_status.status is #{state}" do
         before { commit_status.status = state }
 
@@ -80,7 +80,7 @@ describe CommitStatus, models: true do
   describe '#complete?' do
     subject { commit_status.complete? }
 
-    %w(success failed canceled).each do |state|
+    %w[success failed canceled].each do |state|
       context "if commit_status.status is #{state}" do
         before { commit_status.status = state }
 
@@ -88,7 +88,7 @@ describe CommitStatus, models: true do
       end
     end
 
-    %w(pending running).each do |state|
+    %w[pending running].each do |state|
       context "if commit_status.status is #{state}" do
         before { commit_status.status = state }
 
@@ -187,7 +187,7 @@ describe CommitStatus, models: true do
       subject { CommitStatus.where(pipeline: pipeline).stages }
 
       it 'returns ordered list of stages' do
-        is_expected.to eq(%w(build test deploy))
+        is_expected.to eq(%w[build test deploy])
       end
     end
 
@@ -223,4 +223,33 @@ describe CommitStatus, models: true do
       expect(commit_status.commit).to eq project.commit
     end
   end
+
+  describe '#group_name' do
+    subject { commit_status.group_name }
+
+    tests = {
+      'rspec:windows' => 'rspec:windows',
+      'rspec:windows 0' => 'rspec:windows 0',
+      'rspec:windows 0 test' => 'rspec:windows 0 test',
+      'rspec:windows 0 1' => 'rspec:windows',
+      'rspec:windows 0 1 name' => 'rspec:windows name',
+      'rspec:windows 0/1' => 'rspec:windows',
+      'rspec:windows 0/1 name' => 'rspec:windows name',
+      'rspec:windows 0:1' => 'rspec:windows',
+      'rspec:windows 0:1 name' => 'rspec:windows name',
+      'rspec:windows 10000 20000' => 'rspec:windows',
+      'rspec:windows 0 : / 1' => 'rspec:windows',
+      'rspec:windows 0 : / 1 name' => 'rspec:windows name',
+      '0 1 name ruby' => 'name ruby',
+      '0 :/ 1 name ruby' => 'name ruby'
+    }
+
+    tests.each do |name, group_name|
+      it "'#{name}' puts in '#{group_name}'" do
+        commit_status.name = name
+
+        is_expected.to eq(group_name)
+      end
+    end
+  end
 end
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index a371c4a..de791ab 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -45,4 +45,14 @@ describe Issue, "Awardable" do
       expect { issue.toggle_award_emoji("thumbsdown", award_emoji.user) }.to change { AwardEmoji.count }.by(-1)
     end
   end
+
+  describe 'querying award_emoji on an Awardable' do
+    let(:issue) { create(:issue) }
+
+    it 'sorts in ascending fashion' do
+      create_list(:award_emoji, 3, awardable: issue)
+
+      expect(issue.award_emoji).to eq issue.award_emoji.sort_by(&:id)
+    end
+  end
 end
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
new file mode 100644
index 0000000..e118432
--- /dev/null
+++ b/spec/models/concerns/has_status_spec.rb
@@ -0,0 +1,129 @@
+require 'spec_helper'
+
+describe HasStatus do
+  before do
+    @object = Object.new
+    @object.extend(HasStatus::ClassMethods)
+  end
+
+  describe '.status' do
+    before do
+      allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses))
+    end
+
+    subject { @object.status }
+
+    shared_examples 'build status summary' do
+      context 'all successful' do
+        let(:statuses) { Array.new(2) { create(type, status: :success) } }
+        it { is_expected.to eq 'success' }
+      end
+
+      context 'at least one failed' do
+        let(:statuses) do
+          [create(type, status: :success), create(type, status: :failed)]
+        end
+
+        it { is_expected.to eq 'failed' }
+      end
+
+      context 'at least one running' do
+        let(:statuses) do
+          [create(type, status: :success), create(type, status: :running)]
+        end
+
+        it { is_expected.to eq 'running' }
+      end
+
+      context 'at least one pending' do
+        let(:statuses) do
+          [create(type, status: :success), create(type, status: :pending)]
+        end
+
+        it { is_expected.to eq 'running' }
+      end
+
+      context 'success and failed but allowed to fail' do
+        let(:statuses) do
+          [create(type, status: :success),
+           create(type, status: :failed, allow_failure: true)]
+        end
+
+        it { is_expected.to eq 'success' }
+      end
+
+      context 'one failed but allowed to fail' do
+        let(:statuses) { [create(type, status: :failed, allow_failure: true)] }
+        it { is_expected.to eq 'success' }
+      end
+
+      context 'success and canceled' do
+        let(:statuses) do
+          [create(type, status: :success), create(type, status: :canceled)]
+        end
+
+        it { is_expected.to eq 'canceled' }
+      end
+
+      context 'one failed and one canceled' do
+        let(:statuses) do
+          [create(type, status: :failed), create(type, status: :canceled)]
+        end
+
+        it { is_expected.to eq 'failed' }
+      end
+
+      context 'one failed but allowed to fail and one canceled' do
+        let(:statuses) do
+          [create(type, status: :failed, allow_failure: true),
+           create(type, status: :canceled)]
+        end
+
+        it { is_expected.to eq 'canceled' }
+      end
+
+      context 'one running one canceled' do
+        let(:statuses) do
+          [create(type, status: :running), create(type, status: :canceled)]
+        end
+
+        it { is_expected.to eq 'running' }
+      end
+
+      context 'all canceled' do
+        let(:statuses) do
+          [create(type, status: :canceled), create(type, status: :canceled)]
+        end
+        it { is_expected.to eq 'canceled' }
+      end
+
+      context 'success and canceled but allowed to fail' do
+        let(:statuses) do
+          [create(type, status: :success),
+           create(type, status: :canceled, allow_failure: true)]
+        end
+
+        it { is_expected.to eq 'success' }
+      end
+
+      context 'one finished and second running but allowed to fail' do
+        let(:statuses) do
+          [create(type, status: :success),
+           create(type, status: :running, allow_failure: true)]
+        end
+
+        it { is_expected.to eq 'running' }
+      end
+    end
+
+    context 'ci build statuses' do
+      let(:type) { :ci_build }
+      it_behaves_like 'build status summary'
+    end
+
+    context 'generic commit statuses' do
+      let(:type) { :generic_commit_status }
+      it_behaves_like 'build status summary'
+    end
+  end
+end
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
new file mode 100644
index 0000000..5363aea
--- /dev/null
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe ProjectFeaturesCompatibility do
+  let(:project) { create(:project) }
+  let(:features) { %w(issues wiki builds merge_requests snippets) }
+
+  # We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table
+  # All those fields got moved to a new table called project_feature and are now integers instead of booleans
+  # This spec tests if the described concern makes sure parameters received by the API are correctly parsed to the new table
+  # So we can keep it compatible
+
+  it "converts fields from 'true' to ProjectFeature::ENABLED" do
+    features.each do |feature|
+      project.update_attribute("#{feature}_enabled".to_sym, "true")
+      expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED)
+    end
+  end
+
+  it "converts fields from 'false' to ProjectFeature::DISABLED" do
+    features.each do |feature|
+      project.update_attribute("#{feature}_enabled".to_sym, "false")
+      expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED)
+    end
+  end
+end
diff --git a/spec/models/concerns/statuseable_spec.rb b/spec/models/concerns/statuseable_spec.rb
deleted file mode 100644
index 8e0a2a2..0000000
--- a/spec/models/concerns/statuseable_spec.rb
+++ /dev/null
@@ -1,129 +0,0 @@
-require 'spec_helper'
-
-describe Statuseable do
-  before do
-    @object = Object.new
-    @object.extend(Statuseable::ClassMethods)
-  end
-
-  describe '.status' do
-    before do
-      allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses))
-    end
-
-    subject { @object.status }
-    
-    shared_examples 'build status summary' do
-      context 'all successful' do
-        let(:statuses) { Array.new(2) { create(type, status: :success) } }
-        it { is_expected.to eq 'success' }
-      end
-
-      context 'at least one failed' do
-        let(:statuses) do
-          [create(type, status: :success), create(type, status: :failed)]
-        end
-
-        it { is_expected.to eq 'failed' }
-      end
-
-      context 'at least one running' do
-        let(:statuses) do
-          [create(type, status: :success), create(type, status: :running)]
-        end
-
-        it { is_expected.to eq 'running' }
-      end
-
-      context 'at least one pending' do
-        let(:statuses) do
-          [create(type, status: :success), create(type, status: :pending)]
-        end
-
-        it { is_expected.to eq 'running' }
-      end
-
-      context 'success and failed but allowed to fail' do
-        let(:statuses) do
-          [create(type, status: :success),
-           create(type, status: :failed, allow_failure: true)]
-        end
-
-        it { is_expected.to eq 'success' }
-      end
-
-      context 'one failed but allowed to fail' do
-        let(:statuses) { [create(type, status: :failed, allow_failure: true)] }
-        it { is_expected.to eq 'success' }
-      end
-
-      context 'success and canceled' do
-        let(:statuses) do
-          [create(type, status: :success), create(type, status: :canceled)]
-        end
-
-        it { is_expected.to eq 'canceled' }
-      end
-
-      context 'one failed and one canceled' do
-        let(:statuses) do
-          [create(type, status: :failed), create(type, status: :canceled)]
-        end
-
-        it { is_expected.to eq 'failed' }
-      end
-
-      context 'one failed but allowed to fail and one canceled' do
-        let(:statuses) do
-          [create(type, status: :failed, allow_failure: true),
-           create(type, status: :canceled)]
-        end
-
-        it { is_expected.to eq 'canceled' }
-      end
-
-      context 'one running one canceled' do
-        let(:statuses) do
-          [create(type, status: :running), create(type, status: :canceled)]
-        end
-
-        it { is_expected.to eq 'running' }
-      end
-
-      context 'all canceled' do
-        let(:statuses) do
-          [create(type, status: :canceled), create(type, status: :canceled)]
-        end
-        it { is_expected.to eq 'canceled' }
-      end
-
-      context 'success and canceled but allowed to fail' do
-        let(:statuses) do
-          [create(type, status: :success),
-           create(type, status: :canceled, allow_failure: true)]
-        end
-
-        it { is_expected.to eq 'success' }
-      end
-
-      context 'one finished and second running but allowed to fail' do
-        let(:statuses) do
-          [create(type, status: :success),
-           create(type, status: :running, allow_failure: true)]
-        end
-
-        it { is_expected.to eq 'running' }
-      end
-    end
-
-    context 'ci build statuses' do
-      let(:type) { :ci_build }
-      it_behaves_like 'build status summary'
-    end
-
-    context 'generic commit statuses' do
-      let(:type) { :generic_commit_status }
-      it_behaves_like 'build status summary'
-    end
-  end
-end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
new file mode 100644
index 0000000..b9381e3
--- /dev/null
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe 'CycleAnalytics#code', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
+  let(:project) { create(:project) }
+  let(:from_date) { 10.days.ago }
+  let(:user) { create(:user, :admin) }
+  subject { CycleAnalytics.new(project, from: from_date) }
+
+  generate_cycle_analytics_spec(
+    phase: :code,
+    data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } },
+    start_time_conditions: [["issue mentioned in a commit",
+                             -> (context, data) do
+                               context.create_commit_referencing_issue(data[:issue])
+                             end]],
+    end_time_conditions:   [["merge request that closes issue is created",
+                             -> (context, data) do
+                               context.create_merge_request_closing_issue(data[:issue])
+                             end]],
+    post_fn: -> (context, data) do
+      context.merge_merge_requests_closing_issue(data[:issue])
+      context.deploy_master
+    end)
+
+  context "when a regular merge request (that doesn't close the issue) is created" do
+    it "returns nil" do
+      5.times do
+        issue = create(:issue, project: project)
+
+        create_commit_referencing_issue(issue)
+        create_merge_request_closing_issue(issue, message: "Closes nothing")
+
+        merge_merge_requests_closing_issue(issue)
+        deploy_master
+      end
+
+      expect(subject.code).to be_nil
+    end
+  end
+end
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
new file mode 100644
index 0000000..e9cc712
--- /dev/null
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe 'CycleAnalytics#issue', models: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
+  let(:project) { create(:project) }
+  let(:from_date) { 10.days.ago }
+  let(:user) { create(:user, :admin) }
+  subject { CycleAnalytics.new(project, from: from_date) }
+
+  generate_cycle_analytics_spec(
+    phase: :issue,
+    data_fn: -> (context) { { issue: context.build(:issue, project: context.project) } },
+    start_time_conditions: [["issue created", -> (context, data) { data[:issue].save }]],
+    end_time_conditions:   [["issue associated with a milestone",
+                             -> (context, data) do
+                               if data[:issue].persisted?
+                                 data[:issue].update(milestone: context.create(:milestone, project: context.project))
+                               end
+                             end],
+                            ["list label added to issue",
+                             -> (context, data) do
+                               if data[:issue].persisted?
+                                 data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id])
+                               end
+                             end]],
+    post_fn: -> (context, data) do
+      if data[:issue].persisted?
+        context.create_merge_request_closing_issue(data[:issue].reload)
+        context.merge_merge_requests_closing_issue(data[:issue])
+        context.deploy_master
+      end
+    end)
+
+  context "when a regular label (instead of a list label) is added to the issue" do
+    it "returns nil" do
+      5.times do
+        regular_label = create(:label)
+        issue = create(:issue, project: project)
+        issue.update(label_ids: [regular_label.id])
+
+        create_merge_request_closing_issue(issue)
+        merge_merge_requests_closing_issue(issue)
+        deploy_master
+      end
+
+      expect(subject.issue).to be_nil
+    end
+  end
+end
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
new file mode 100644
index 0000000..5b8c96d
--- /dev/null
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe 'CycleAnalytics#plan', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
+  let(:project) { create(:project) }
+  let(:from_date) { 10.days.ago }
+  let(:user) { create(:user, :admin) }
+  subject { CycleAnalytics.new(project, from: from_date) }
+
+  generate_cycle_analytics_spec(
+    phase: :plan,
+    data_fn: -> (context) do
+      {
+        issue: context.create(:issue, project: context.project),
+        branch_name: context.random_git_name
+      }
+    end,
+    start_time_conditions: [["issue associated with a milestone",
+                             -> (context, data) do
+                               data[:issue].update(milestone: context.create(:milestone, project: context.project))
+                             end],
+                            ["list label added to issue",
+                             -> (context, data) do
+                               data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id])
+                             end]],
+    end_time_conditions:   [["issue mentioned in a commit",
+                             -> (context, data) do
+                               context.create_commit_referencing_issue(data[:issue], branch_name: data[:branch_name])
+                             end]],
+    post_fn: -> (context, data) do
+      context.create_merge_request_closing_issue(data[:issue], source_branch: data[:branch_name])
+      context.merge_merge_requests_closing_issue(data[:issue])
+      context.deploy_master
+    end)
+
+  context "when a regular label (instead of a list label) is added to the issue" do
+    it "returns nil" do
+      branch_name = random_git_name
+      label = create(:label)
+      issue = create(:issue, project: project)
+      issue.update(label_ids: [label.id])
+      create_commit_referencing_issue(issue, branch_name: branch_name)
+
+      create_merge_request_closing_issue(issue, source_branch: branch_name)
+      merge_merge_requests_closing_issue(issue)
+      deploy_master
+
+      expect(subject.issue).to be_nil
+    end
+  end
+end
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
new file mode 100644
index 0000000..1f5e5ca
--- /dev/null
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe 'CycleAnalytics#production', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
+  let(:project) { create(:project) }
+  let(:from_date) { 10.days.ago }
+  let(:user) { create(:user, :admin) }
+  subject { CycleAnalytics.new(project, from: from_date) }
+
+  generate_cycle_analytics_spec(
+    phase: :production,
+    data_fn: -> (context) { { issue: context.build(:issue, project: context.project) } },
+    start_time_conditions: [["issue is created", -> (context, data) { data[:issue].save }]],
+    before_end_fn: lambda do |context, data|
+      context.create_merge_request_closing_issue(data[:issue])
+      context.merge_merge_requests_closing_issue(data[:issue])
+    end,
+    end_time_conditions:
+      [["merge request that closes issue is deployed to production", -> (context, data) { context.deploy_master }],
+       ["production deploy happens after merge request is merged (along with other changes)",
+        lambda do |context, data|
+          # Make other changes on master
+          sha = context.project.repository.commit_file(context.user, context.random_git_name, "content", "commit message", 'master', false)
+          context.project.repository.commit(sha)
+
+          context.deploy_master
+        end]])
+
+  context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
+    it "returns nil" do
+      5.times do
+        merge_request = create(:merge_request)
+        MergeRequests::MergeService.new(project, user).execute(merge_request)
+        deploy_master
+      end
+
+      expect(subject.production).to be_nil
+    end
+  end
+
+  context "when the deployment happens to a non-production environment" do
+    it "returns nil" do
+      5.times do
+        issue = create(:issue, project: project)
+        merge_request = create_merge_request_closing_issue(issue)
+        MergeRequests::MergeService.new(project, user).execute(merge_request)
+        deploy_master(environment: 'staging')
+      end
+
+      expect(subject.production).to be_nil
+    end
+  end
+end
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
new file mode 100644
index 0000000..b6e26d8
--- /dev/null
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'CycleAnalytics#review', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
+  let(:project) { create(:project) }
+  let(:from_date) { 10.days.ago }
+  let(:user) { create(:user, :admin) }
+  subject { CycleAnalytics.new(project, from: from_date) }
+
+  generate_cycle_analytics_spec(
+    phase: :review,
+    data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } },
+    start_time_conditions: [["merge request that closes issue is created",
+                             -> (context, data) do
+                               context.create_merge_request_closing_issue(data[:issue])
+                             end]],
+    end_time_conditions:   [["merge request that closes issue is merged",
+                             -> (context, data) do
+                               context.merge_merge_requests_closing_issue(data[:issue])
+                             end]],
+    post_fn: -> (context, data) { context.deploy_master })
+
+  context "when a regular merge request (that doesn't close the issue) is created and merged" do
+    it "returns nil" do
+      5.times do
+        MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
+
+        deploy_master
+      end
+
+      expect(subject.review).to be_nil
+    end
+  end
+end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
new file mode 100644
index 0000000..af1c447
--- /dev/null
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe 'CycleAnalytics#staging', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
+  let(:project) { create(:project) }
+  let(:from_date) { 10.days.ago }
+  let(:user) { create(:user, :admin) }
+  subject { CycleAnalytics.new(project, from: from_date) }
+
+  generate_cycle_analytics_spec(
+    phase: :staging,
+    data_fn: lambda do |context|
+      issue = context.create(:issue, project: context.project)
+      { issue: issue, merge_request: context.create_merge_request_closing_issue(issue) }
+    end,
+    start_time_conditions: [["merge request that closes issue is merged",
+                             -> (context, data) do
+                               context.merge_merge_requests_closing_issue(data[:issue])
+                             end ]],
+    end_time_conditions:   [["merge request that closes issue is deployed to production",
+                             -> (context, data) do
+                               context.deploy_master
+                             end],
+                            ["production deploy happens after merge request is merged (along with other changes)",
+                             lambda do |context, data|
+                               # Make other changes on master
+                               sha = context.project.repository.commit_file(
+                                 context.user,
+                                 context.random_git_name,
+                                 "content",
+                                 "commit message",
+                                 'master',
+                                 false)
+                               context.project.repository.commit(sha)
+
+                               context.deploy_master
+                             end]])
+
+  context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
+    it "returns nil" do
+      5.times do
+        merge_request = create(:merge_request)
+        MergeRequests::MergeService.new(project, user).execute(merge_request)
+        deploy_master
+      end
+
+      expect(subject.staging).to be_nil
+    end
+  end
+
+  context "when the deployment happens to a non-production environment" do
+    it "returns nil" do
+      5.times do
+        issue = create(:issue, project: project)
+        merge_request = create_merge_request_closing_issue(issue)
+        MergeRequests::MergeService.new(project, user).execute(merge_request)
+        deploy_master(environment: 'staging')
+      end
+
+      expect(subject.staging).to be_nil
+    end
+  end
+end
diff --git a/spec/models/cycle_analytics/summary_spec.rb b/spec/models/cycle_analytics/summary_spec.rb
new file mode 100644
index 0000000..743bc2d
--- /dev/null
+++ b/spec/models/cycle_analytics/summary_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe CycleAnalytics::Summary, models: true do
+  let(:project) { create(:project) }
+  let(:from) { Time.now }
+  let(:user) { create(:user, :admin) }
+  subject { described_class.new(project, from: from) }
+
+  describe "#new_issues" do
+    it "finds the number of issues created after the 'from date'" do
+      Timecop.freeze(5.days.ago) { create(:issue, project: project) }
+      Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
+
+      expect(subject.new_issues).to eq(1)
+    end
+
+    it "doesn't find issues from other projects" do
+      Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) }
+
+      expect(subject.new_issues).to eq(0)
+    end
+  end
+
+  describe "#commits" do
+    it "finds the number of commits created after the 'from date'" do
+      Timecop.freeze(5.days.ago) { create_commit("Test message", project, user, 'master') }
+      Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master') }
+
+      expect(subject.commits).to eq(1)
+    end
+
+    it "doesn't find commits from other projects" do
+      Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project), user, 'master') }
+
+      expect(subject.commits).to eq(0)
+    end
+  end
+
+  describe "#deploys" do
+    it "finds the number of deploys made created after the 'from date'" do
+      Timecop.freeze(5.days.ago) { create(:deployment, project: project) }
+      Timecop.freeze(5.days.from_now) { create(:deployment, project: project) }
+
+      expect(subject.deploys).to eq(1)
+    end
+
+    it "doesn't find commits from other projects" do
+      Timecop.freeze(5.days.from_now) { create(:deployment, project: create(:project)) }
+
+      expect(subject.deploys).to eq(0)
+    end
+  end
+end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
new file mode 100644
index 0000000..89ace0b
--- /dev/null
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+describe 'CycleAnalytics#test', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
+  let(:project) { create(:project) }
+  let(:from_date) { 10.days.ago }
+  let(:user) { create(:user, :admin) }
+  subject { CycleAnalytics.new(project, from: from_date) }
+
+  generate_cycle_analytics_spec(
+    phase: :test,
+    data_fn: lambda do |context|
+      issue = context.create(:issue, project: context.project)
+      merge_request = context.create_merge_request_closing_issue(issue)
+      pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project)
+      { pipeline: pipeline, issue: issue }
+    end,
+    start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
+    end_time_conditions:   [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]],
+    post_fn: -> (context, data) do
+      context.merge_merge_requests_closing_issue(data[:issue])
+      context.deploy_master
+    end)
+
+  context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
+    it "returns nil" do
+      5.times do
+        issue = create(:issue, project: project)
+        merge_request = create_merge_request_closing_issue(issue)
+        pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+
+        pipeline.run!
+        pipeline.succeed!
+
+        merge_merge_requests_closing_issue(issue)
+        deploy_master
+      end
+
+      expect(subject.test).to be_nil
+    end
+  end
+
+  context "when the pipeline is not for a merge request" do
+    it "returns nil" do
+      5.times do
+        pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha)
+
+        pipeline.run!
+        pipeline.succeed!
+
+        deploy_master
+      end
+
+      expect(subject.test).to be_nil
+    end
+  end
+
+  context "when the pipeline is dropped (failed)" do
+    it "returns nil" do
+      5.times do
+        issue = create(:issue, project: project)
+        merge_request = create_merge_request_closing_issue(issue)
+        pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+
+        pipeline.run!
+        pipeline.drop!
+
+        merge_merge_requests_closing_issue(issue)
+        deploy_master
+      end
+
+      expect(subject.test).to be_nil
+    end
+  end
+
+  context "when the pipeline is cancelled" do
+    it "returns nil" do
+      5.times do
+        issue = create(:issue, project: project)
+        merge_request = create_merge_request_closing_issue(issue)
+        pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+
+        pipeline.run!
+        pipeline.cancel!
+
+        merge_merge_requests_closing_issue(issue)
+        deploy_master
+      end
+
+      expect(subject.test).to be_nil
+    end
+  end
+end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 6a64047..3db5937 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -31,6 +31,43 @@ describe DiffNote, models: true do
 
   subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
 
+  describe ".resolve!" do
+    let(:current_user) { create(:user) }
+    let!(:commit_note) { create(:diff_note_on_commit) }
+    let!(:resolved_note) { create(:diff_note_on_merge_request, :resolved) }
+    let!(:unresolved_note) { create(:diff_note_on_merge_request) }
+
+    before do
+      described_class.resolve!(current_user)
+
+      commit_note.reload
+      resolved_note.reload
+      unresolved_note.reload
+    end
+
+    it 'resolves only the resolvable, not yet resolved notes' do
+      expect(commit_note.resolved_at).to be_nil
+      expect(resolved_note.resolved_by).not_to eq(current_user)
+      expect(unresolved_note.resolved_at).not_to be_nil
+      expect(unresolved_note.resolved_by).to eq(current_user)
+    end
+  end
+
+  describe ".unresolve!" do
+    let!(:resolved_note) { create(:diff_note_on_merge_request, :resolved) }
+
+    before do
+      described_class.unresolve!
+
+      resolved_note.reload
+    end
+
+    it 'unresolves the resolved notes' do
+      expect(resolved_note.resolved_by).to be_nil
+      expect(resolved_note.resolved_at).to be_nil
+    end
+  end
+
   describe "#position=" do
     context "when provided a string" do
       it "sets the position" do
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index 179f2e7..0142706 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -238,27 +238,19 @@ describe Discussion, model: true do
 
     context "when resolvable" do
       let(:user) { create(:user) }
+      let(:second_note) { create(:diff_note_on_commit) } # unresolvable
 
       before do
         allow(subject).to receive(:resolvable?).and_return(true)
-
-        allow(first_note).to receive(:resolvable?).and_return(true)
-        allow(second_note).to receive(:resolvable?).and_return(false)
-        allow(third_note).to receive(:resolvable?).and_return(true)
       end
 
       context "when all resolvable notes are resolved" do
         before do
           first_note.resolve!(user)
           third_note.resolve!(user)
-        end
 
-        it "calls resolve! on every resolvable note" do
-          expect(first_note).to receive(:resolve!).with(current_user)
-          expect(second_note).not_to receive(:resolve!)
-          expect(third_note).to receive(:resolve!).with(current_user)
-
-          subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
         end
 
         it "doesn't change resolved_at on the resolved notes" do
@@ -309,46 +301,44 @@ describe Discussion, model: true do
           first_note.resolve!(user)
         end
 
-        it "calls resolve! on every resolvable note" do
-          expect(first_note).to receive(:resolve!).with(current_user)
-          expect(second_note).not_to receive(:resolve!)
-          expect(third_note).to receive(:resolve!).with(current_user)
-
-          subject.resolve!(current_user)
-        end
-
         it "doesn't change resolved_at on the resolved note" do
           expect(first_note.resolved_at).not_to be_nil
 
-          expect { subject.resolve!(current_user) }.not_to change { first_note.resolved_at }
+          expect { subject.resolve!(current_user) }.
+            not_to change { first_note.reload.resolved_at }
         end
 
         it "doesn't change resolved_by on the resolved note" do
           expect(first_note.resolved_by).to eq(user)
 
-          expect { subject.resolve!(current_user) }.not_to change { first_note.resolved_by }
+          expect { subject.resolve!(current_user) }.
+            not_to change { first_note.reload && first_note.resolved_by }
         end
 
         it "doesn't change the resolved state on the resolved note" do
           expect(first_note.resolved?).to be true
 
-          expect { subject.resolve!(current_user) }.not_to change { first_note.resolved? }
+          expect { subject.resolve!(current_user) }.
+            not_to change { first_note.reload && first_note.resolved? }
         end
 
         it "sets resolved_at on the unresolved note" do
           subject.resolve!(current_user)
+          third_note.reload
 
           expect(third_note.resolved_at).not_to be_nil
         end
 
         it "sets resolved_by on the unresolved note" do
           subject.resolve!(current_user)
+          third_note.reload
 
           expect(third_note.resolved_by).to eq(current_user)
         end
 
         it "marks the unresolved note as resolved" do
           subject.resolve!(current_user)
+          third_note.reload
 
           expect(third_note.resolved?).to be true
         end
@@ -373,16 +363,10 @@ describe Discussion, model: true do
       end
 
       context "when no resolvable notes are resolved" do
-        it "calls resolve! on every resolvable note" do
-          expect(first_note).to receive(:resolve!).with(current_user)
-          expect(second_note).not_to receive(:resolve!)
-          expect(third_note).to receive(:resolve!).with(current_user)
-
-          subject.resolve!(current_user)
-        end
-
         it "sets resolved_at on the unresolved notes" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved_at).not_to be_nil
           expect(third_note.resolved_at).not_to be_nil
@@ -390,6 +374,8 @@ describe Discussion, model: true do
 
         it "sets resolved_by on the unresolved notes" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved_by).to eq(current_user)
           expect(third_note.resolved_by).to eq(current_user)
@@ -397,6 +383,8 @@ describe Discussion, model: true do
 
         it "marks the unresolved notes as resolved" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved?).to be true
           expect(third_note.resolved?).to be true
@@ -404,18 +392,24 @@ describe Discussion, model: true do
 
         it "sets resolved_at" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved_at).not_to be_nil
         end
 
         it "sets resolved_by" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved_by).to eq(current_user)
         end
 
         it "marks as resolved" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved?).to be true
         end
@@ -451,16 +445,10 @@ describe Discussion, model: true do
           third_note.resolve!(user)
         end
 
-        it "calls unresolve! on every resolvable note" do
-          expect(first_note).to receive(:unresolve!)
-          expect(second_note).not_to receive(:unresolve!)
-          expect(third_note).to receive(:unresolve!)
-
-          subject.unresolve!
-        end
-
         it "unsets resolved_at on the resolved notes" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved_at).to be_nil
           expect(third_note.resolved_at).to be_nil
@@ -468,6 +456,8 @@ describe Discussion, model: true do
 
         it "unsets resolved_by on the resolved notes" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved_by).to be_nil
           expect(third_note.resolved_by).to be_nil
@@ -475,6 +465,8 @@ describe Discussion, model: true do
 
         it "unmarks the resolved notes as resolved" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved?).to be false
           expect(third_note.resolved?).to be false
@@ -482,12 +474,16 @@ describe Discussion, model: true do
 
         it "unsets resolved_at" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved_at).to be_nil
         end
 
         it "unsets resolved_by" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved_by).to be_nil
         end
@@ -504,40 +500,22 @@ describe Discussion, model: true do
           first_note.resolve!(user)
         end
 
-        it "calls unresolve! on every resolvable note" do
-          expect(first_note).to receive(:unresolve!)
-          expect(second_note).not_to receive(:unresolve!)
-          expect(third_note).to receive(:unresolve!)
-
-          subject.unresolve!
-        end
-
         it "unsets resolved_at on the resolved note" do
           subject.unresolve!
 
-          expect(first_note.resolved_at).to be_nil
+          expect(subject.first_note.resolved_at).to be_nil
         end
 
         it "unsets resolved_by on the resolved note" do
           subject.unresolve!
 
-          expect(first_note.resolved_by).to be_nil
+          expect(subject.first_note.resolved_by).to be_nil
         end
 
         it "unmarks the resolved note as resolved" do
           subject.unresolve!
 
-          expect(first_note.resolved?).to be false
-        end
-      end
-
-      context "when no resolvable notes are resolved" do
-        it "calls unresolve! on every resolvable note" do
-          expect(first_note).to receive(:unresolve!)
-          expect(second_note).not_to receive(:unresolve!)
-          expect(third_note).to receive(:unresolve!)
-
-          subject.unresolve!
+          expect(subject.first_note.resolved?).to be false
         end
       end
     end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index c881897..6b1867a 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -63,4 +63,20 @@ describe Environment, models: true do
       end
     end
   end
+
+  describe '#environment_type' do
+    subject { environment.environment_type }
+
+    it 'sets a environment type if name has multiple segments' do
+      environment.update!(name: 'production/worker.gitlab.com')
+
+      is_expected.to eq('production')
+    end
+
+    it 'nullifies a type if it\'s a simple name' do
+      environment.update!(name: 'production')
+
+      is_expected.to be_nil
+    end
+  end
 end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index b5d0d79..8600eb4 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -16,18 +16,12 @@ describe Event, models: true do
 
   describe 'Callbacks' do
     describe 'after_create :reset_project_activity' do
-      let(:project) { create(:project) }
+      let(:project) { create(:empty_project) }
 
-      context "project's last activity was less than 5 minutes ago" do
-        it 'does not update project.last_activity_at if it has been touched less than 5 minutes ago' do
-          create_event(project, project.owner)
-          project.update_column(:last_activity_at, 5.minutes.ago)
-          project_last_activity_at = project.last_activity_at
+      it 'calls the reset_project_activity method' do
+        expect_any_instance_of(Event).to receive(:reset_project_activity)
 
-          create_event(project, project.owner)
-
-          expect(project.last_activity_at).to eq(project_last_activity_at)
-        end
+        create_event(project, project.owner)
       end
     end
   end
@@ -161,6 +155,35 @@ describe Event, models: true do
     end
   end
 
+  describe '#reset_project_activity' do
+    let(:project) { create(:empty_project) }
+
+    context 'when a project was updated less than 1 hour ago' do
+      it 'does not update the project' do
+        project.update(last_activity_at: Time.now)
+
+        expect(project).not_to receive(:update_column).
+          with(:last_activity_at, a_kind_of(Time))
+
+        create_event(project, project.owner)
+      end
+    end
+
+    context 'when a project was updated more than 1 hour ago' do
+      it 'updates the project' do
+        project.update(last_activity_at: 1.year.ago)
+
+        expect_any_instance_of(Gitlab::ExclusiveLease).
+          to receive(:try_obtain).and_return(true)
+
+        expect(project).to receive(:update_column).
+          with(:last_activity_at, a_kind_of(Time))
+
+        create_event(project, project.owner)
+      end
+    end
+  end
+
   def create_event(project, user, attrs = {})
     data = {
       before: Gitlab::Git::BLANK_SHA,
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index ea4b59c..0b3ef9b 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -187,6 +187,52 @@ describe Group, models: true do
     it { expect(group.has_master?(@members[:requester])).to be_falsey }
   end
 
+  describe '#lfs_enabled?' do
+    context 'LFS enabled globally' do
+      before do
+        allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+      end
+
+      it 'returns true when nothing is set' do
+        expect(group.lfs_enabled?).to be_truthy
+      end
+
+      it 'returns false when set to false' do
+        group.update_attribute(:lfs_enabled, false)
+
+        expect(group.lfs_enabled?).to be_falsey
+      end
+
+      it 'returns true when set to true' do
+        group.update_attribute(:lfs_enabled, true)
+
+        expect(group.lfs_enabled?).to be_truthy
+      end
+    end
+
+    context 'LFS disabled globally' do
+      before do
+        allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
+      end
+
+      it 'returns false when nothing is set' do
+        expect(group.lfs_enabled?).to be_falsey
+      end
+
+      it 'returns false when set to false' do
+        group.update_attribute(:lfs_enabled, false)
+
+        expect(group.lfs_enabled?).to be_falsey
+      end
+
+      it 'returns false when set to true' do
+        group.update_attribute(:lfs_enabled, true)
+
+        expect(group.lfs_enabled?).to be_falsey
+      end
+    end
+  end
+
   describe '#owners' do
     let(:owner) { create(:user) }
     let(:developer) { create(:user) }
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 4a45799..474ae62 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-#  id                    :integer          not null, primary key
-#  url                   :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  type                  :string(255)      default("ProjectHook")
-#  service_id            :integer
-#  push_events           :boolean          default(TRUE), not null
-#  issues_events         :boolean          default(FALSE), not null
-#  merge_requests_events :boolean          default(FALSE), not null
-#  tag_push_events       :boolean          default(FALSE)
-#  note_events           :boolean          default(FALSE), not null
-#
-
 require 'spec_helper'
 
 describe ProjectHook, models: true do
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 534e1b4..1a83c83 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-#  id                    :integer          not null, primary key
-#  url                   :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  type                  :string(255)      default("ProjectHook")
-#  service_id            :integer
-#  push_events           :boolean          default(TRUE), not null
-#  issues_events         :boolean          default(FALSE), not null
-#  merge_requests_events :boolean          default(FALSE), not null
-#  tag_push_events       :boolean          default(FALSE)
-#  note_events           :boolean          default(FALSE), not null
-#
-
 require "spec_helper"
 
 describe ServiceHook, models: true do
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index cbdf7ee..ad2b710 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-#  id                    :integer          not null, primary key
-#  url                   :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  type                  :string(255)      default("ProjectHook")
-#  service_id            :integer
-#  push_events           :boolean          default(TRUE), not null
-#  issues_events         :boolean          default(FALSE), not null
-#  merge_requests_events :boolean          default(FALSE), not null
-#  tag_push_events       :boolean          default(FALSE)
-#  note_events           :boolean          default(FALSE), not null
-#
-
 require "spec_helper"
 
 describe SystemHook, models: true do
@@ -48,7 +30,7 @@ describe SystemHook, models: true do
 
     it "user_create hook" do
       create(:user)
-      
+
       expect(WebMock).to have_requested(:post, system_hook.url).with(
         body: /user_create/,
         headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index f9bab48..e52b9d7 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-#  id                    :integer          not null, primary key
-#  url                   :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  type                  :string(255)      default("ProjectHook")
-#  service_id            :integer
-#  push_events           :boolean          default(TRUE), not null
-#  issues_events         :boolean          default(FALSE), not null
-#  merge_requests_events :boolean          default(FALSE), not null
-#  tag_push_events       :boolean          default(FALSE)
-#  note_events           :boolean          default(FALSE), not null
-#
-
 require 'spec_helper'
 
 describe WebHook, models: true do
diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb
new file mode 100644
index 0000000..e170b08
--- /dev/null
+++ b/spec/models/issue/metrics_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Issue::Metrics, models: true do
+  let(:project) { create(:project) }
+
+  subject { create(:issue, project: project) }
+
+  describe "when recording the default set of issue metrics on issue save" do
+    context "milestones" do
+      it "records the first time an issue is associated with a milestone" do
+        time = Time.now
+        Timecop.freeze(time) { subject.update(milestone: create(:milestone)) }
+        metrics = subject.metrics
+
+        expect(metrics).to be_present
+        expect(metrics.first_associated_with_milestone_at).to be_within(1.second).of(time)
+      end
+
+      it "does not record the second time an issue is associated with a milestone" do
+        time = Time.now
+        Timecop.freeze(time) { subject.update(milestone: create(:milestone)) }
+        Timecop.freeze(time + 2.hours) { subject.update(milestone: nil) }
+        Timecop.freeze(time + 6.hours) { subject.update(milestone: create(:milestone)) }
+        metrics = subject.metrics
+
+        expect(metrics).to be_present
+        expect(metrics.first_associated_with_milestone_at).to be_within(1.second).of(time)
+      end
+    end
+
+    context "list labels" do
+      it "records the first time an issue is associated with a list label" do
+        list_label = create(:label, lists: [create(:list)])
+        time = Time.now
+        Timecop.freeze(time) { subject.update(label_ids: [list_label.id]) }
+        metrics = subject.metrics
+
+        expect(metrics).to be_present
+        expect(metrics.first_added_to_board_at).to be_within(1.second).of(time)
+      end
+
+      it "does not record the second time an issue is associated with a list label" do
+        time = Time.now
+        first_list_label = create(:label, lists: [create(:list)])
+        Timecop.freeze(time) { subject.update(label_ids: [first_list_label.id]) }
+        second_list_label = create(:label, lists: [create(:list)])
+        Timecop.freeze(time + 5.hours) { subject.update(label_ids: [second_list_label.id]) }
+        metrics = subject.metrics
+
+        expect(metrics).to be_present
+        expect(metrics.first_added_to_board_at).to be_within(1.second).of(time)
+      end
+    end
+  end
+end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index fef90d9..0b1634f 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -57,7 +57,7 @@ describe Member, models: true do
 
   describe 'Scopes & finders' do
     before do
-      project = create(:project)
+      project = create(:empty_project)
       group = create(:group)
       @owner_user = create(:user).tap { |u| group.add_owner(u) }
       @owner = group.members.find_by(user_id: @owner_user.id)
@@ -65,6 +65,15 @@ describe Member, models: true do
       @master_user = create(:user).tap { |u| project.team << [u, :master] }
       @master = project.members.find_by(user_id: @master_user.id)
 
+      @blocked_user = create(:user).tap do |u|
+        project.team << [u, :master]
+        project.team << [u, :developer]
+
+        u.block!
+      end
+      @blocked_master = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::MASTER)
+      @blocked_developer = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::DEVELOPER)
+
       Member.add_user(
         project.members,
         'toto1 at example.com',
@@ -73,7 +82,7 @@ describe Member, models: true do
       )
       @invited_member = project.members.invite.find_by_invite_email('toto1 at example.com')
 
-      accepted_invite_user = build(:user)
+      accepted_invite_user = build(:user, state: :active)
       Member.add_user(
         project.members,
         'toto2 at example.com',
@@ -91,7 +100,7 @@ describe Member, models: true do
 
     describe '.access_for_user_ids' do
       it 'returns the right access levels' do
-        users = [@owner_user.id, @master_user.id]
+        users = [@owner_user.id, @master_user.id, @blocked_user.id]
         expected = {
           @owner_user.id => Gitlab::Access::OWNER,
           @master_user.id => Gitlab::Access::MASTER
@@ -125,6 +134,19 @@ describe Member, models: true do
       it { expect(described_class.request).not_to include @accepted_request_member }
     end
 
+    describe '.developers' do
+      subject { described_class.developers.to_a }
+
+      it { is_expected.not_to include @owner }
+      it { is_expected.not_to include @master }
+      it { is_expected.to include @invited_member }
+      it { is_expected.to include @accepted_invite_member }
+      it { is_expected.not_to include @requested_member }
+      it { is_expected.to include @accepted_request_member }
+      it { is_expected.not_to include @blocked_master }
+      it { is_expected.not_to include @blocked_developer }
+    end
+
     describe '.owners_and_masters' do
       it { expect(described_class.owners_and_masters).to include @owner }
       it { expect(described_class.owners_and_masters).to include @master }
@@ -132,6 +154,20 @@ describe Member, models: true do
       it { expect(described_class.owners_and_masters).not_to include @accepted_invite_member }
       it { expect(described_class.owners_and_masters).not_to include @requested_member }
       it { expect(described_class.owners_and_masters).not_to include @accepted_request_member }
+      it { expect(described_class.owners_and_masters).not_to include @blocked_master }
+    end
+
+    describe '.has_access' do
+      subject { described_class.has_access.to_a }
+
+      it { is_expected.to include @owner }
+      it { is_expected.to include @master }
+      it { is_expected.to include @invited_member }
+      it { is_expected.to include @accepted_invite_member }
+      it { is_expected.not_to include @requested_member }
+      it { is_expected.to include @accepted_request_member }
+      it { is_expected.not_to include @blocked_master }
+      it { is_expected.not_to include @blocked_developer }
     end
   end
 
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 4f875fd..56fa7fa 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -1,22 +1,3 @@
-# == 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
-#  notification_level :integer          not null
-#  type               :string(255)
-#  created_at         :datetime
-#  updated_at         :datetime
-#  created_by_id      :integer
-#  invite_email       :string(255)
-#  invite_token       :string(255)
-#  invite_accepted_at :datetime
-#
-
 require 'spec_helper'
 
 describe GroupMember, models: true do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 913d746..805c15a 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -1,22 +1,3 @@
-# == 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
-#  notification_level :integer          not null
-#  type               :string(255)
-#  created_at         :datetime
-#  updated_at         :datetime
-#  created_by_id      :integer
-#  invite_email       :string(255)
-#  invite_token       :string(255)
-#  invite_accepted_at :datetime
-#
-
 require 'spec_helper'
 
 describe ProjectMember, models: true do
@@ -71,9 +52,6 @@ describe ProjectMember, models: true do
 
   describe :import_team do
     before do
-      @abilities = Six.new
-      @abilities << Ability
-
       @project_1 = create :project
       @project_2 = create :project
 
@@ -92,8 +70,8 @@ describe ProjectMember, models: true do
       it { expect(@project_2.users).to include(@user_1) }
       it { expect(@project_2.users).to include(@user_2) }
 
-      it { expect(@abilities.allowed?(@user_1, :create_project, @project_2)).to be_truthy }
-      it { expect(@abilities.allowed?(@user_2, :read_project, @project_2)).to be_truthy }
+      it { expect(Ability.allowed?(@user_1, :create_project, @project_2)).to be_truthy }
+      it { expect(Ability.allowed?(@user_2, :read_project, @project_2)).to be_truthy }
     end
 
     describe 'project 1 should not be changed' do
diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb
new file mode 100644
index 0000000..a79dd21
--- /dev/null
+++ b/spec/models/merge_request/metrics_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe MergeRequest::Metrics, models: true do
+  let(:project) { create(:project) }
+
+  subject { create(:merge_request, source_project: project) }
+
+  describe "when recording the default set of metrics on merge request save" do
+    it "records the merge time" do
+      time = Time.now
+      Timecop.freeze(time) { subject.mark_as_merged }
+      metrics = subject.metrics
+
+      expect(metrics).to be_present
+      expect(metrics.merged_at).to be_within(1.second).of(time)
+    end
+  end
+end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 29f7396..530a7de 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -1,6 +1,27 @@
 require 'spec_helper'
 
 describe MergeRequestDiff, models: true do
+  describe 'create new record' do
+    subject { create(:merge_request).merge_request_diff }
+
+    it { expect(subject).to be_valid }
+    it { expect(subject).to be_persisted }
+    it { expect(subject.commits.count).to eq(5) }
+    it { expect(subject.diffs.count).to eq(8) }
+    it { expect(subject.head_commit_sha).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
+    it { expect(subject.base_commit_sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }
+    it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') }
+  end
+
+  describe '#latest' do
+    let!(:mr) { create(:merge_request, :with_diffs) }
+    let!(:first_diff) { mr.merge_request_diff }
+    let!(:last_diff) { mr.create_merge_request_diff }
+
+    it { expect(last_diff.latest?).to be_truthy }
+    it { expect(first_diff.latest?).to be_falsey }
+  end
+
   describe '#diffs' do
     let(:mr) { create(:merge_request, :with_diffs) }
     let(:mr_diff) { mr.merge_request_diff }
@@ -43,5 +64,27 @@ describe MergeRequestDiff, models: true do
         end
       end
     end
+
+    describe '#commits_sha' do
+      shared_examples 'returning all commits SHA' do
+        it 'returns all commits SHA' do
+          commits_sha = subject.commits_sha
+
+          expect(commits_sha).to eq(subject.commits.map(&:sha))
+        end
+      end
+
+      context 'when commits were loaded' do
+        before do
+          subject.commits
+        end
+
+        it_behaves_like 'returning all commits SHA'
+      end
+
+      context 'when commits were not loaded' do
+        it_behaves_like 'returning all commits SHA'
+      end
+    end
   end
 end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c4138c4..12df6ad 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -9,7 +9,7 @@ describe MergeRequest, models: true do
     it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') }
     it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') }
     it { is_expected.to belong_to(:merge_user).class_name("User") }
-    it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) }
+    it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) }
   end
 
   describe 'modules' do
@@ -159,7 +159,7 @@ describe MergeRequest, models: true do
 
     context 'when there are MR diffs' do
       it 'delegates to the MR diffs' do
-        merge_request.merge_request_diff = MergeRequestDiff.new
+        merge_request.save
 
         expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options))
 
@@ -316,7 +316,7 @@ describe MergeRequest, models: true do
     end
 
     it "can be removed if the last commit is the head of the source branch" do
-      allow(subject.source_project).to receive(:commit).and_return(subject.diff_head_commit)
+      allow(subject).to receive(:source_branch_head).and_return(subject.diff_head_commit)
 
       expect(subject.can_remove_source_branch?(user)).to be_truthy
     end
@@ -477,8 +477,8 @@ describe MergeRequest, models: true do
 
         allow(subject).to receive(:diff_head_sha).and_return('123abc')
 
-        expect(subject.source_project).to receive(:pipeline).
-          with('123abc', 'master').
+        expect(subject.source_project).to receive(:pipeline_for).
+          with('master', '123abc').
           and_return(pipeline)
 
         expect(subject.pipeline).to eq(pipeline)
@@ -495,15 +495,77 @@ describe MergeRequest, models: true do
   end
 
   describe '#all_pipelines' do
-    let!(:pipelines) do
-      subject.merge_request_diff.commits.map do |commit|
-        create(:ci_empty_pipeline, project: subject.source_project, sha: commit.id, ref: subject.source_branch)
+    shared_examples 'returning pipelines with proper ordering' do
+      let!(:all_pipelines) do
+        subject.all_commits_sha.map do |sha|
+          create(:ci_empty_pipeline,
+                 project: subject.source_project,
+                 sha: sha,
+                 ref: subject.source_branch)
+        end
       end
+
+      it 'returns all pipelines' do
+        expect(subject.all_pipelines).not_to be_empty
+        expect(subject.all_pipelines).to eq(all_pipelines.reverse)
+      end
+    end
+
+    context 'with single merge_request_diffs' do
+      it_behaves_like 'returning pipelines with proper ordering'
     end
 
-    it 'returns a pipelines from source projects with proper ordering' do
-      expect(subject.all_pipelines).not_to be_empty
-      expect(subject.all_pipelines).to eq(pipelines.reverse)
+    context 'with multiple irrelevant merge_request_diffs' do
+      before do
+        subject.update(target_branch: 'v1.0.0')
+      end
+
+      it_behaves_like 'returning pipelines with proper ordering'
+    end
+
+    context 'with unsaved merge request' do
+      subject { build(:merge_request) }
+
+      let!(:pipeline) do
+        create(:ci_empty_pipeline,
+               project: subject.project,
+               sha: subject.diff_head_sha,
+               ref: subject.source_branch)
+      end
+
+      it 'returns pipelines from diff_head_sha' do
+        expect(subject.all_pipelines).to contain_exactly(pipeline)
+      end
+    end
+  end
+
+  describe '#all_commits_sha' do
+    let(:all_commits_sha) do
+      subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq
+    end
+
+    shared_examples 'returning all SHA' do
+      it 'returns all SHA from all merge_request_diffs' do
+        expect(subject.merge_request_diffs.size).to eq(2)
+        expect(subject.all_commits_sha).to eq(all_commits_sha)
+      end
+    end
+
+    context 'with a completely different branch' do
+      before do
+        subject.update(target_branch: 'v1.0.0')
+      end
+
+      it_behaves_like 'returning all SHA'
+    end
+
+    context 'with a branch having no difference' do
+      before do
+        subject.update(target_branch: 'v1.1.0')
+        subject.reload # make sure commits were not cached
+      end
+
+      it_behaves_like 'returning all SHA'
     end
   end
 
@@ -701,18 +763,67 @@ describe MergeRequest, models: true do
     end
   end
 
-  describe "#environments" do
+  describe '#environments' do
     let(:project)       { create(:project) }
-    let!(:environment)  { create(:environment, project: project) }
-    let!(:environment1) { create(:environment, project: project) }
-    let!(:environment2) { create(:environment, project: project) }
     let(:merge_request) { create(:merge_request, source_project: project) }
 
-    it 'selects deployed environments' do
-      create(:deployment, environment: environment, sha: project.commit('master').id)
-      create(:deployment, environment: environment1, sha: project.commit('feature').id)
+    context 'with multiple environments' do
+      let(:environments) { create_list(:environment, 3, project: project) }
 
-      expect(merge_request.environments).to eq [environment]
+      before do
+        create(:deployment, environment: environments.first, ref: 'master', sha: project.commit('master').id)
+        create(:deployment, environment: environments.second, ref: 'feature', sha: project.commit('feature').id)
+      end
+
+      it 'selects deployed environments' do
+        expect(merge_request.environments).to contain_exactly(environments.first)
+      end
+    end
+
+    context 'with environments on source project' do
+      let(:source_project) do
+        create(:project) do |fork_project|
+          fork_project.create_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+        end
+      end
+
+      let(:merge_request) do
+        create(:merge_request,
+               source_project: source_project, source_branch: 'feature',
+               target_project: project)
+      end
+
+      let(:source_environment) { create(:environment, project: source_project) }
+
+      before do
+        create(:deployment, environment: source_environment, ref: 'feature', sha: merge_request.diff_head_sha)
+      end
+
+      it 'selects deployed environments' do
+        expect(merge_request.environments).to contain_exactly(source_environment)
+      end
+
+      context 'with environments on target project' do
+        let(:target_environment) { create(:environment, project: project) }
+
+        before do
+          create(:deployment, environment: target_environment, tag: true, sha: merge_request.diff_head_sha)
+        end
+
+        it 'selects deployed environments' do
+          expect(merge_request.environments).to contain_exactly(source_environment, target_environment)
+        end
+      end
+    end
+
+    context 'without a diff_head_commit' do
+      before do
+        expect(merge_request).to receive(:diff_head_commit).and_return(nil)
+      end
+
+      it 'returns an empty array' do
+        expect(merge_request.environments).to be_empty
+      end
     end
   end
 
@@ -721,12 +832,15 @@ describe MergeRequest, models: true do
 
     let(:commit) { subject.project.commit(sample_commit.id) }
 
-    it "reloads the diff content" do
-      expect(subject.merge_request_diff).to receive(:reload_content)
-
+    it "does not change existing merge request diff" do
+      expect(subject.merge_request_diff).not_to receive(:save_git_content)
       subject.reload_diff
     end
 
+    it "creates new merge request diff" do
+      expect { subject.reload_diff }.to change { subject.merge_request_diffs.count }.by(1)
+    end
+
     it "executs diff cache service" do
       expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)
 
@@ -736,13 +850,15 @@ describe MergeRequest, models: true do
     it "updates diff note positions" do
       old_diff_refs = subject.diff_refs
 
-      merge_request_diff = subject.merge_request_diff
-
       # Update merge_request_diff so that #diff_refs will return commit.diff_refs
-      allow(merge_request_diff).to receive(:reload_content) do
-        merge_request_diff.base_commit_sha = commit.parent_id
-        merge_request_diff.start_commit_sha = commit.parent_id
-        merge_request_diff.head_commit_sha = commit.sha
+      allow(subject).to receive(:create_merge_request_diff) do
+        subject.merge_request_diffs.create(
+          base_commit_sha: commit.parent_id,
+          start_commit_sha: commit.parent_id,
+          head_commit_sha: commit.sha
+        )
+
+        subject.merge_request_diff(true)
       end
 
       expect(Notes::DiffPositionUpdateService).to receive(:new).with(
@@ -752,14 +868,31 @@ describe MergeRequest, models: true do
         new_diff_refs: commit.diff_refs,
         paths: note.position.paths
       ).and_call_original
-      expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note)
 
+      expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note)
       expect_any_instance_of(DiffNote).to receive(:save).once
 
       subject.reload_diff
     end
   end
 
+  describe '#branch_merge_base_commit' do
+    context 'source and target branch exist' do
+      it { expect(subject.branch_merge_base_commit.sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }
+      it { expect(subject.branch_merge_base_commit).to be_a(Commit) }
+    end
+
+    context 'when the target branch does not exist' do
+      before do
+        subject.project.repository.raw_repository.delete_branch(subject.target_branch)
+      end
+
+      it 'returns nil' do
+        expect(subject.branch_merge_base_commit).to be_nil
+      end
+    end
+  end
+
   describe "#diff_sha_refs" do
     context "with diffs" do
       subject { create(:merge_request, :with_diffs) }
@@ -940,4 +1073,157 @@ describe MergeRequest, models: true do
       expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
     end
   end
+
+  describe "#forked_source_project_missing?" do
+    let(:project)      { create(:project) }
+    let(:fork_project) { create(:project, forked_from_project: project) }
+    let(:user)         { create(:user) }
+    let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
+
+    context "when the fork exists" do
+      let(:merge_request) do
+        create(:merge_request,
+          source_project: fork_project,
+          target_project: project)
+      end
+
+      it { expect(merge_request.forked_source_project_missing?).to be_falsey }
+    end
+
+    context "when the source project is the same as the target project" do
+      let(:merge_request) { create(:merge_request, source_project: project) }
+
+      it { expect(merge_request.forked_source_project_missing?).to be_falsey }
+    end
+
+    context "when the fork does not exist" do
+      let(:merge_request) do
+        create(:merge_request,
+          source_project: fork_project,
+          target_project: project)
+      end
+
+      it "returns true" do
+        unlink_project.execute
+        merge_request.reload
+
+        expect(merge_request.forked_source_project_missing?).to be_truthy
+      end
+    end
+  end
+
+  describe "#closed_without_fork?" do
+    let(:project)      { create(:project) }
+    let(:fork_project) { create(:project, forked_from_project: project) }
+    let(:user)         { create(:user) }
+    let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
+
+    context "when the merge request is closed" do
+      let(:closed_merge_request) do
+        create(:closed_merge_request,
+          source_project: fork_project,
+          target_project: project)
+      end
+
+      it "returns false if the fork exist" do
+        expect(closed_merge_request.closed_without_fork?).to be_falsey
+      end
+
+      it "returns true if the fork does not exist" do
+        unlink_project.execute
+        closed_merge_request.reload
+
+        expect(closed_merge_request.closed_without_fork?).to be_truthy
+      end
+    end
+
+    context "when the merge request is open" do
+      let(:open_merge_request) do
+        create(:merge_request,
+          source_project: fork_project,
+          target_project: project)
+      end
+
+      it "returns false" do
+        expect(open_merge_request.closed_without_fork?).to be_falsey
+      end
+    end
+  end
+
+  describe '#closed_without_source_project?' do
+    let(:project)      { create(:project) }
+    let(:user)         { create(:user) }
+    let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) }
+    let(:destroy_service) { Projects::DestroyService.new(fork_project, user) }
+
+    context 'when the merge request is closed' do
+      let(:closed_merge_request) do
+        create(:closed_merge_request,
+          source_project: fork_project,
+          target_project: project)
+      end
+
+      it 'returns false if the source project exists' do
+        expect(closed_merge_request.closed_without_source_project?).to be_falsey
+      end
+
+      it 'returns true if the source project does not exist' do
+        destroy_service.execute
+        closed_merge_request.reload
+
+        expect(closed_merge_request.closed_without_source_project?).to be_truthy
+      end
+    end
+
+    context 'when the merge request is open' do
+      it 'returns false' do
+        expect(subject.closed_without_source_project?).to be_falsey
+      end
+    end
+  end
+
+  describe '#reopenable?' do
+    context 'when the merge request is closed' do
+      it 'returns true' do
+        subject.close
+
+        expect(subject.reopenable?).to be_truthy
+      end
+
+      context 'forked project' do
+        let(:project)      { create(:project) }
+        let(:user)         { create(:user) }
+        let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) }
+        let(:merge_request) do
+          create(:closed_merge_request,
+            source_project: fork_project,
+            target_project: project)
+        end
+
+        it 'returns false if unforked' do
+          Projects::UnlinkForkService.new(fork_project, user).execute
+
+          expect(merge_request.reload.reopenable?).to be_falsey
+        end
+
+        it 'returns false if the source project is deleted' do
+          Projects::DestroyService.new(fork_project, user).execute
+
+          expect(merge_request.reload.reopenable?).to be_falsey
+        end
+
+        it 'returns false if the merge request is merged' do
+          merge_request.update_attributes(state: 'merged')
+
+          expect(merge_request.reload.reopenable?).to be_falsey
+        end
+      end
+    end
+
+    context 'when the merge request is opened' do
+      it 'returns false' do
+        expect(subject.reopenable?).to be_falsey
+      end
+    end
+  end
 end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index ef27470..e6b6e7c 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -85,8 +85,6 @@ describe Note, models: true do
       @u1 = create(:user)
       @u2 = create(:user)
       @u3 = create(:user)
-      @abilities = Six.new
-      @abilities << Ability
     end
 
     describe 'read' do
@@ -95,9 +93,9 @@ describe Note, models: true do
         @p2.project_members.create(user: @u3, access_level: ProjectMember::GUEST)
       end
 
-      it { expect(@abilities.allowed?(@u1, :read_note, @p1)).to be_falsey }
-      it { expect(@abilities.allowed?(@u2, :read_note, @p1)).to be_truthy }
-      it { expect(@abilities.allowed?(@u3, :read_note, @p1)).to be_falsey }
+      it { expect(Ability.allowed?(@u1, :read_note, @p1)).to be_falsey }
+      it { expect(Ability.allowed?(@u2, :read_note, @p1)).to be_truthy }
+      it { expect(Ability.allowed?(@u3, :read_note, @p1)).to be_falsey }
     end
 
     describe 'write' do
@@ -106,9 +104,9 @@ describe Note, models: true do
         @p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER)
       end
 
-      it { expect(@abilities.allowed?(@u1, :create_note, @p1)).to be_falsey }
-      it { expect(@abilities.allowed?(@u2, :create_note, @p1)).to be_truthy }
-      it { expect(@abilities.allowed?(@u3, :create_note, @p1)).to be_falsey }
+      it { expect(Ability.allowed?(@u1, :create_note, @p1)).to be_falsey }
+      it { expect(Ability.allowed?(@u2, :create_note, @p1)).to be_truthy }
+      it { expect(Ability.allowed?(@u3, :create_note, @p1)).to be_falsey }
     end
 
     describe 'admin' do
@@ -118,9 +116,9 @@ describe Note, models: true do
         @p2.project_members.create(user: @u3, access_level: ProjectMember::MASTER)
       end
 
-      it { expect(@abilities.allowed?(@u1, :admin_note, @p1)).to be_falsey }
-      it { expect(@abilities.allowed?(@u2, :admin_note, @p1)).to be_truthy }
-      it { expect(@abilities.allowed?(@u3, :admin_note, @p1)).to be_falsey }
+      it { expect(Ability.allowed?(@u1, :admin_note, @p1)).to be_falsey }
+      it { expect(Ability.allowed?(@u2, :admin_note, @p1)).to be_truthy }
+      it { expect(Ability.allowed?(@u3, :admin_note, @p1)).to be_falsey }
     end
   end
 
@@ -225,7 +223,7 @@ describe Note, models: true do
     let(:note) do
       create :note,
         noteable: ext_issue, project: ext_proj,
-        note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
+        note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}",
         system: true
     end
 
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
new file mode 100644
index 0000000..8d554a0
--- /dev/null
+++ b/spec/models/project_feature_spec.rb
@@ -0,0 +1,91 @@
+require 'spec_helper'
+
+describe ProjectFeature do
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+
+  describe '#feature_available?' do
+    let(:features) { %w(issues wiki builds merge_requests snippets) }
+
+    context 'when features are disabled' do
+      it "returns false" do
+        features.each do |feature|
+          project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::DISABLED)
+          expect(project.feature_available?(:issues, user)).to eq(false)
+        end
+      end
+    end
+
+    context 'when features are enabled only for team members' do
+      it "returns false when user is not a team member" do
+        features.each do |feature|
+          project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+          expect(project.feature_available?(:issues, user)).to eq(false)
+        end
+      end
+
+      it "returns true when user is a team member" do
+        project.team << [user, :developer]
+
+        features.each do |feature|
+          project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+          expect(project.feature_available?(:issues, user)).to eq(true)
+        end
+      end
+
+      it "returns true when user is a member of project group" do
+        group = create(:group)
+        project = create(:project, namespace: group)
+        group.add_developer(user)
+
+        features.each do |feature|
+          project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+          expect(project.feature_available?(:issues, user)).to eq(true)
+        end
+      end
+
+      it "returns true if user is an admin" do
+        user.update_attribute(:admin, true)
+
+        features.each do |feature|
+          project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+          expect(project.feature_available?(:issues, user)).to eq(true)
+        end
+      end
+    end
+
+    context 'when feature is enabled for everyone' do
+      it "returns true" do
+        features.each do |feature|
+          project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::ENABLED)
+          expect(project.feature_available?(:issues, user)).to eq(true)
+        end
+      end
+    end
+  end
+
+  describe '#*_enabled?' do
+    let(:features) { %w(wiki builds merge_requests) }
+
+    it "returns false when feature is disabled" do
+      features.each do |feature|
+        project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::DISABLED)
+        expect(project.public_send("#{feature}_enabled?")).to eq(false)
+      end
+    end
+
+    it "returns true when feature is enabled only for team members" do
+      features.each do |feature|
+        project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+        expect(project.public_send("#{feature}_enabled?")).to eq(true)
+      end
+    end
+
+    it "returns true when feature is enabled for everyone" do
+      features.each do |feature|
+        project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::ENABLED)
+        expect(project.public_send("#{feature}_enabled?")).to eq(true)
+      end
+    end
+  end
+end
diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb
deleted file mode 100644
index 3637907..0000000
--- a/spec/models/project_security_spec.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-require 'spec_helper'
-
-describe Project, models: true do
-  describe 'authorization' do
-    before do
-      @p1 = create(:project)
-
-      @u1 = create(:user)
-      @u2 = create(:user)
-      @u3 = create(:user)
-      @u4 = @p1.owner
-
-      @abilities = Six.new
-      @abilities << Ability
-    end
-
-    let(:guest_actions) { Ability.project_guest_rules }
-    let(:report_actions) { Ability.project_report_rules }
-    let(:dev_actions) { Ability.project_dev_rules }
-    let(:master_actions) { Ability.project_master_rules }
-    let(:owner_actions) { Ability.project_owner_rules }
-
-    describe "Non member rules" do
-      it "denies for non-project users any actions" do
-        owner_actions.each do |action|
-          expect(@abilities.allowed?(@u1, action, @p1)).to be_falsey
-        end
-      end
-    end
-
-    describe "Guest Rules" do
-      before do
-        @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::GUEST)
-      end
-
-      it "allows for project user any guest actions" do
-        guest_actions.each do |action|
-          expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
-        end
-      end
-    end
-
-    describe "Report Rules" do
-      before do
-        @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER)
-      end
-
-      it "allows for project user any report actions" do
-        report_actions.each do |action|
-          expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
-        end
-      end
-    end
-
-    describe "Developer Rules" do
-      before do
-        @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER)
-        @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::DEVELOPER)
-      end
-
-      it "denies for developer master-specific actions" do
-        [dev_actions - report_actions].each do |action|
-          expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
-        end
-      end
-
-      it "allows for project user any dev actions" do
-        dev_actions.each do |action|
-          expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
-        end
-      end
-    end
-
-    describe "Master Rules" do
-      before do
-        @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER)
-        @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER)
-      end
-
-      it "denies for developer master-specific actions" do
-        [master_actions - dev_actions].each do |action|
-          expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
-        end
-      end
-
-      it "allows for project user any master actions" do
-        master_actions.each do |action|
-          expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
-        end
-      end
-    end
-
-    describe "Owner Rules" do
-      before do
-        @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER)
-        @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER)
-      end
-
-      it "denies for masters admin-specific actions" do
-        [owner_actions - master_actions].each do |action|
-          expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
-        end
-      end
-
-      it "allows for project owner any admin actions" do
-        owner_actions.each do |action|
-          expect(@abilities.allowed?(@u4, action, @p1)).to be_truthy
-        end
-      end
-    end
-  end
-end
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index dc702cf..8e5145e 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe AsanaService, models: true do
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index d672d80..4c5acb7 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe AssemblaService, models: true do
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 9ae461f..d7e1a4e 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe BambooService, models: true do
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb
index a6d9717..739cc72 100644
--- a/spec/models/project_services/bugzilla_service_spec.rb
+++ b/spec/models/project_services/bugzilla_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe BugzillaService, models: true do
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 0866e15..6f65beb 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe BuildkiteService, models: true do
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index c76ae21..a3b9d08 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe CampfireService, models: true do
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
index ff976f8..7020667 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe CustomIssueTrackerService, models: true do
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 8ef8922..f13bb1e 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe DroneCiService, models: true do
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index d7c5ea9..342d86a 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#  build_events          :boolean          default(FALSE), not null
-#
-
 require 'spec_helper'
 
 describe ExternalWikiService, models: true do
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index d255701..d6db02d 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe FlowdockService, models: true do
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index 3d0b6c9..529044d 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe GemnasiumService, models: true do
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 8ef79a1..652804f 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe GitlabIssueTrackerService, models: true do
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 34eafbe..cf71368 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe HipchatService, models: true do
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index ffb17fd..f8c45b3 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 require 'socket'
 require 'json'
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 9037ca5..b48a317 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe JiraService, models: true do
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index d098d98..45b2f10 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe PivotaltrackerService, models: true do
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 5959c81..8fc92a9 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe PushoverService, models: true do
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 7d14f6e..b8679cd 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe RedmineService, models: true do
diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb
index 7fcfdf0..452f4e2 100644
--- a/spec/models/project_services/slack_service/build_message_spec.rb
+++ b/spec/models/project_services/slack_service/build_message_spec.rb
@@ -10,7 +10,7 @@ describe SlackService::BuildMessage do
       tag: false,
 
       project_name: 'project_name',
-      project_url: 'somewhere.com',
+      project_url: 'example.gitlab.com',
 
       commit: {
         status: status,
@@ -20,42 +20,38 @@ describe SlackService::BuildMessage do
     }
   end
 
-  context 'succeeded' do
+  let(:message) { build_message }
+
+  context 'build succeeded' do
     let(:status) { 'success' }
     let(:color) { 'good' }
     let(:duration) { 10 }
-    
+    let(:message) { build_message('passed') }
+
     it 'returns a message with information about succeeded build' do
-      message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker passed in 10 seconds'
       expect(subject.pretext).to be_empty
       expect(subject.fallback).to eq(message)
       expect(subject.attachments).to eq([text: message, color: color])
     end
   end
 
-  context 'failed' do
+  context 'build failed' do
     let(:status) { 'failed' }
     let(:color) { 'danger' }
     let(:duration) { 10 }
 
     it 'returns a message with information about failed build' do
-      message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 10 seconds'
       expect(subject.pretext).to be_empty
       expect(subject.fallback).to eq(message)
       expect(subject.attachments).to eq([text: message, color: color])
     end
-  end 
-  
-  describe '#seconds_name' do
-    let(:status) { 'failed' }
-    let(:color) { 'danger' }
-    let(:duration) { 1 }
+  end
 
-    it 'returns seconds as singular when there is only one' do
-      message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 1 second'
-      expect(subject.pretext).to be_empty
-      expect(subject.fallback).to eq(message)
-      expect(subject.attachments).to eq([text: message, color: color])
-    end
+  def build_message(status_text = status)
+    "<example.gitlab.com|project_name>:" \
+    " Commit <example.gitlab.com/commit/" \
+    "97de212e80737a608d939f648d959671fb0a0142/builds|97de212e>" \
+    " of <example.gitlab.com/commits/develop|develop> branch" \
+    " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
   end
 end
diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb
new file mode 100644
index 0000000..babb390
--- /dev/null
+++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe SlackService::PipelineMessage do
+  subject { SlackService::PipelineMessage.new(args) }
+
+  let(:args) do
+    {
+      object_attributes: {
+        id: 123,
+        sha: '97de212e80737a608d939f648d959671fb0a0142',
+        tag: false,
+        ref: 'develop',
+        status: status,
+        duration: duration
+      },
+      project: { path_with_namespace: 'project_name',
+                 web_url: 'example.gitlab.com' },
+      commit: { author_name: 'hacker' }
+    }
+  end
+
+  let(:message) { build_message }
+
+  context 'pipeline succeeded' do
+    let(:status) { 'success' }
+    let(:color) { 'good' }
+    let(:duration) { 10 }
+    let(:message) { build_message('passed') }
+
+    it 'returns a message with information about succeeded build' do
+      expect(subject.pretext).to be_empty
+      expect(subject.fallback).to eq(message)
+      expect(subject.attachments).to eq([text: message, color: color])
+    end
+  end
+
+  context 'pipeline failed' do
+    let(:status) { 'failed' }
+    let(:color) { 'danger' }
+    let(:duration) { 10 }
+
+    it 'returns a message with information about failed build' do
+      expect(subject.pretext).to be_empty
+      expect(subject.fallback).to eq(message)
+      expect(subject.attachments).to eq([text: message, color: color])
+    end
+  end
+
+  def build_message(status_text = status)
+    "<example.gitlab.com|project_name>:" \
+    " Pipeline <example.gitlab.com/pipelines/123|97de212e>" \
+    " of <example.gitlab.com/commits/develop|develop> branch" \
+    " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
+  end
+end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 28af68d..c07a70a 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -1,26 +1,9 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe SlackService, models: true do
+  let(:slack) { SlackService.new }
+  let(:webhook_url) { 'https://example.gitlab.com/' }
+
   describe "Associations" do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
@@ -42,15 +25,14 @@ describe SlackService, models: true do
   end
 
   describe "Execute" do
-    let(:slack)   { SlackService.new }
     let(:user)    { create(:user) }
     let(:project) { create(:project) }
+    let(:username) { 'slack_username' }
+    let(:channel)  { 'slack_channel' }
+
     let(:push_sample_data) do
       Gitlab::DataBuilder::Push.build_sample(project, user)
     end
-    let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
-    let(:username) { 'slack_username' }
-    let(:channel) { 'slack_channel' }
 
     before do
       allow(slack).to receive_messages(
@@ -212,10 +194,8 @@ describe SlackService, models: true do
   end
 
   describe "Note events" do
-    let(:slack)   { SlackService.new }
     let(:user) { create(:user) }
     let(:project) { create(:project, creator_id: user.id) }
-    let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
 
     before do
       allow(slack).to receive_messages(
@@ -285,4 +265,63 @@ describe SlackService, models: true do
       end
     end
   end
+
+  describe 'Pipeline events' do
+    let(:user) { create(:user) }
+    let(:project) { create(:project) }
+
+    let(:pipeline) do
+      create(:ci_pipeline,
+             project: project, status: status,
+             sha: project.commit.sha, ref: project.default_branch)
+    end
+
+    before do
+      allow(slack).to receive_messages(
+        project: project,
+        service_hook: true,
+        webhook: webhook_url
+      )
+    end
+
+    shared_examples 'call Slack API' do
+      before do
+        WebMock.stub_request(:post, webhook_url)
+      end
+
+      it 'calls Slack API for pipeline events' do
+        data = Gitlab::DataBuilder::Pipeline.build(pipeline)
+        slack.execute(data)
+
+        expect(WebMock).to have_requested(:post, webhook_url).once
+      end
+    end
+
+    context 'with failed pipeline' do
+      let(:status) { 'failed' }
+
+      it_behaves_like 'call Slack API'
+    end
+
+    context 'with succeeded pipeline' do
+      let(:status) { 'success' }
+
+      context 'with default to notify_only_broken_pipelines' do
+        it 'does not call Slack API for pipeline events' do
+          data = Gitlab::DataBuilder::Pipeline.build(pipeline)
+          result = slack.execute(data)
+
+          expect(result).to be_falsy
+        end
+      end
+
+      context 'with setting notify_only_broken_pipelines to false' do
+        before do
+          slack.notify_only_broken_pipelines = false
+        end
+
+        it_behaves_like 'call Slack API'
+      end
+    end
+  end
 end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 474715d..f7e8788 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe TeamcityService, models: true do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 9a36600..83f61f0 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -6,6 +6,7 @@ describe Project, models: true do
     it { is_expected.to belong_to(:namespace) }
     it { is_expected.to belong_to(:creator).class_name('User') }
     it { is_expected.to have_many(:users) }
+    it { is_expected.to have_many(:services) }
     it { is_expected.to have_many(:events).dependent(:destroy) }
     it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
     it { is_expected.to have_many(:issues).dependent(:destroy) }
@@ -24,6 +25,30 @@ describe Project, models: true do
     it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
     it { is_expected.to have_one(:asana_service).dependent(:destroy) }
     it { is_expected.to have_one(:board).dependent(:destroy) }
+    it { is_expected.to have_one(:campfire_service).dependent(:destroy) }
+    it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) }
+    it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) }
+    it { is_expected.to have_one(:builds_email_service).dependent(:destroy) }
+    it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) }
+    it { is_expected.to have_one(:irker_service).dependent(:destroy) }
+    it { is_expected.to have_one(:pivotaltracker_service).dependent(:destroy) }
+    it { is_expected.to have_one(:hipchat_service).dependent(:destroy) }
+    it { is_expected.to have_one(:flowdock_service).dependent(:destroy) }
+    it { is_expected.to have_one(:assembla_service).dependent(:destroy) }
+    it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) }
+    it { is_expected.to have_one(:buildkite_service).dependent(:destroy) }
+    it { is_expected.to have_one(:bamboo_service).dependent(:destroy) }
+    it { is_expected.to have_one(:teamcity_service).dependent(:destroy) }
+    it { is_expected.to have_one(:jira_service).dependent(:destroy) }
+    it { is_expected.to have_one(:redmine_service).dependent(:destroy) }
+    it { is_expected.to have_one(:custom_issue_tracker_service).dependent(:destroy) }
+    it { is_expected.to have_one(:bugzilla_service).dependent(:destroy) }
+    it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) }
+    it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) }
+    it { is_expected.to have_one(:project_feature).dependent(:destroy) }
+    it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:destroy) }
+    it { is_expected.to have_one(:last_event).class_name('Event') }
+    it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
     it { is_expected.to have_many(:commit_statuses) }
     it { is_expected.to have_many(:pipelines) }
     it { is_expected.to have_many(:builds) }
@@ -31,9 +56,16 @@ describe Project, models: true do
     it { is_expected.to have_many(:runners) }
     it { is_expected.to have_many(:variables) }
     it { is_expected.to have_many(:triggers) }
+    it { is_expected.to have_many(:labels).dependent(:destroy) }
+    it { is_expected.to have_many(:users_star_projects).dependent(:destroy) }
     it { is_expected.to have_many(:environments).dependent(:destroy) }
     it { is_expected.to have_many(:deployments).dependent(:destroy) }
     it { is_expected.to have_many(:todos).dependent(:destroy) }
+    it { is_expected.to have_many(:releases).dependent(:destroy) }
+    it { is_expected.to have_many(:lfs_objects_projects).dependent(:destroy) }
+    it { is_expected.to have_many(:project_group_links).dependent(:destroy) }
+    it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
+    it { is_expected.to have_many(:forks).through(:forked_project_links) }
 
     describe '#members & #requesters' do
       let(:project) { create(:project) }
@@ -178,7 +210,7 @@ describe Project, models: true do
       expect(project.runners_token).not_to eq('')
     end
 
-    it 'does not set an random toke if one provided' do
+    it 'does not set an random token if one provided' do
       project = FactoryGirl.create :empty_project, runners_token: 'my-token'
       expect(project.runners_token).to eq('my-token')
     end
@@ -276,20 +308,23 @@ describe Project, models: true do
   end
 
   describe 'last_activity methods' do
-    let(:project) { create(:project) }
-    let(:last_event) { double(created_at: Time.now) }
+    let(:timestamp) { Time.now - 2.hours }
+    let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
 
     describe 'last_activity' do
       it 'alias last_activity to last_event' do
-        allow(project).to receive(:last_event).and_return(last_event)
+        last_event = create(:event, project: project)
+
         expect(project.last_activity).to eq(last_event)
       end
     end
 
     describe 'last_activity_date' do
       it 'returns the creation date of the project\'s last event if present' do
-        create(:event, project: project)
-        expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i)
+        expect_any_instance_of(Event).to receive(:try_obtain_lease).and_return(true)
+        new_event = create(:event, project: project, created_at: Time.now)
+
+        expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
       end
 
       it 'returns the project\'s last update date if it has no events' do
@@ -506,6 +541,18 @@ describe Project, models: true do
     end
   end
 
+  describe '#has_wiki?' do
+    let(:no_wiki_project) { build(:project, wiki_enabled: false, has_external_wiki: false) }
+    let(:wiki_enabled_project) { build(:project) }
+    let(:external_wiki_project) { build(:project, has_external_wiki: true) }
+
+    it 'returns true if project is wiki enabled or has external wiki' do
+      expect(wiki_enabled_project).to have_wiki
+      expect(external_wiki_project).to have_wiki
+      expect(no_wiki_project).not_to have_wiki
+    end
+  end
+
   describe '#external_wiki' do
     let(:project) { create(:project) }
 
@@ -685,31 +732,43 @@ describe Project, models: true do
     end
   end
 
-  describe '#pipeline' do
-    let(:project) { create :project }
-    let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' }
-
-    subject { project.pipeline(pipeline.sha, 'master') }
+  describe '#pipeline_for' do
+    let(:project) { create(:project) }
+    let!(:pipeline) { create_pipeline }
 
-    it { is_expected.to eq(pipeline) }
+    shared_examples 'giving the correct pipeline' do
+      it { is_expected.to eq(pipeline) }
 
-    context 'return latest' do
-      let(:pipeline2) { create :ci_pipeline, project: project, ref: 'master' }
+      context 'return latest' do
+        let!(:pipeline2) { create_pipeline }
 
-      before do
-        pipeline
-        pipeline2
+        it { is_expected.to eq(pipeline2) }
       end
+    end
+
+    context 'with explicit sha' do
+      subject { project.pipeline_for('master', pipeline.sha) }
+
+      it_behaves_like 'giving the correct pipeline'
+    end
+
+    context 'with implicit sha' do
+      subject { project.pipeline_for('master') }
 
-      it { is_expected.to eq(pipeline2) }
+      it_behaves_like 'giving the correct pipeline'
+    end
+
+    def create_pipeline
+      create(:ci_pipeline,
+             project: project,
+             ref: 'master',
+             sha: project.commit('master').sha)
     end
   end
 
   describe '#builds_enabled' do
     let(:project) { create :project }
 
-    before { project.builds_enabled = true }
-
     subject { project.builds_enabled }
 
     it { expect(project.builds_enabled?).to be_truthy }
@@ -1361,6 +1420,68 @@ describe Project, models: true do
     end
   end
 
+  describe '#lfs_enabled?' do
+    let(:project) { create(:project) }
+
+    shared_examples 'project overrides group' do
+      it 'returns true when enabled in project' do
+        project.update_attribute(:lfs_enabled, true)
+
+        expect(project.lfs_enabled?).to be_truthy
+      end
+
+      it 'returns false when disabled in project' do
+        project.update_attribute(:lfs_enabled, false)
+
+        expect(project.lfs_enabled?).to be_falsey
+      end
+
+      it 'returns the value from the namespace, when no value is set in project' do
+        expect(project.lfs_enabled?).to eq(project.namespace.lfs_enabled?)
+      end
+    end
+
+    context 'LFS disabled in group' do
+      before do
+        project.namespace.update_attribute(:lfs_enabled, false)
+        enable_lfs
+      end
+
+      it_behaves_like 'project overrides group'
+    end
+
+    context 'LFS enabled in group' do
+      before do
+        project.namespace.update_attribute(:lfs_enabled, true)
+        enable_lfs
+      end
+
+      it_behaves_like 'project overrides group'
+    end
+
+    describe 'LFS disabled globally' do
+      shared_examples 'it always returns false' do
+        it do
+          expect(project.lfs_enabled?).to be_falsey
+          expect(project.namespace.lfs_enabled?).to be_falsey
+        end
+      end
+
+      context 'when no values are set' do
+        it_behaves_like 'it always returns false'
+      end
+
+      context 'when all values are set to true' do
+        before do
+          project.namespace.update_attribute(:lfs_enabled, true)
+          project.update_attribute(:lfs_enabled, true)
+        end
+
+        it_behaves_like 'it always returns false'
+      end
+    end
+  end
+
   describe '.where_paths_in' do
     context 'without any paths' do
       it 'returns an empty relation' do
@@ -1442,4 +1563,132 @@ describe Project, models: true do
       expect(shared_project.authorized_for_user?(master, Gitlab::Access::MASTER)).to be(true)
     end
   end
+
+  describe 'change_head' do
+    let(:project) { create(:project) }
+
+    it 'calls the before_change_head method' do
+      expect(project.repository).to receive(:before_change_head)
+      project.change_head(project.default_branch)
+    end
+
+    it 'creates the new reference with rugged' do
+      expect(project.repository.rugged.references).to receive(:create).with('HEAD',
+                                                                            "refs/heads/#{project.default_branch}",
+                                                                            force: true)
+      project.change_head(project.default_branch)
+    end
+
+    it 'copies the gitattributes' do
+      expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch)
+      project.change_head(project.default_branch)
+    end
+
+    it 'expires the avatar cache' do
+      expect(project.repository).to receive(:expire_avatar_cache).with(project.default_branch)
+      project.change_head(project.default_branch)
+    end
+
+    it 'reloads the default branch' do
+      expect(project).to receive(:reload_default_branch)
+      project.change_head(project.default_branch)
+    end
+  end
+
+  describe '#pushes_since_gc' do
+    let(:project) { create(:project) }
+
+    after do
+      project.reset_pushes_since_gc
+    end
+
+    context 'without any pushes' do
+      it 'returns 0' do
+        expect(project.pushes_since_gc).to eq(0)
+      end
+    end
+
+    context 'with a number of pushes' do
+      it 'returns the number of pushes' do
+        3.times { project.increment_pushes_since_gc }
+
+        expect(project.pushes_since_gc).to eq(3)
+      end
+    end
+  end
+
+  describe '#increment_pushes_since_gc' do
+    let(:project) { create(:project) }
+
+    after do
+      project.reset_pushes_since_gc
+    end
+
+    it 'increments the number of pushes since the last GC' do
+      3.times { project.increment_pushes_since_gc }
+
+      expect(project.pushes_since_gc).to eq(3)
+    end
+  end
+
+  describe '#reset_pushes_since_gc' do
+    let(:project) { create(:project) }
+
+    after do
+      project.reset_pushes_since_gc
+    end
+
+    it 'resets the number of pushes since the last GC' do
+      3.times { project.increment_pushes_since_gc }
+
+      project.reset_pushes_since_gc
+
+      expect(project.pushes_since_gc).to eq(0)
+    end
+  end
+
+  describe '#environments_for' do
+    let(:project) { create(:project) }
+    let(:environment) { create(:environment, project: project) }
+
+    context 'tagged deployment' do
+      before do
+        create(:deployment, environment: environment, ref: '1.0', tag: true, sha: project.commit.id)
+      end
+
+      it 'returns environment when with_tags is set' do
+        expect(project.environments_for('master', project.commit, with_tags: true)).to contain_exactly(environment)
+      end
+
+      it 'does not return environment when no with_tags is set' do
+        expect(project.environments_for('master', project.commit)).to be_empty
+      end
+
+      it 'does not return environment when commit is not part of deployment' do
+        expect(project.environments_for('master', project.commit('feature'))).to be_empty
+      end
+    end
+
+    context 'branch deployment' do
+      before do
+        create(:deployment, environment: environment, ref: 'master', sha: project.commit.id)
+      end
+
+      it 'returns environment when ref is set' do
+        expect(project.environments_for('master', project.commit)).to contain_exactly(environment)
+      end
+
+      it 'does not environment when ref is different' do
+        expect(project.environments_for('feature', project.commit)).to be_empty
+      end
+
+      it 'does not return environment when commit is not part of deployment' do
+        expect(project.environments_for('master', project.commit('feature'))).to be_empty
+      end
+    end
+  end
+
+  def enable_lfs
+    allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+  end
 end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 1fea50a..db29f4d 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -16,6 +16,21 @@ describe Repository, models: true do
     merge_commit_id = repository.merge(user, merge_request, commit_options)
     repository.commit(merge_commit_id)
   end
+  let(:author_email) { FFaker::Internet.email }
+
+  # I have to remove periods from the end of the name
+  # This happened when the user's name had a suffix (i.e. "Sr.")
+  # This seems to be what git does under the hood. For example, this commit:
+  #
+  # $ git commit --author='Foo Sr. <foo at example.com>' -m 'Where's my trailing period?'
+  #
+  # results in this:
+  #
+  # $ git show --pretty
+  # ...
+  # Author: Foo Sr <foo at example.com>
+  # ...
+  let(:author_name) { FFaker::Name.name.chomp("\.") }
 
   describe '#branch_names_contains' do
     subject { repository.branch_names_contains(sample_commit.id) }
@@ -132,7 +147,31 @@ describe Repository, models: true do
     end
   end
 
-  describe :commit_file do
+  describe "#commit_dir" do
+    it "commits a change that creates a new directory" do
+      expect do
+        repository.commit_dir(user, 'newdir', 'Create newdir', 'master')
+      end.to change { repository.commits('master').count }.by(1)
+
+      newdir = repository.tree('master', 'newdir')
+      expect(newdir.path).to eq('newdir')
+    end
+
+    context "when an author is specified" do
+      it "uses the given email/name to set the commit's author" do
+        expect do
+          repository.commit_dir(user, "newdir", "Add newdir", 'master', author_email: author_email, author_name: author_name)
+        end.to change { repository.commits('master').count }.by(1)
+
+        last_commit = repository.commit
+
+        expect(last_commit.author_email).to eq(author_email)
+        expect(last_commit.author_name).to eq(author_name)
+      end
+    end
+  end
+
+  describe "#commit_file" do
     it 'commits change to a file successfully' do
       expect do
         repository.commit_file(user, 'CHANGELOG', 'Changelog!',
@@ -144,9 +183,23 @@ describe Repository, models: true do
 
       expect(blob.data).to eq('Changelog!')
     end
+
+    context "when an author is specified" do
+      it "uses the given email/name to set the commit's author" do
+        expect do
+          repository.commit_file(user, "README", 'README!', 'Add README',
+                                'master', true, author_email: author_email, author_name: author_name)
+        end.to change { repository.commits('master').count }.by(1)
+
+        last_commit = repository.commit
+
+        expect(last_commit.author_email).to eq(author_email)
+        expect(last_commit.author_name).to eq(author_name)
+      end
+    end
   end
 
-  describe :update_file do
+  describe "#update_file" do
     it 'updates filename successfully' do
       expect do
         repository.update_file(user, 'NEWLICENSE', 'Copyright!',
@@ -160,6 +213,85 @@ describe Repository, models: true do
       expect(files).not_to include('LICENSE')
       expect(files).to include('NEWLICENSE')
     end
+
+    context "when an author is specified" do
+      it "uses the given email/name to set the commit's author" do
+        repository.commit_file(user, "README", 'README!', 'Add README', 'master', true)
+
+        expect do
+          repository.update_file(user, 'README', "Updated README!",
+                                branch: 'master',
+                                previous_path: 'README',
+                                message: 'Update README',
+                                author_email: author_email,
+                                author_name: author_name)
+        end.to change { repository.commits('master').count }.by(1)
+
+        last_commit = repository.commit
+
+        expect(last_commit.author_email).to eq(author_email)
+        expect(last_commit.author_name).to eq(author_name)
+      end
+    end
+  end
+
+  describe "#remove_file" do
+    it 'removes file successfully' do
+      repository.commit_file(user, "README", 'README!', 'Add README', 'master', true)
+
+      expect do
+        repository.remove_file(user, "README", "Remove README", 'master')
+      end.to change { repository.commits('master').count }.by(1)
+
+      expect(repository.blob_at('master', 'README')).to be_nil
+    end
+
+    context "when an author is specified" do
+      it "uses the given email/name to set the commit's author" do
+        repository.commit_file(user, "README", 'README!', 'Add README', 'master', true)
+
+        expect do
+          repository.remove_file(user, "README", "Remove README", 'master', author_email: author_email, author_name: author_name)
+        end.to change { repository.commits('master').count }.by(1)
+
+        last_commit = repository.commit
+
+        expect(last_commit.author_email).to eq(author_email)
+        expect(last_commit.author_name).to eq(author_name)
+      end
+    end
+  end
+
+  describe '#get_committer_and_author' do
+    it 'returns the committer and author data' do
+      options = repository.get_committer_and_author(user)
+      expect(options[:committer][:email]).to eq(user.email)
+      expect(options[:author][:email]).to eq(user.email)
+    end
+
+    context 'when the email/name are given' do
+      it 'returns an object containing the email/name' do
+        options = repository.get_committer_and_author(user, email: author_email, name: author_name)
+        expect(options[:author][:email]).to eq(author_email)
+        expect(options[:author][:name]).to eq(author_name)
+      end
+    end
+
+    context 'when the email is given but the name is not' do
+      it 'returns the committer as the author' do
+        options = repository.get_committer_and_author(user, email: author_email)
+        expect(options[:author][:email]).to eq(user.email)
+        expect(options[:author][:name]).to eq(user.name)
+      end
+    end
+
+    context 'when the name is given but the email is not' do
+      it 'returns nil' do
+        options = repository.get_committer_and_author(user, name: author_name)
+        expect(options[:author][:email]).to eq(user.email)
+        expect(options[:author][:name]).to eq(user.name)
+      end
+    end
   end
 
   describe "search_files" do
@@ -186,32 +318,6 @@ describe Repository, models: true do
       it { is_expected.to be_an String }
       it { expect(subject.lines[2]).to eq("master:CHANGELOG:188:  - Feature: Replace teams with group membership\n") }
     end
-
-    describe 'parsing result' do
-      subject { repository.parse_search_result(search_result) }
-      let(:search_result) { results.first }
-
-      it { is_expected.to be_an OpenStruct }
-      it { expect(subject.filename).to eq('CHANGELOG') }
-      it { expect(subject.basename).to eq('CHANGELOG') }
-      it { expect(subject.ref).to eq('master') }
-      it { expect(subject.startline).to eq(186) }
-      it { expect(subject.data.lines[2]).to eq("  - Feature: Replace teams with group membership\n") }
-
-      context "when filename has extension" do
-        let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
-
-        it { expect(subject.filename).to eq('CONTRIBUTE.md') }
-        it { expect(subject.basename).to eq('CONTRIBUTE') }
-      end
-
-      context "when file under directory" do
-        let(:search_result) { "master:a/b/c.md:5:a b c\n" }
-
-        it { expect(subject.filename).to eq('a/b/c.md') }
-        it { expect(subject.basename).to eq('a/b/c') }
-      end
-    end
   end
 
   describe "#changelog" do
@@ -382,6 +488,24 @@ describe Repository, models: true do
     end
   end
 
+  describe '#find_branch' do
+    it 'loads a branch with a fresh repo' do
+      expect(Gitlab::Git::Repository).to receive(:new).twice.and_call_original
+
+      2.times do
+        expect(repository.find_branch('feature')).not_to be_nil
+      end
+    end
+
+    it 'loads a branch with a cached repo' do
+      expect(Gitlab::Git::Repository).to receive(:new).once.and_call_original
+
+      2.times do
+        expect(repository.find_branch('feature', fresh_repo: false)).not_to be_nil
+      end
+    end
+  end
+
   describe '#rm_branch' do
     let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
     let(:blank_sha) { '0000000000000000000000000000000000000000' }
@@ -423,43 +547,77 @@ describe Repository, models: true do
     end
   end
 
-  describe '#commit_with_hooks' do
+  describe '#update_branch_with_hooks' do
     let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
+    let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev
 
     context 'when pre hooks were successful' do
       before do
         expect_any_instance_of(GitHooksService).to receive(:execute).
-          with(user, repository.path_to_repo, old_rev, sample_commit.id, 'refs/heads/feature').
+          with(user, repository.path_to_repo, old_rev, new_rev, 'refs/heads/feature').
           and_yield.and_return(true)
       end
 
       it 'runs without errors' do
         expect do
-          repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+          repository.update_branch_with_hooks(user, 'feature') { new_rev }
         end.not_to raise_error
       end
 
       it 'ensures the autocrlf Git option is set to :input' do
         expect(repository).to receive(:update_autocrlf_option)
 
-        repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+        repository.update_branch_with_hooks(user, 'feature') { new_rev }
       end
 
       context "when the branch wasn't empty" do
         it 'updates the head' do
           expect(repository.find_branch('feature').target.id).to eq(old_rev)
-          repository.commit_with_hooks(user, 'feature') { sample_commit.id }
-          expect(repository.find_branch('feature').target.id).to eq(sample_commit.id)
+          repository.update_branch_with_hooks(user, 'feature') { new_rev }
+          expect(repository.find_branch('feature').target.id).to eq(new_rev)
         end
       end
     end
 
+    context 'when the update adds more than one commit' do
+      it 'runs without errors' do
+        old_rev = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
+
+        # old_rev is an ancestor of new_rev
+        expect(repository.rugged.merge_base(old_rev, new_rev)).to eq(old_rev)
+
+        # old_rev is not a direct ancestor (parent) of new_rev
+        expect(repository.rugged.lookup(new_rev).parent_ids).not_to include(old_rev)
+
+        branch = 'feature-ff-target'
+        repository.add_branch(user, branch, old_rev)
+
+        expect { repository.update_branch_with_hooks(user, branch) { new_rev } }.not_to raise_error
+      end
+    end
+
+    context 'when the update would remove commits from the target branch' do
+      it 'raises an exception' do
+        branch = 'master'
+        old_rev = repository.find_branch(branch).target.sha
+
+        # The 'master' branch is NOT an ancestor of new_rev.
+        expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev)
+
+        # Updating 'master' to new_rev would lose the commits on 'master' that
+        # are not contained in new_rev. This should not be allowed.
+        expect do
+          repository.update_branch_with_hooks(user, branch) { new_rev }
+        end.to raise_error(Repository::CommitError)
+      end
+    end
+
     context 'when pre hooks failed' do
       it 'gets an error' do
         allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
 
         expect do
-          repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+          repository.update_branch_with_hooks(user, 'feature') { new_rev }
         end.to raise_error(GitHooksService::PreReceiveError)
       end
     end
@@ -467,6 +625,7 @@ describe Repository, models: true do
     context 'when target branch is different from source branch' do
       before do
         allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
+        allow(repository).to receive(:update_ref!)
       end
 
       it 'expires branch cache' do
@@ -477,7 +636,7 @@ describe Repository, models: true do
         expect(repository).to     receive(:expire_has_visible_content_cache)
         expect(repository).to     receive(:expire_branch_count_cache)
 
-        repository.commit_with_hooks(user, 'new-feature') { sample_commit.id }
+        repository.update_branch_with_hooks(user, 'new-feature') { new_rev }
       end
     end
 
@@ -1250,4 +1409,18 @@ describe Repository, models: true do
       File.delete(path)
     end
   end
+
+  describe '#update_ref!' do
+    it 'can create a ref' do
+      repository.update_ref!('refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
+
+      expect(repository.find_branch('foobar')).not_to be_nil
+    end
+
+    it 'raises CommitError when the ref update fails' do
+      expect do
+        repository.update_ref!('refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
+      end.to raise_error(Repository::CommitError)
+    end
+  end
 end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 0621c6a..e6bc529 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -9,12 +9,14 @@ describe Snippet, models: true do
     it { is_expected.to include_module(Participable) }
     it { is_expected.to include_module(Referable) }
     it { is_expected.to include_module(Sortable) }
+    it { is_expected.to include_module(Awardable) }
   end
 
   describe 'associations' do
     it { is_expected.to belong_to(:author).class_name('User') }
     it { is_expected.to belong_to(:project) }
     it { is_expected.to have_many(:notes).dependent(:destroy) }
+    it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
   end
 
   describe 'validation' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 8eb0c50..a1770d9 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1006,8 +1006,7 @@ describe User, models: true do
     end
 
     it 'does not include projects for which issues are disabled' do
-      project = create(:project)
-      project.update_attributes(issues_enabled: false)
+      project = create(:project, issues_access_level: ProjectFeature::DISABLED)
 
       expect(user.projects_where_can_admin_issues.to_a).to be_empty
       expect(user.can?(:admin_issue, project)).to eq(false)
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
new file mode 100644
index 0000000..a7a0674
--- /dev/null
+++ b/spec/policies/project_policy_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe ProjectPolicy, models: true do
+  let(:project) { create(:empty_project, :public) }
+  let(:guest) { create(:user) }
+  let(:reporter) { create(:user) }
+  let(:dev) { create(:user) }
+  let(:master) { create(:user) }
+  let(:owner) { create(:user) }
+  let(:admin) { create(:admin) }
+
+  let(:users_ordered_by_permissions) do
+    [nil, guest, reporter, dev, master, owner, admin]
+  end
+
+  let(:users_permissions) do
+    users_ordered_by_permissions.map { |u| Ability.allowed(u, project).size }
+  end
+
+  before do
+    project.team << [guest, :guest]
+    project.team << [master, :master]
+    project.team << [dev, :developer]
+    project.team << [reporter, :reporter]
+
+    group = create(:group)
+    project.project_group_links.create(
+      group: group,
+      group_access: Gitlab::Access::MASTER)
+    group.add_owner(owner)
+  end
+
+  it 'returns increasing permissions for each level' do
+    expect(users_permissions).to eq(users_permissions.sort.uniq)
+  end
+
+  it 'does not include the read_issue permission when the issue author is not a member of the private project' do
+    project = create(:project, :private)
+    issue   = create(:issue, project: project)
+    user    = issue.author
+
+    expect(project.team.member?(issue.author)).to eq(false)
+
+    expect(BasePolicy.class_for(project).abilities(user, project).can_set).
+      not_to include(:read_issue)
+
+    expect(Ability.allowed?(user, :read_issue, project)).to be_falsy
+  end
+end
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index c65510f..e66faee 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
 describe API::Helpers, api: true do
   include API::Helpers
   include ApiHelpers
+  include SentryHelper
 
   let(:user) { create(:user) }
   let(:admin) { create(:admin) }
@@ -35,11 +36,36 @@ describe API::Helpers, api: true do
     params.delete(API::Helpers::SUDO_PARAM)
   end
 
+  def warden_authenticate_returns(value)
+    warden = double("warden", authenticate: value)
+    env['warden'] = warden
+  end
+
+  def doorkeeper_guard_returns(value)
+    allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value }
+  end
+
   def error!(message, status)
     raise Exception
   end
 
   describe ".current_user" do
+    subject { current_user }
+
+    describe "when authenticating via Warden" do
+      before { doorkeeper_guard_returns false }
+
+      context "fails" do
+        it { is_expected.to be_nil }
+      end
+
+      context "succeeds" do
+        before { warden_authenticate_returns user }
+
+        it { is_expected.to eq(user) }
+      end
+    end
+
     describe "when authenticating using a user's private token" do
       it "returns nil for an invalid token" do
         env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token'
@@ -234,4 +260,30 @@ describe API::Helpers, api: true do
       expect(to_boolean(nil)).to be_nil
     end
   end
+
+  describe '.handle_api_exception' do
+    before do
+      allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true)
+      allow_any_instance_of(self.class).to receive(:rack_response)
+    end
+
+    it 'does not report a MethodNotAllowed exception to Sentry' do
+      exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' })
+      allow(exception).to receive(:backtrace).and_return(caller)
+
+      expect(Raven).not_to receive(:capture_exception).with(exception)
+
+      handle_api_exception(exception)
+    end
+
+    it 'does report RuntimeError to Sentry' do
+      exception = RuntimeError.new('test error')
+      allow(exception).to receive(:backtrace).and_return(caller)
+
+      expect_any_instance_of(self.class).to receive(:sentry_context)
+      expect(Raven).to receive(:capture_exception).with(exception)
+
+      handle_api_exception(exception)
+    end
+  end
 end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 73c268c..5ad4fc4 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -3,8 +3,8 @@ require 'spec_helper'
 describe API::API, api: true  do
   include ApiHelpers
   let(:user)            { create(:user) }
-  let!(:project)        { create(:project) }
-  let(:issue)           { create(:issue, project: project, author: user) }
+  let!(:project)        { create(:empty_project) }
+  let(:issue)           { create(:issue, project: project) }
   let!(:award_emoji)    { create(:award_emoji, awardable: issue, user: user) }
   let!(:merge_request)  { create(:merge_request, source_project: project, target_project: project) }
   let!(:downvote)       { create(:award_emoji, :downvote, awardable: merge_request, user: user) }
@@ -39,6 +39,19 @@ describe API::API, api: true  do
       end
     end
 
+    context 'on a snippet' do
+      let(:snippet) { create(:project_snippet, :public, project: project) }
+      let!(:award)  { create(:award_emoji, awardable: snippet) }
+
+      it 'returns the awarded emoji' do
+        get api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first['name']).to eq(award.name)
+      end
+    end
+
     context 'when the user has no access' do
       it 'returns a status code 404' do
         user1 = create(:user)
@@ -91,6 +104,20 @@ describe API::API, api: true  do
       end
     end
 
+    context 'on a snippet' do
+      let(:snippet) { create(:project_snippet, :public, project: project) }
+      let!(:award)  { create(:award_emoji, awardable: snippet) }
+
+      it 'returns the awarded emoji' do
+        get api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response['name']).to eq(award.name)
+        expect(json_response['awardable_id']).to eq(snippet.id)
+        expect(json_response['awardable_type']).to eq("Snippet")
+      end
+    end
+
     context 'when the user has no access' do
       it 'returns a status code 404' do
         user1 = create(:user)
@@ -115,6 +142,8 @@ describe API::API, api: true  do
   end
 
   describe "POST /projects/:id/awardable/:awardable_id/award_emoji" do
+    let(:issue2)  { create(:issue, project: project, author: user) }
+
     context "on an issue" do
       it "creates a new award emoji" do
         post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'blowfish'
@@ -136,6 +165,12 @@ describe API::API, api: true  do
         expect(response).to have_http_status(401)
       end
 
+      it "returns a 404 error if the user authored issue" do
+        post api("/projects/#{project.id}/issues/#{issue2.id}/award_emoji", user), name: 'thumbsup'
+
+        expect(response).to have_http_status(404)
+      end
+
       it "normalizes +1 as thumbsup award" do
         post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1'
 
@@ -152,9 +187,23 @@ describe API::API, api: true  do
         end
       end
     end
+
+    context 'on a snippet' do
+      it 'creates a new award emoji' do
+        snippet = create(:project_snippet, :public, project: project)
+
+        post api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), name: 'blowfish'
+
+        expect(response).to have_http_status(201)
+        expect(json_response['name']).to eq('blowfish')
+        expect(json_response['user']['username']).to eq(user.username)
+      end
+    end
   end
 
   describe "POST /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji" do
+    let(:note2)  { create(:note, project: project, noteable: issue, author: user) }
+
     it 'creates a new award emoji' do
       expect do
         post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
@@ -164,6 +213,12 @@ describe API::API, api: true  do
       expect(json_response['user']['username']).to eq(user.username)
     end
 
+    it "it returns 404 error when user authored note" do
+      post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup'
+
+      expect(response).to have_http_status(404)
+    end
+
     it "normalizes +1 as thumbsup award" do
       post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1'
 
@@ -213,6 +268,19 @@ describe API::API, api: true  do
         expect(response).to have_http_status(404)
       end
     end
+
+    context 'when the awardable is a Snippet' do
+      let(:snippet) { create(:project_snippet, :public, project: project) }
+      let!(:award)  { create(:award_emoji, awardable: snippet, user: user) }
+
+      it 'deletes the award' do
+        expect do
+          delete api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user)
+        end.to change { snippet.award_emoji.count }.from(1).to(0)
+
+        expect(response).to have_http_status(200)
+      end
+    end
   end
 
   describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_emoji_id' do
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
new file mode 100644
index 0000000..7c9078b
--- /dev/null
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -0,0 +1,180 @@
+require 'spec_helper'
+
+describe API::BroadcastMessages, api: true do
+  include ApiHelpers
+
+  let(:user)  { create(:user) }
+  let(:admin) { create(:admin) }
+
+  describe 'GET /broadcast_messages' do
+    it 'returns a 401 for anonymous users' do
+      get api('/broadcast_messages')
+
+      expect(response).to have_http_status(401)
+    end
+
+    it 'returns a 403 for users' do
+      get api('/broadcast_messages', user)
+
+      expect(response).to have_http_status(403)
+    end
+
+    it 'returns an Array of BroadcastMessages for admins' do
+      create(:broadcast_message)
+
+      get api('/broadcast_messages', admin)
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_kind_of(Array)
+      expect(json_response.first.keys)
+        .to match_array(%w(id message starts_at ends_at color font active))
+    end
+  end
+
+  describe 'GET /broadcast_messages/:id' do
+    let!(:message) { create(:broadcast_message) }
+
+    it 'returns a 401 for anonymous users' do
+      get api("/broadcast_messages/#{message.id}")
+
+      expect(response).to have_http_status(401)
+    end
+
+    it 'returns a 403 for users' do
+      get api("/broadcast_messages/#{message.id}", user)
+
+      expect(response).to have_http_status(403)
+    end
+
+    it 'returns the specified message for admins' do
+      get api("/broadcast_messages/#{message.id}", admin)
+
+      expect(response).to have_http_status(200)
+      expect(json_response['id']).to eq message.id
+      expect(json_response.keys)
+        .to match_array(%w(id message starts_at ends_at color font active))
+    end
+  end
+
+  describe 'POST /broadcast_messages' do
+    it 'returns a 401 for anonymous users' do
+      post api('/broadcast_messages'), attributes_for(:broadcast_message)
+
+      expect(response).to have_http_status(401)
+    end
+
+    it 'returns a 403 for users' do
+      post api('/broadcast_messages', user), attributes_for(:broadcast_message)
+
+      expect(response).to have_http_status(403)
+    end
+
+    context 'as an admin' do
+      it 'requires the `message` parameter' do
+        attrs = attributes_for(:broadcast_message)
+        attrs.delete(:message)
+
+        post api('/broadcast_messages', admin), attrs
+
+        expect(response).to have_http_status(400)
+        expect(json_response['error']).to eq 'message is missing'
+      end
+
+      it 'defines sane default start and end times' do
+        time = Time.zone.parse('2016-07-02 10:11:12')
+        travel_to(time) do
+          post api('/broadcast_messages', admin), message: 'Test message'
+
+          expect(response).to have_http_status(201)
+          expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z'
+          expect(json_response['ends_at']).to   eq '2016-07-02T11:11:12.000Z'
+        end
+      end
+
+      it 'accepts a custom background and foreground color' do
+        attrs = attributes_for(:broadcast_message, color: '#000000', font: '#cecece')
+
+        post api('/broadcast_messages', admin), attrs
+
+        expect(response).to have_http_status(201)
+        expect(json_response['color']).to eq attrs[:color]
+        expect(json_response['font']).to eq attrs[:font]
+      end
+    end
+  end
+
+  describe 'PUT /broadcast_messages/:id' do
+    let!(:message) { create(:broadcast_message) }
+
+    it 'returns a 401 for anonymous users' do
+      put api("/broadcast_messages/#{message.id}"),
+        attributes_for(:broadcast_message)
+
+      expect(response).to have_http_status(401)
+    end
+
+    it 'returns a 403 for users' do
+      put api("/broadcast_messages/#{message.id}", user),
+        attributes_for(:broadcast_message)
+
+      expect(response).to have_http_status(403)
+    end
+
+    context 'as an admin' do
+      it 'accepts new background and foreground colors' do
+        attrs = { color: '#000000', font: '#cecece' }
+
+        put api("/broadcast_messages/#{message.id}", admin), attrs
+
+        expect(response).to have_http_status(200)
+        expect(json_response['color']).to eq attrs[:color]
+        expect(json_response['font']).to eq attrs[:font]
+      end
+
+      it 'accepts new start and end times' do
+        time = Time.zone.parse('2016-07-02 10:11:12')
+        travel_to(time) do
+          attrs = { starts_at: Time.zone.now, ends_at: 3.hours.from_now }
+
+          put api("/broadcast_messages/#{message.id}", admin), attrs
+
+          expect(response).to have_http_status(200)
+          expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z'
+          expect(json_response['ends_at']).to   eq '2016-07-02T13:11:12.000Z'
+        end
+      end
+
+      it 'accepts a new message' do
+        attrs = { message: 'new message' }
+
+        put api("/broadcast_messages/#{message.id}", admin), attrs
+
+        expect(response).to have_http_status(200)
+        expect { message.reload }.to change { message.message }.to('new message')
+      end
+    end
+  end
+
+  describe 'DELETE /broadcast_messages/:id' do
+    let!(:message) { create(:broadcast_message) }
+
+    it 'returns a 401 for anonymous users' do
+      delete api("/broadcast_messages/#{message.id}"),
+        attributes_for(:broadcast_message)
+
+      expect(response).to have_http_status(401)
+    end
+
+    it 'returns a 403 for users' do
+      delete api("/broadcast_messages/#{message.id}", user),
+        attributes_for(:broadcast_message)
+
+      expect(response).to have_http_status(403)
+    end
+
+    it 'deletes the broadcast message for admins' do
+      expect { delete api("/broadcast_messages/#{message.id}", admin) }
+        .to change { BroadcastMessage.count }.by(-1)
+    end
+  end
+end
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 9a17a70..ee0b61e 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -15,7 +15,9 @@ describe API::API, api: true do
   describe 'GET /projects/:id/builds ' do
     let(:query) { '' }
 
-    before { get api("/projects/#{project.id}/builds?#{query}", api_user) }
+    before do
+      get api("/projects/#{project.id}/builds?#{query}", api_user)
+    end
 
     context 'authorized user' do
       it 'returns project builds' do
@@ -122,7 +124,9 @@ describe API::API, api: true do
   end
 
   describe 'GET /projects/:id/builds/:build_id' do
-    before { get api("/projects/#{project.id}/builds/#{build.id}", api_user) }
+    before do
+      get api("/projects/#{project.id}/builds/#{build.id}", api_user)
+    end
 
     context 'authorized user' do
       it 'returns specific build data' do
@@ -141,7 +145,9 @@ describe API::API, api: true do
   end
 
   describe 'GET /projects/:id/builds/:build_id/artifacts' do
-    before { get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) }
+    before do
+      get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
+    end
 
     context 'build with artifacts' do
       let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
@@ -292,7 +298,9 @@ describe API::API, api: true do
   end
 
   describe 'POST /projects/:id/builds/:build_id/cancel' do
-    before { post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) }
+    before do
+      post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user)
+    end
 
     context 'authorized user' do
       context 'user with :update_build persmission' do
@@ -323,7 +331,9 @@ describe API::API, api: true do
   describe 'POST /projects/:id/builds/:build_id/retry' do
     let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
 
-    before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) }
+    before do
+      post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user)
+    end
 
     context 'authorized user' do
       context 'user with :update_build permission' do
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 2d6093f..7aa7e85 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -117,17 +117,36 @@ describe API::CommitStatuses, api: true do
     let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" }
 
     context 'developer user' do
-      context 'only required parameters' do
-        before { post api(post_url, developer), state: 'success' }
+      %w[pending running success failed canceled].each do |status|
+        context "for #{status}" do
+          context 'uses only required parameters' do
+            it 'creates commit status' do
+              post api(post_url, developer), state: status
+
+              expect(response).to have_http_status(201)
+              expect(json_response['sha']).to eq(commit.id)
+              expect(json_response['status']).to eq(status)
+              expect(json_response['name']).to eq('default')
+              expect(json_response['ref']).not_to be_empty
+              expect(json_response['target_url']).to be_nil
+              expect(json_response['description']).to be_nil
+            end
+          end
+        end
+      end
 
-        it 'creates commit status' do
-          expect(response).to have_http_status(201)
-          expect(json_response['sha']).to eq(commit.id)
-          expect(json_response['status']).to eq('success')
-          expect(json_response['name']).to eq('default')
-          expect(json_response['ref']).to be_nil
-          expect(json_response['target_url']).to be_nil
-          expect(json_response['description']).to be_nil
+      context 'transitions status from pending' do
+        before do
+          post api(post_url, developer), state: 'pending'
+        end
+
+        %w[running success failed canceled].each do |status|
+          it "to #{status}" do
+            expect { post api(post_url, developer), state: status }.not_to change { CommitStatus.count }
+
+            expect(response).to have_http_status(201)
+            expect(json_response['status']).to eq(status)
+          end
         end
       end
 
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 7ca75d7..10f772c 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -95,7 +95,7 @@ describe API::API, api: true  do
       end
 
       it "returns status for CI" do
-        pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master')
+        pipeline = project.ensure_pipeline('master', project.repository.commit.sha)
         pipeline.update(status: 'success')
 
         get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
@@ -105,12 +105,12 @@ describe API::API, api: true  do
       end
 
       it "returns status for CI when pipeline is created" do
-        project.ensure_pipeline(project.repository.commit.sha, 'master')
+        project.ensure_pipeline('master', project.repository.commit.sha)
 
         get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
 
         expect(response).to have_http_status(200)
-        expect(json_response['status']).to be_nil
+        expect(json_response['status']).to eq("created")
       end
     end
 
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 2d1213d..050d0dd 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -5,6 +5,21 @@ describe API::API, api: true  do
   let(:user) { create(:user) }
   let!(:project) { create(:project, namespace: user.namespace ) }
   let(:file_path) { 'files/ruby/popen.rb' }
+  let(:author_email) { FFaker::Internet.email }
+
+  # I have to remove periods from the end of the name
+  # This happened when the user's name had a suffix (i.e. "Sr.")
+  # This seems to be what git does under the hood. For example, this commit:
+  #
+  # $ git commit --author='Foo Sr. <foo at example.com>' -m 'Where's my trailing period?'
+  #
+  # results in this:
+  #
+  # $ git show --pretty
+  # ...
+  # Author: Foo Sr <foo at example.com>
+  # ...
+  let(:author_name) { FFaker::Name.name.chomp("\.") }
 
   before { project.team << [user, :developer] }
 
@@ -16,6 +31,7 @@ describe API::API, api: true  do
       }
 
       get api("/projects/#{project.id}/repository/files", user), params
+
       expect(response).to have_http_status(200)
       expect(json_response['file_path']).to eq(file_path)
       expect(json_response['file_name']).to eq('popen.rb')
@@ -25,6 +41,7 @@ describe API::API, api: true  do
 
     it "returns a 400 bad request if no params given" do
       get api("/projects/#{project.id}/repository/files", user)
+
       expect(response).to have_http_status(400)
     end
 
@@ -35,6 +52,7 @@ describe API::API, api: true  do
       }
 
       get api("/projects/#{project.id}/repository/files", user), params
+
       expect(response).to have_http_status(404)
     end
   end
@@ -51,12 +69,17 @@ describe API::API, api: true  do
 
     it "creates a new file in project repo" do
       post api("/projects/#{project.id}/repository/files", user), valid_params
+
       expect(response).to have_http_status(201)
       expect(json_response['file_path']).to eq('newfile.rb')
+      last_commit = project.repository.commit.raw
+      expect(last_commit.author_email).to eq(user.email)
+      expect(last_commit.author_name).to eq(user.name)
     end
 
     it "returns a 400 bad request if no params given" do
       post api("/projects/#{project.id}/repository/files", user)
+
       expect(response).to have_http_status(400)
     end
 
@@ -65,8 +88,22 @@ describe API::API, api: true  do
         and_return(false)
 
       post api("/projects/#{project.id}/repository/files", user), valid_params
+
       expect(response).to have_http_status(400)
     end
+
+    context "when specifying an author" do
+      it "creates a new file with the specified author" do
+        valid_params.merge!(author_email: author_email, author_name: author_name)
+
+        post api("/projects/#{project.id}/repository/files", user), valid_params
+
+        expect(response).to have_http_status(201)
+        last_commit = project.repository.commit.raw
+        expect(last_commit.author_email).to eq(author_email)
+        expect(last_commit.author_name).to eq(author_name)
+      end
+    end
   end
 
   describe "PUT /projects/:id/repository/files" do
@@ -81,14 +118,32 @@ describe API::API, api: true  do
 
     it "updates existing file in project repo" do
       put api("/projects/#{project.id}/repository/files", user), valid_params
+
       expect(response).to have_http_status(200)
       expect(json_response['file_path']).to eq(file_path)
+      last_commit = project.repository.commit.raw
+      expect(last_commit.author_email).to eq(user.email)
+      expect(last_commit.author_name).to eq(user.name)
     end
 
     it "returns a 400 bad request if no params given" do
       put api("/projects/#{project.id}/repository/files", user)
+
       expect(response).to have_http_status(400)
     end
+
+    context "when specifying an author" do
+      it "updates a file with the specified author" do
+        valid_params.merge!(author_email: author_email, author_name: author_name, content: "New content")
+
+        put api("/projects/#{project.id}/repository/files", user), valid_params
+
+        expect(response).to have_http_status(200)
+        last_commit = project.repository.commit.raw
+        expect(last_commit.author_email).to eq(author_email)
+        expect(last_commit.author_name).to eq(author_name)
+      end
+    end
   end
 
   describe "DELETE /projects/:id/repository/files" do
@@ -102,12 +157,17 @@ describe API::API, api: true  do
 
     it "deletes existing file in project repo" do
       delete api("/projects/#{project.id}/repository/files", user), valid_params
+
       expect(response).to have_http_status(200)
       expect(json_response['file_path']).to eq(file_path)
+      last_commit = project.repository.commit.raw
+      expect(last_commit.author_email).to eq(user.email)
+      expect(last_commit.author_name).to eq(user.name)
     end
 
     it "returns a 400 bad request if no params given" do
       delete api("/projects/#{project.id}/repository/files", user)
+
       expect(response).to have_http_status(400)
     end
 
@@ -115,8 +175,22 @@ describe API::API, api: true  do
       allow_any_instance_of(Repository).to receive(:remove_file).and_return(false)
 
       delete api("/projects/#{project.id}/repository/files", user), valid_params
+
       expect(response).to have_http_status(400)
     end
+
+    context "when specifying an author" do
+      it "removes a file with the specified author" do
+        valid_params.merge!(author_email: author_email, author_name: author_name)
+
+        delete api("/projects/#{project.id}/repository/files", user), valid_params
+
+        expect(response).to have_http_status(200)
+        last_commit = project.repository.commit.raw
+        expect(last_commit.author_email).to eq(author_email)
+        expect(last_commit.author_name).to eq(author_name)
+      end
+    end
   end
 
   describe "POST /projects/:id/repository/files with binary file" do
@@ -143,6 +217,7 @@ describe API::API, api: true  do
 
     it "remains unchanged" do
       get api("/projects/#{project.id}/repository/files", user), get_params
+
       expect(response).to have_http_status(200)
       expect(json_response['file_path']).to eq(file_path)
       expect(json_response['file_name']).to eq(file_path)
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index f802fcd..34f84f7 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -6,6 +6,12 @@ describe API::API, api: true  do
   let(:user2) { create(:user) }
   let(:user3) { create(:user) }
   let(:admin) { create(:admin) }
+  let(:group) { create(:group) }
+  let(:group2) do
+    group = create(:group, name: 'group2_name')
+    group.add_owner(user2)
+    group
+  end
 
   let(:project) do
     create(:project, creator_id: user.id, namespace: user.namespace)
@@ -22,6 +28,7 @@ describe API::API, api: true  do
     context 'when authenticated' do
       it 'forks if user has sufficient access to project' do
         post api("/projects/fork/#{project.id}", user2)
+
         expect(response).to have_http_status(201)
         expect(json_response['name']).to eq(project.name)
         expect(json_response['path']).to eq(project.path)
@@ -32,6 +39,7 @@ describe API::API, api: true  do
 
       it 'forks if user is admin' do
         post api("/projects/fork/#{project.id}", admin)
+
         expect(response).to have_http_status(201)
         expect(json_response['name']).to eq(project.name)
         expect(json_response['path']).to eq(project.path)
@@ -42,12 +50,14 @@ describe API::API, api: true  do
 
       it 'fails on missing project access for the project to fork' do
         post api("/projects/fork/#{project.id}", user3)
+
         expect(response).to have_http_status(404)
         expect(json_response['message']).to eq('404 Project Not Found')
       end
 
       it 'fails if forked project exists in the user namespace' do
         post api("/projects/fork/#{project.id}", user)
+
         expect(response).to have_http_status(409)
         expect(json_response['message']['name']).to eq(['has already been taken'])
         expect(json_response['message']['path']).to eq(['has already been taken'])
@@ -55,14 +65,70 @@ describe API::API, api: true  do
 
       it 'fails if project to fork from does not exist' do
         post api('/projects/fork/424242', user)
+
         expect(response).to have_http_status(404)
         expect(json_response['message']).to eq('404 Project Not Found')
       end
+
+      it 'forks with explicit own user namespace id' do
+        post api("/projects/fork/#{project.id}", user2), namespace: user2.namespace.id
+
+        expect(response).to have_http_status(201)
+        expect(json_response['owner']['id']).to eq(user2.id)
+      end
+
+      it 'forks with explicit own user name as namespace' do
+        post api("/projects/fork/#{project.id}", user2), namespace: user2.username
+
+        expect(response).to have_http_status(201)
+        expect(json_response['owner']['id']).to eq(user2.id)
+      end
+
+      it 'forks to another user when admin' do
+        post api("/projects/fork/#{project.id}", admin), namespace: user2.username
+
+        expect(response).to have_http_status(201)
+        expect(json_response['owner']['id']).to eq(user2.id)
+      end
+
+      it 'fails if trying to fork to another user when not admin' do
+        post api("/projects/fork/#{project.id}", user2), namespace: admin.namespace.id
+
+        expect(response).to have_http_status(404)
+      end
+
+      it 'fails if trying to fork to non-existent namespace' do
+        post api("/projects/fork/#{project.id}", user2), namespace: 42424242
+
+        expect(response).to have_http_status(404)
+        expect(json_response['message']).to eq('404 Target Namespace Not Found')
+      end
+
+      it 'forks to owned group' do
+        post api("/projects/fork/#{project.id}", user2), namespace: group2.name
+
+        expect(response).to have_http_status(201)
+        expect(json_response['namespace']['name']).to eq(group2.name)
+      end
+
+      it 'fails to fork to not owned group' do
+        post api("/projects/fork/#{project.id}", user2), namespace: group.name
+
+        expect(response).to have_http_status(404)
+      end
+
+      it 'forks to not owned group when admin' do
+        post api("/projects/fork/#{project.id}", admin), namespace: group.name
+
+        expect(response).to have_http_status(201)
+        expect(json_response['namespace']['name']).to eq(group.name)
+      end
     end
 
     context 'when unauthenticated' do
       it 'returns authentication error' do
         post api("/projects/fork/#{project.id}")
+
         expect(response).to have_http_status(401)
         expect(json_response['message']).to eq('401 Unauthorized')
       end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 4860b23..1f68ef1 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -120,10 +120,11 @@ describe API::API, api: true  do
 
     context 'when authenticated as the group owner' do
       it 'updates the group' do
-        put api("/groups/#{group1.id}", user1), name: new_group_name
+        put api("/groups/#{group1.id}", user1), name: new_group_name, request_access_enabled: true
 
         expect(response).to have_http_status(200)
         expect(json_response['name']).to eq(new_group_name)
+        expect(json_response['request_access_enabled']).to eq(true)
       end
 
       it 'returns 404 for a non existing group' do
@@ -238,8 +239,14 @@ describe API::API, api: true  do
 
     context "when authenticated as user with group permissions" do
       it "creates group" do
-        post api("/groups", user3), attributes_for(:group)
+        group = attributes_for(:group, { request_access_enabled: false })
+
+        post api("/groups", user3), group
         expect(response).to have_http_status(201)
+
+        expect(json_response["name"]).to eq(group[:name])
+        expect(json_response["path"]).to eq(group[:path])
+        expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled])
       end
 
       it "does not create group, duplicate" do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index be52f88..46e8e6f 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -38,6 +38,105 @@ describe API::API, api: true  do
     end
   end
 
+  describe 'GET /internal/two_factor_recovery_codes' do
+    it 'returns an error message when the key does not exist' do
+      post api('/internal/two_factor_recovery_codes'),
+           secret_token: secret_token,
+           key_id: 12345
+
+      expect(json_response['success']).to be_falsey
+      expect(json_response['message']).to eq('Could not find the given key')
+    end
+
+    it 'returns an error message when the key is a deploy key' do
+      deploy_key = create(:deploy_key)
+
+      post api('/internal/two_factor_recovery_codes'),
+           secret_token: secret_token,
+           key_id: deploy_key.id
+
+      expect(json_response['success']).to be_falsey
+      expect(json_response['message']).to eq('Deploy keys cannot be used to retrieve recovery codes')
+    end
+
+    it 'returns an error message when the user does not exist' do
+      key_without_user = create(:key, user: nil)
+
+      post api('/internal/two_factor_recovery_codes'),
+           secret_token: secret_token,
+           key_id: key_without_user.id
+
+      expect(json_response['success']).to be_falsey
+      expect(json_response['message']).to eq('Could not find a user for the given key')
+      expect(json_response['recovery_codes']).to be_nil
+    end
+
+    context 'when two-factor is enabled' do
+      it 'returns new recovery codes when the user exists' do
+        allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(true)
+        allow_any_instance_of(User)
+          .to receive(:generate_otp_backup_codes!).and_return(%w(119135e5a3ebce8e 34bd7b74adbc8861))
+
+        post api('/internal/two_factor_recovery_codes'),
+             secret_token: secret_token,
+             key_id: key.id
+
+        expect(json_response['success']).to be_truthy
+        expect(json_response['recovery_codes']).to match_array(%w(119135e5a3ebce8e 34bd7b74adbc8861))
+      end
+    end
+
+    context 'when two-factor is not enabled' do
+      it 'returns an error message' do
+        allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(false)
+
+        post api('/internal/two_factor_recovery_codes'),
+             secret_token: secret_token,
+             key_id: key.id
+
+        expect(json_response['success']).to be_falsey
+        expect(json_response['recovery_codes']).to be_nil
+      end
+    end
+  end
+
+  describe "POST /internal/lfs_authenticate" do
+    before do
+      project.team << [user, :developer]
+    end
+
+    context 'user key' do
+      it 'returns the correct information about the key' do
+        lfs_auth(key.id, project)
+
+        expect(response).to have_http_status(200)
+        expect(json_response['username']).to eq(user.username)
+        expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value)
+
+        expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
+      end
+
+      it 'returns a 404 when the wrong key is provided' do
+        lfs_auth(nil, project)
+
+        expect(response).to have_http_status(404)
+      end
+    end
+
+    context 'deploy key' do
+      let(:key) { create(:deploy_key) }
+
+      it 'returns the correct information about the key' do
+        lfs_auth(key.id, project)
+
+        expect(response).to have_http_status(200)
+        expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}")
+        expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value)
+        expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
+      end
+    end
+  end
+
   describe "GET /internal/discover" do
     it do
       get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
@@ -327,4 +426,13 @@ describe API::API, api: true  do
       protocol: 'ssh'
     )
   end
+
+  def lfs_auth(key_id, project)
+    post(
+      api("/internal/lfs_authenticate"),
+      key_id: key_id,
+      secret_token: secret_token,
+      project: project.path_with_namespace
+    )
+  end
 end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index a40e1a9..f840778 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
 
 describe API::API, api: true  do
   include ApiHelpers
+
   let(:user)        { create(:user) }
   let(:user2)       { create(:user) }
   let(:non_member)  { create(:user) }
@@ -16,21 +17,27 @@ describe API::API, api: true  do
            assignee: user,
            project: project,
            state: :closed,
-           milestone: milestone
+           milestone: milestone,
+           created_at: generate(:issue_created_at),
+           updated_at: 3.hours.ago
   end
   let!(:confidential_issue) do
     create :issue,
            :confidential,
            project: project,
            author: author,
-           assignee: assignee
+           assignee: assignee,
+           created_at: generate(:issue_created_at),
+           updated_at: 2.hours.ago
   end
   let!(:issue) do
     create :issue,
            author: user,
            assignee: user,
            project: project,
-           milestone: milestone
+           milestone: milestone,
+           created_at: generate(:issue_created_at),
+           updated_at: 1.hour.ago
   end
   let!(:label) do
     create(:label, title: 'label', color: '#FFAABB', project: project)
@@ -61,6 +68,7 @@ describe API::API, api: true  do
         expect(response).to have_http_status(200)
         expect(json_response).to be_an Array
         expect(json_response.first['title']).to eq(issue.title)
+        expect(json_response.last).to have_key('web_url')
       end
 
       it "adds pagination headers and keep query params" do
@@ -133,6 +141,42 @@ describe API::API, api: true  do
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(0)
       end
+
+      it 'sorts by created_at descending by default' do
+        get api('/issues', user)
+        response_dates = json_response.map { |issue| issue['created_at'] }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(response_dates).to eq(response_dates.sort.reverse)
+      end
+
+      it 'sorts ascending when requested' do
+        get api('/issues?sort=asc', user)
+        response_dates = json_response.map { |issue| issue['created_at'] }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(response_dates).to eq(response_dates.sort)
+      end
+
+      it 'sorts by updated_at descending when requested' do
+        get api('/issues?order_by=updated_at', user)
+        response_dates = json_response.map { |issue| issue['updated_at'] }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(response_dates).to eq(response_dates.sort.reverse)
+      end
+
+      it 'sorts by updated_at ascending when requested' do
+        get api('/issues?order_by=updated_at&sort=asc', user)
+        response_dates = json_response.map { |issue| issue['updated_at'] }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(response_dates).to eq(response_dates.sort)
+      end
     end
   end
 
@@ -145,21 +189,24 @@ describe API::API, api: true  do
              assignee: user,
              project: group_project,
              state: :closed,
-             milestone: group_milestone
+             milestone: group_milestone,
+             updated_at: 3.hours.ago
     end
     let!(:group_confidential_issue) do
       create :issue,
              :confidential,
              project: group_project,
              author: author,
-             assignee: assignee
+             assignee: assignee,
+             updated_at: 2.hours.ago
     end
     let!(:group_issue) do
       create :issue,
              author: user,
              assignee: user,
              project: group_project,
-             milestone: group_milestone
+             milestone: group_milestone,
+             updated_at: 1.hour.ago
     end
     let!(:group_label) do
       create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
@@ -276,6 +323,42 @@ describe API::API, api: true  do
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(group_closed_issue.id)
     end
+
+    it 'sorts by created_at descending by default' do
+      get api(base_url, user)
+      response_dates = json_response.map { |issue| issue['created_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort.reverse)
+    end
+
+    it 'sorts ascending when requested' do
+      get api("#{base_url}?sort=asc", user)
+      response_dates = json_response.map { |issue| issue['created_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort)
+    end
+
+    it 'sorts by updated_at descending when requested' do
+      get api("#{base_url}?order_by=updated_at", user)
+      response_dates = json_response.map { |issue| issue['updated_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort.reverse)
+    end
+
+    it 'sorts by updated_at ascending when requested' do
+      get api("#{base_url}?order_by=updated_at&sort=asc", user)
+      response_dates = json_response.map { |issue| issue['updated_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort)
+    end
   end
 
   describe "GET /projects/:id/issues" do
@@ -384,6 +467,42 @@ describe API::API, api: true  do
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(closed_issue.id)
     end
+
+    it 'sorts by created_at descending by default' do
+      get api("#{base_url}/issues", user)
+      response_dates = json_response.map { |issue| issue['created_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort.reverse)
+    end
+
+    it 'sorts ascending when requested' do
+      get api("#{base_url}/issues?sort=asc", user)
+      response_dates = json_response.map { |issue| issue['created_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort)
+    end
+
+    it 'sorts by updated_at descending when requested' do
+      get api("#{base_url}/issues?order_by=updated_at", user)
+      response_dates = json_response.map { |issue| issue['updated_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort.reverse)
+    end
+
+    it 'sorts by updated_at ascending when requested' do
+      get api("#{base_url}/issues?order_by=updated_at&sort=asc", user)
+      response_dates = json_response.map { |issue| issue['updated_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort)
+    end
   end
 
   describe "GET /projects/:id/issues/:issue_id" do
@@ -403,6 +522,7 @@ describe API::API, api: true  do
       expect(json_response['milestone']).to be_a Hash
       expect(json_response['assignee']).to be_a Hash
       expect(json_response['author']).to be_a Hash
+      expect(json_response['confidential']).to be_falsy
     end
 
     it "returns a project issue by id" do
@@ -468,13 +588,63 @@ describe API::API, api: true  do
   end
 
   describe "POST /projects/:id/issues" do
-    it "creates a new project issue" do
+    it 'creates a new project issue' do
       post api("/projects/#{project.id}/issues", user),
         title: 'new issue', labels: 'label, label2'
+
       expect(response).to have_http_status(201)
       expect(json_response['title']).to eq('new issue')
       expect(json_response['description']).to be_nil
       expect(json_response['labels']).to eq(['label', 'label2'])
+      expect(json_response['confidential']).to be_falsy
+    end
+
+    it 'creates a new confidential project issue' do
+      post api("/projects/#{project.id}/issues", user),
+        title: 'new issue', confidential: true
+
+      expect(response).to have_http_status(201)
+      expect(json_response['title']).to eq('new issue')
+      expect(json_response['confidential']).to be_truthy
+    end
+
+    it 'creates a new confidential project issue with a different param' do
+      post api("/projects/#{project.id}/issues", user),
+        title: 'new issue', confidential: 'y'
+
+      expect(response).to have_http_status(201)
+      expect(json_response['title']).to eq('new issue')
+      expect(json_response['confidential']).to be_truthy
+    end
+
+    it 'creates a public issue when confidential param is false' do
+      post api("/projects/#{project.id}/issues", user),
+        title: 'new issue', confidential: false
+
+      expect(response).to have_http_status(201)
+      expect(json_response['title']).to eq('new issue')
+      expect(json_response['confidential']).to be_falsy
+    end
+
+    it 'creates a public issue when confidential param is invalid' do
+      post api("/projects/#{project.id}/issues", user),
+        title: 'new issue', confidential: 'foo'
+
+      expect(response).to have_http_status(201)
+      expect(json_response['title']).to eq('new issue')
+      expect(json_response['confidential']).to be_falsy
+    end
+
+    it "sends notifications for subscribers of newly added labels" do
+      label = project.labels.first
+      label.toggle_subscription(user2)
+
+      perform_enqueued_jobs do
+        post api("/projects/#{project.id}/issues", user),
+          title: 'new issue', labels: label.title
+      end
+
+      should_email(user2)
     end
 
     it "returns a 400 bad request if title not given" do
@@ -618,6 +788,30 @@ describe API::API, api: true  do
         expect(response).to have_http_status(200)
         expect(json_response['title']).to eq('updated title')
       end
+
+      it 'sets an issue to confidential' do
+        put api("/projects/#{project.id}/issues/#{issue.id}", user),
+          confidential: true
+
+        expect(response).to have_http_status(200)
+        expect(json_response['confidential']).to be_truthy
+      end
+
+      it 'makes a confidential issue public' do
+        put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
+          confidential: false
+
+        expect(response).to have_http_status(200)
+        expect(json_response['confidential']).to be_falsy
+      end
+
+      it 'does not update a confidential issue with wrong confidential flag' do
+        put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
+          confidential: 'foo'
+
+        expect(response).to have_http_status(200)
+        expect(json_response['confidential']).to be_truthy
+      end
     end
   end
 
@@ -632,6 +826,18 @@ describe API::API, api: true  do
       expect(json_response['labels']).to eq([label.title])
     end
 
+    it "sends notifications for subscribers of newly added labels when issue is updated" do
+      label = create(:label, title: 'foo', color: '#FFAABB', project: project)
+      label.toggle_subscription(user2)
+
+      perform_enqueued_jobs do
+        put api("/projects/#{project.id}/issues/#{issue.id}", user),
+          title: 'updated title', labels: label.title
+      end
+
+      should_email(user2)
+    end
+
     it 'removes all labels' do
       put api("/projects/#{project.id}/issues/#{issue.id}", user),
           labels: ''
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
new file mode 100644
index 0000000..391fc13
--- /dev/null
+++ b/spec/requests/api/lint_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe API::Lint, api: true do
+  include ApiHelpers
+
+  describe 'POST /ci/lint' do
+    context 'with valid .gitlab-ci.yaml content' do
+      let(:yaml_content) do
+        File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+      end
+
+      it 'passes validation' do
+        post api('/ci/lint'), { content: yaml_content }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Hash
+        expect(json_response['status']).to eq('valid')
+        expect(json_response['errors']).to eq([])
+      end
+    end
+
+    context 'with an invalid .gitlab_ci.yml' do
+      it 'responds with errors about invalid syntax' do
+        post api('/ci/lint'), { content: 'invalid content' }
+
+        expect(response).to have_http_status(200)
+        expect(json_response['status']).to eq('invalid')
+        expect(json_response['errors']).to eq(['Invalid configuration format'])
+      end
+
+      it "responds with errors about invalid configuration" do
+        post api('/ci/lint'), { content: '{ image: "ruby:2.1",  services: ["postgres"] }' }
+
+        expect(response).to have_http_status(200)
+        expect(json_response['status']).to eq('invalid')
+        expect(json_response['errors']).to eq(['jobs config should contain at least one visible job'])
+      end
+    end
+
+    context 'without the content parameter' do
+      it 'responds with validation error about missing content' do
+        post api('/ci/lint')
+
+        expect(response).to have_http_status(400)
+        expect(json_response['error']).to eq('content is missing')
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 1e365bf..92032f0 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -30,20 +30,29 @@ describe API::Members, api: true  do
         let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members", stranger) }
       end
 
-      context 'when authenticated as a non-member' do
-        %i[access_requester stranger].each do |type|
-          context "as a #{type}" do
-            it 'returns 200' do
-              user = public_send(type)
-              get api("/#{source_type.pluralize}/#{source.id}/members", user)
+      %i[master developer access_requester stranger].each do |type|
+        context "when authenticated as a #{type}" do
+          it 'returns 200' do
+            user = public_send(type)
+            get api("/#{source_type.pluralize}/#{source.id}/members", user)
 
-              expect(response).to have_http_status(200)
-              expect(json_response.size).to eq(2)
-            end
+            expect(response).to have_http_status(200)
+            expect(json_response.size).to eq(2)
+            expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
           end
         end
       end
 
+      it 'does not return invitees' do
+        create(:"#{source_type}_member", invite_token: '123', invite_email: 'test at abc.com', source: source, user: nil)
+
+        get api("/#{source_type.pluralize}/#{source.id}/members", developer)
+
+        expect(response).to have_http_status(200)
+        expect(json_response.size).to eq(2)
+        expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+      end
+
       it 'finds members with query string' do
         get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
 
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
new file mode 100644
index 0000000..8f1e5ac
--- /dev/null
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -0,0 +1,49 @@
+require "spec_helper"
+
+describe API::API, 'MergeRequestDiffs', api: true  do
+  include ApiHelpers
+
+  let!(:user)          { create(:user) }
+  let!(:merge_request) { create(:merge_request, importing: true) }
+  let!(:project)       { merge_request.target_project }
+
+  before do
+    merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
+    merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+    project.team << [user, :master]
+  end
+
+  describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do
+    context 'valid merge request' do
+      before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) }
+      let(:merge_request_diff) { merge_request.merge_request_diffs.first }
+
+      it { expect(response.status).to eq 200 }
+      it { expect(json_response.size).to eq(merge_request.merge_request_diffs.size) }
+      it { expect(json_response.first['id']).to eq(merge_request_diff.id) }
+      it { expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) }
+    end
+
+    it 'returns a 404 when merge_request_id not found' do
+      get api("/projects/#{project.id}/merge_requests/999/versions", user)
+      expect(response).to have_http_status(404)
+    end
+  end
+
+  describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do
+    context 'valid merge request' do
+      before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user) }
+      let(:merge_request_diff) { merge_request.merge_request_diffs.first }
+
+      it { expect(response.status).to eq 200 }
+      it { expect(json_response['id']).to eq(merge_request_diff.id) }
+      it { expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) }
+      it { expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size) }
+    end
+
+    it 'returns a 404 when merge_request_id not found' do
+      get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user)
+      expect(response).to have_http_status(404)
+    end
+  end
+end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 617600d..a7930c5 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -9,7 +9,7 @@ describe API::API, api: true  do
   let!(:project)    { create(:project, creator_id: user.id, namespace: user.namespace) }
   let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
   let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
-  let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
+  let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
   let!(:note)       { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
   let!(:note2)      { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
   let(:milestone)   { create(:milestone, title: '1.0.0', project: project) }
@@ -33,6 +33,14 @@ describe API::API, api: true  do
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(3)
         expect(json_response.last['title']).to eq(merge_request.title)
+        expect(json_response.last).to have_key('web_url')
+        expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
+        expect(json_response.last['merge_commit_sha']).to be_nil
+        expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
+        expect(json_response.first['title']).to eq(merge_request_merged.title)
+        expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
+        expect(json_response.first['merge_commit_sha']).not_to be_nil
+        expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
       end
 
       it "returns an array of all merge_requests" do
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index d6a0c65..b89dac0 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe API::API, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
-  let!(:project) { create(:project, namespace: user.namespace ) }
+  let!(:project) { create(:empty_project, namespace: user.namespace ) }
   let!(:closed_milestone) { create(:closed_milestone, project: project) }
   let!(:milestone) { create(:milestone, project: project) }
 
@@ -12,6 +12,7 @@ describe API::API, api: true  do
   describe 'GET /projects/:id/milestones' do
     it 'returns project milestones' do
       get api("/projects/#{project.id}/milestones", user)
+
       expect(response).to have_http_status(200)
       expect(json_response).to be_an Array
       expect(json_response.first['title']).to eq(milestone.title)
@@ -19,6 +20,7 @@ describe API::API, api: true  do
 
     it 'returns a 401 error if user not authenticated' do
       get api("/projects/#{project.id}/milestones")
+
       expect(response).to have_http_status(401)
     end
 
@@ -44,6 +46,7 @@ describe API::API, api: true  do
   describe 'GET /projects/:id/milestones/:milestone_id' do
     it 'returns a project milestone by id' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
+
       expect(response).to have_http_status(200)
       expect(json_response['title']).to eq(milestone.title)
       expect(json_response['iid']).to eq(milestone.iid)
@@ -60,11 +63,13 @@ describe API::API, api: true  do
 
     it 'returns 401 error if user not authenticated' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}")
+
       expect(response).to have_http_status(401)
     end
 
     it 'returns a 404 error if milestone id not found' do
       get api("/projects/#{project.id}/milestones/1234", user)
+
       expect(response).to have_http_status(404)
     end
   end
@@ -72,6 +77,7 @@ describe API::API, api: true  do
   describe 'POST /projects/:id/milestones' do
     it 'creates a new project milestone' do
       post api("/projects/#{project.id}/milestones", user), title: 'new milestone'
+
       expect(response).to have_http_status(201)
       expect(json_response['title']).to eq('new milestone')
       expect(json_response['description']).to be_nil
@@ -80,6 +86,7 @@ describe API::API, api: true  do
     it 'creates a new project milestone with description and due date' do
       post api("/projects/#{project.id}/milestones", user),
         title: 'new milestone', description: 'release', due_date: '2013-03-02'
+
       expect(response).to have_http_status(201)
       expect(json_response['description']).to eq('release')
       expect(json_response['due_date']).to eq('2013-03-02')
@@ -87,6 +94,14 @@ describe API::API, api: true  do
 
     it 'returns a 400 error if title is missing' do
       post api("/projects/#{project.id}/milestones", user)
+
+      expect(response).to have_http_status(400)
+    end
+
+    it 'returns a 400 error if params are invalid (duplicate title)' do
+      post api("/projects/#{project.id}/milestones", user),
+        title: milestone.title, description: 'release', due_date: '2013-03-02'
+
       expect(response).to have_http_status(400)
     end
   end
@@ -95,6 +110,7 @@ describe API::API, api: true  do
     it 'updates a project milestone' do
       put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
         title: 'updated title'
+
       expect(response).to have_http_status(200)
       expect(json_response['title']).to eq('updated title')
     end
@@ -102,6 +118,7 @@ describe API::API, api: true  do
     it 'returns a 404 error if milestone id not found' do
       put api("/projects/#{project.id}/milestones/1234", user),
         title: 'updated title'
+
       expect(response).to have_http_status(404)
     end
   end
@@ -131,6 +148,7 @@ describe API::API, api: true  do
     end
     it 'returns project issues for a particular milestone' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+
       expect(response).to have_http_status(200)
       expect(json_response).to be_an Array
       expect(json_response.first['milestone']['title']).to eq(milestone.title)
@@ -138,11 +156,12 @@ describe API::API, api: true  do
 
     it 'returns a 401 error if user not authenticated' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
+
       expect(response).to have_http_status(401)
     end
 
     describe 'confidential issues' do
-      let(:public_project) { create(:project, :public) }
+      let(:public_project) { create(:empty_project, :public) }
       let(:milestone) { create(:milestone, project: public_project) }
       let(:issue) { create(:issue, project: public_project) }
       let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 737fa14..063a870 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -25,7 +25,7 @@ describe API::API, api: true  do
   let!(:cross_reference_note) do
     create :note,
     noteable: ext_issue, project: ext_proj,
-    note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
+    note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}",
     system: true
   end
 
@@ -220,6 +220,15 @@ describe API::API, api: true  do
           expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time)
         end
       end
+
+      context 'when the user is posting an award emoji' do
+        it 'returns an award emoji' do
+          post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: ':+1:'
+
+          expect(response).to have_http_status(201)
+          expect(json_response['awardable_id']).to eq issue.id
+        end
+      end
     end
 
     context "when noteable is a Snippet" do
diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb
new file mode 100644
index 0000000..e6d8a5e
--- /dev/null
+++ b/spec/requests/api/notification_settings_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+  include ApiHelpers
+
+  let(:user) { create(:user) }
+  let!(:group) { create(:group) }
+  let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) }
+
+  describe "GET /notification_settings" do
+    it "returns global notification settings for the current user" do
+      get api("/notification_settings", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a Hash
+      expect(json_response['notification_email']).to eq(user.notification_email)
+      expect(json_response['level']).to eq(user.global_notification_setting.level)
+    end
+  end
+
+  describe "PUT /notification_settings" do
+    let(:email) { create(:email, user: user) }
+
+    it "updates global notification settings for the current user" do
+      put api("/notification_settings", user), { level: 'watch', notification_email: email.email }
+
+      expect(response).to have_http_status(200)
+      expect(json_response['notification_email']).to eq(email.email)
+      expect(user.reload.notification_email).to eq(email.email)
+      expect(json_response['level']).to eq(user.reload.global_notification_setting.level)
+    end
+  end
+
+  describe "PUT /notification_settings" do
+    it "fails on non-user email address" do
+      put api("/notification_settings", user), { notification_email: 'invalid at example.com' }
+
+      expect(response).to have_http_status(400)
+    end
+  end
+
+  describe "GET /groups/:id/notification_settings" do
+    it "returns group level notification settings for the current user" do
+      get api("/groups/#{group.id}/notification_settings", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a Hash
+      expect(json_response['level']).to eq(user.notification_settings_for(group).level)
+    end
+  end
+
+  describe "PUT /groups/:id/notification_settings" do
+    it "updates group level notification settings for the current user" do
+      put api("/groups/#{group.id}/notification_settings", user), { level: 'watch' }
+
+      expect(response).to have_http_status(200)
+      expect(json_response['level']).to eq(user.reload.notification_settings_for(group).level)
+    end
+  end
+
+  describe "GET /projects/:id/notification_settings" do
+    it "returns project level notification settings for the current user" do
+      get api("/projects/#{project.id}/notification_settings", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a Hash
+      expect(json_response['level']).to eq(user.notification_settings_for(project).level)
+    end
+  end
+
+  describe "PUT /projects/:id/notification_settings" do
+    it "updates project level notification settings for the current user" do
+      put api("/projects/#{project.id}/notification_settings", user), { level: 'custom', new_note: true }
+
+      expect(response).to have_http_status(200)
+      expect(json_response['level']).to eq(user.reload.notification_settings_for(project).level)
+      expect(json_response['events']['new_note']).to eq(true)
+      expect(json_response['events']['new_issue']).to eq(false)
+    end
+  end
+
+  describe "PUT /projects/:id/notification_settings" do
+    it "fails on invalid level" do
+      put api("/projects/#{project.id}/notification_settings", user), { level: 'invalid' }
+
+      expect(response).to have_http_status(400)
+    end
+  end
+end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 914e88c..765dc8a 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -34,6 +34,7 @@ describe API::API, 'ProjectHooks', api: true do
         expect(json_response.first['note_events']).to eq(true)
         expect(json_response.first['build_events']).to eq(true)
         expect(json_response.first['pipeline_events']).to eq(true)
+        expect(json_response.first['wiki_page_events']).to eq(true)
         expect(json_response.first['enable_ssl_verification']).to eq(true)
       end
     end
@@ -57,6 +58,9 @@ describe API::API, 'ProjectHooks', api: true do
         expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
         expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
         expect(json_response['note_events']).to eq(hook.note_events)
+        expect(json_response['build_events']).to eq(hook.build_events)
+        expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
+        expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
         expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
       end
 
@@ -93,6 +97,7 @@ describe API::API, 'ProjectHooks', api: true do
       expect(json_response['note_events']).to eq(false)
       expect(json_response['build_events']).to eq(false)
       expect(json_response['pipeline_events']).to eq(false)
+      expect(json_response['wiki_page_events']).to eq(false)
       expect(json_response['enable_ssl_verification']).to eq(true)
     end
 
@@ -118,6 +123,9 @@ describe API::API, 'ProjectHooks', api: true do
       expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
       expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
       expect(json_response['note_events']).to eq(hook.note_events)
+      expect(json_response['build_events']).to eq(hook.build_events)
+      expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
+      expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
       expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
     end
 
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 42757ff..01148f0 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -30,6 +30,7 @@ describe API::API, api: true do
       expect(response).to have_http_status(200)
       expect(json_response.size).to eq(3)
       expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
+      expect(json_response.last).to have_key('web_url')
     end
 
     it 'hides private snippets from regular user' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 4742b3d..192c7d1 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -73,7 +73,7 @@ describe API::API, api: true  do
       end
 
       it 'does not include open_issues_count' do
-        project.update_attributes( { issues_enabled: false } )
+        project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
 
         get api('/projects', user)
         expect(response.status).to eq 200
@@ -224,14 +224,23 @@ describe API::API, api: true  do
         description: FFaker::Lorem.sentence,
         issues_enabled: false,
         merge_requests_enabled: false,
-        wiki_enabled: false
+        wiki_enabled: false,
+        only_allow_merge_if_build_succeeds: false,
+        request_access_enabled: true
       })
 
       post api('/projects', user), project
 
       project.each_pair do |k, v|
+        next if %i{ issues_enabled merge_requests_enabled wiki_enabled }.include?(k)
         expect(json_response[k.to_s]).to eq(v)
       end
+
+      # Check feature permissions attributes
+      project = Project.find_by_path(project[:path])
+      expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
+      expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED)
+      expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
     end
 
     it 'sets a project as public' do
@@ -276,6 +285,18 @@ describe API::API, api: true  do
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
     end
 
+    it 'sets a project as allowing merge even if build fails' do
+      project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
+      post api('/projects', user), project
+      expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey
+    end
+
+    it 'sets a project as allowing merge only if build succeeds' do
+      project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true })
+      post api('/projects', user), project
+      expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy
+    end
+
     context 'when a visibility level is restricted' do
       before do
         @project = attributes_for(:project, { public: true })
@@ -332,7 +353,8 @@ describe API::API, api: true  do
         description: FFaker::Lorem.sentence,
         issues_enabled: false,
         merge_requests_enabled: false,
-        wiki_enabled: false
+        wiki_enabled: false,
+        request_access_enabled: true
       })
 
       post api("/projects/user/#{user.id}", admin), project
@@ -384,6 +406,18 @@ describe API::API, api: true  do
       expect(json_response['public']).to be_falsey
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
     end
+
+    it 'sets a project as allowing merge even if build fails' do
+      project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
+      post api("/projects/user/#{user.id}", admin), project
+      expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey
+    end
+
+    it 'sets a project as allowing merge only if build succeeds' do
+      project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true })
+      post api("/projects/user/#{user.id}", admin), project
+      expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy
+    end
   end
 
   describe "POST /projects/:id/uploads" do
@@ -444,6 +478,7 @@ describe API::API, api: true  do
       expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
       expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
       expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
+      expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds)
     end
 
     it 'returns a project by path name' do
@@ -854,6 +889,15 @@ describe API::API, api: true  do
         expect(json_response['message']['name']).to eq(['has already been taken'])
       end
 
+      it 'updates request_access_enabled' do
+        project_param = { request_access_enabled: false }
+
+        put api("/projects/#{project.id}", user), project_param
+
+        expect(response).to have_http_status(200)
+        expect(json_response['request_access_enabled']).to eq(false)
+      end
+
       it 'updates path & name to existing path & name in different namespace' do
         project_param = { path: project4.path, name: project4.name }
         put api("/projects/#{project3.id}", user), project_param
@@ -915,7 +959,8 @@ describe API::API, api: true  do
                           wiki_enabled: true,
                           snippets_enabled: true,
                           merge_requests_enabled: true,
-                          description: 'new description' }
+                          description: 'new description',
+                          request_access_enabled: true }
         put api("/projects/#{project.id}", user3), project_param
         expect(response).to have_http_status(403)
       end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 0bbba64..ef73778 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -605,6 +605,7 @@ describe API::API, api: true  do
       expect(json_response['can_create_project']).to eq(user.can_create_project?)
       expect(json_response['can_create_group']).to eq(user.can_create_group?)
       expect(json_response['projects_limit']).to eq(user.projects_limit)
+      expect(json_response['private_token']).to be_blank
     end
 
     it "returns 401 error if user is unauthenticated" do
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index ca7932d..df97f1b 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -15,6 +15,25 @@ describe Ci::API::API do
 
     describe "POST /builds/register" do
       let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
+      let(:user_agent) { 'gitlab-ci-multi-runner 1.5.2 (1-5-stable; go1.6.3; linux/amd64)' }
+
+      shared_examples 'no builds available' do
+        context 'when runner sends version in User-Agent' do
+          context 'for stable version' do
+            it { expect(response).to have_http_status(204) }
+          end
+
+          context 'for beta version' do
+            let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (1-5-stable; go1.6.3; linux/amd64)' }
+            it { expect(response).to have_http_status(204) }
+          end
+        end
+
+        context "when runner doesn't send version in User-Agent" do
+          let(:user_agent) { 'Go-http-client/1.1' }
+          it { expect(response).to have_http_status(404) }
+        end
+      end
 
       it "starts a build" do
         register_builds info: { platform: :darwin }
@@ -33,36 +52,30 @@ describe Ci::API::API do
       context 'when builds are finished' do
         before do
           build.success
-        end
-
-        it "returns 404 error if no builds for specific runner" do
           register_builds
-
-          expect(response).to have_http_status(404)
         end
+
+        it_behaves_like 'no builds available'
       end
 
       context 'for other project with builds' do
         before do
           build.success
           create(:ci_build, :pending)
-        end
-
-        it "returns 404 error if no builds for shared runner" do
           register_builds
-
-          expect(response).to have_http_status(404)
         end
+
+        it_behaves_like 'no builds available'
       end
 
       context 'for shared runner' do
         let(:shared_runner) { create(:ci_runner, token: "SharedRunner") }
 
-        it "should return 404 error if no builds for shared runner" do
+        before do
           register_builds shared_runner.token
-
-          expect(response).to have_http_status(404)
         end
+
+        it_behaves_like 'no builds available'
       end
 
       context 'for triggered build' do
@@ -136,18 +149,27 @@ describe Ci::API::API do
         end
 
         context 'when runner is not allowed to pick untagged builds' do
-          before { runner.update_column(:run_untagged, false) }
-
-          it 'does not pick build' do
+          before do
+            runner.update_column(:run_untagged, false)
             register_builds
-
-            expect(response).to have_http_status 404
           end
+
+          it_behaves_like 'no builds available'
+        end
+      end
+
+      context 'when runner is paused' do
+        let(:inactive_runner) { create(:ci_runner, :inactive, token: "InactiveRunner") }
+
+        before do
+          register_builds inactive_runner.token
         end
+
+        it { expect(response).to have_http_status 404 }
       end
 
       def register_builds(token = runner.token, **params)
-        post ci_api("/builds/register"), params.merge(token: token)
+        post ci_api("/builds/register"), params.merge(token: token), { 'User-Agent' => user_agent }
       end
     end
 
@@ -230,8 +252,10 @@ describe Ci::API::API do
       let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
       let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
       let(:get_url) { ci_api("/builds/#{build.id}/artifacts") }
-      let(:headers) { { "GitLab-Workhorse" => "1.0" } }
-      let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
+      let(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+      let(:headers) { { "GitLab-Workhorse" => "1.0", Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } }
+      let(:token) { build.token }
+      let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => token) }
 
       before { build.run! }
 
@@ -239,27 +263,51 @@ describe Ci::API::API do
         context "should authorize posting artifact to running build" do
           it "using token as parameter" do
             post authorize_url, { token: build.token }, headers
+
             expect(response).to have_http_status(200)
+            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
             expect(json_response["TempPath"]).not_to be_nil
           end
 
           it "using token as header" do
             post authorize_url, {}, headers_with_token
+
             expect(response).to have_http_status(200)
+            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
             expect(json_response["TempPath"]).not_to be_nil
           end
+
+          it "using runners token" do
+            post authorize_url, { token: build.project.runners_token }, headers
+
+            expect(response).to have_http_status(200)
+            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+            expect(json_response["TempPath"]).not_to be_nil
+          end
+
+          it "reject requests that did not go through gitlab-workhorse" do
+            headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+            post authorize_url, { token: build.token }, headers
+
+            expect(response).to have_http_status(500)
+          end
         end
 
         context "should fail to post too large artifact" do
           it "using token as parameter" do
             stub_application_setting(max_artifacts_size: 0)
+
             post authorize_url, { token: build.token, filesize: 100 }, headers
+
             expect(response).to have_http_status(413)
           end
 
           it "using token as header" do
             stub_application_setting(max_artifacts_size: 0)
+
             post authorize_url, { filesize: 100 }, headers_with_token
+
             expect(response).to have_http_status(413)
           end
         end
@@ -327,6 +375,16 @@ describe Ci::API::API do
 
               it_behaves_like 'successful artifacts upload'
             end
+
+            context 'when using runners token' do
+              let(:token) { build.project.runners_token }
+
+              before do
+                upload_artifacts(file_upload, headers_with_token)
+              end
+
+              it_behaves_like 'successful artifacts upload'
+            end
           end
 
           context 'posts artifacts file and metadata file' do
@@ -466,19 +524,40 @@ describe Ci::API::API do
 
         before do
           delete delete_url, token: build.token
-          build.reload
         end
 
-        it 'removes build artifacts' do
-          expect(response).to have_http_status(200)
-          expect(build.artifacts_file.exists?).to be_falsy
-          expect(build.artifacts_metadata.exists?).to be_falsy
-          expect(build.artifacts_size).to be_nil
+        shared_examples 'having removable artifacts' do
+          it 'removes build artifacts' do
+            build.reload
+
+            expect(response).to have_http_status(200)
+            expect(build.artifacts_file.exists?).to be_falsy
+            expect(build.artifacts_metadata.exists?).to be_falsy
+            expect(build.artifacts_size).to be_nil
+          end
+        end
+
+        context 'when using build token' do
+          before do
+            delete delete_url, token: build.token
+          end
+
+          it_behaves_like 'having removable artifacts'
+        end
+
+        context 'when using runnners token' do
+          before do
+            delete delete_url, token: build.project.runners_token
+          end
+
+          it_behaves_like 'having removable artifacts'
         end
       end
 
       describe 'GET /builds/:id/artifacts' do
-        before { get get_url, token: build.token }
+        before do
+          get get_url, token: token
+        end
 
         context 'build has artifacts' do
           let(:build) { create(:ci_build, :artifacts) }
@@ -487,13 +566,29 @@ describe Ci::API::API do
               'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
           end
 
-          it 'downloads artifact' do
-            expect(response).to have_http_status(200)
-            expect(response.headers).to include download_headers
+          shared_examples 'having downloadable artifacts' do
+            it 'download artifacts' do
+              expect(response).to have_http_status(200)
+              expect(response.headers).to include download_headers
+            end
+          end
+
+          context 'when using build token' do
+            let(:token) { build.token }
+
+            it_behaves_like 'having downloadable artifacts'
+          end
+
+          context 'when using runnners token' do
+            let(:token) { build.project.runners_token }
+
+            it_behaves_like 'having downloadable artifacts'
           end
         end
 
         context 'build does not has artifacts' do
+          let(:token) { build.token }
+
           it 'responds with not found' do
             expect(response).to have_http_status(404)
           end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index afaf4b7..7451668 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -1,6 +1,8 @@
 require "spec_helper"
 
 describe 'Git HTTP requests', lib: true do
+  include WorkhorseHelpers
+
   let(:user)    { create(:user) }
   let(:project) { create(:project, path: 'project.git-project') }
 
@@ -48,6 +50,7 @@ describe 'Git HTTP requests', lib: true do
 
         expect(response).to have_http_status(200)
         expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
+        expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
       end
     end
   end
@@ -63,6 +66,7 @@ describe 'Git HTTP requests', lib: true do
       it "downloads get status 200" do
         download(path, {}) do |response|
           expect(response).to have_http_status(200)
+          expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
         end
       end
 
@@ -101,6 +105,14 @@ describe 'Git HTTP requests', lib: true do
           end
         end
       end
+      
+      context 'when the request is not from gitlab-workhorse' do
+        it 'raises an exception' do
+          expect do
+            get("/#{project.path_with_namespace}.git/info/refs?service=git-upload-pack")
+          end.to raise_error(JWT::DecodeError)
+        end
+      end
     end
 
     context "when the project is private" do
@@ -170,11 +182,13 @@ describe 'Git HTTP requests', lib: true do
                 clone_get(path, env)
 
                 expect(response).to have_http_status(200)
+                expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
               end
 
               it "uploads get status 200" do
                 upload(path, env) do |response|
                   expect(response).to have_http_status(200)
+                  expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
                 end
               end
             end
@@ -189,6 +203,7 @@ describe 'Git HTTP requests', lib: true do
                 clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
 
                 expect(response).to have_http_status(200)
+                expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
               end
 
               it "uploads get status 401 (no project existence information leak)" do
@@ -285,23 +300,79 @@ describe 'Git HTTP requests', lib: true do
       end
 
       context "when a gitlab ci token is provided" do
-        let(:token) { 123 }
-        let(:project) { FactoryGirl.create :empty_project }
+        let(:build) { create(:ci_build, :running) }
+        let(:project) { build.project }
+        let(:other_project) { create(:empty_project) }
 
         before do
-          project.update_attributes(runners_token: token, builds_enabled: true)
+          project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED)
         end
 
-        it "downloads get status 200" do
-          clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
+        context 'when build created by system is authenticated' do
+          it "downloads get status 200" do
+            clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
 
-          expect(response).to have_http_status(200)
+            expect(response).to have_http_status(200)
+            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+          end
+
+          it "uploads get status 401 (no project existence information leak)" do
+            push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+            expect(response).to have_http_status(401)
+          end
+
+          it "downloads from other project get status 404" do
+            clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+            expect(response).to have_http_status(404)
+          end
         end
 
-        it "uploads get status 401 (no project existence information leak)" do
-          push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
+        context 'and build created by' do
+          before do
+            build.update(user: user)
+            project.team << [user, :reporter]
+          end
 
-          expect(response).to have_http_status(401)
+          shared_examples 'can download code only' do
+            it 'downloads get status 200' do
+              clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+              expect(response).to have_http_status(200)
+              expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+            end
+
+            it 'uploads get status 403' do
+              push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+              expect(response).to have_http_status(401)
+            end
+          end
+
+          context 'administrator' do
+            let(:user) { create(:admin) }
+
+            it_behaves_like 'can download code only'
+
+            it 'downloads from other project get status 403' do
+              clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+              expect(response).to have_http_status(403)
+            end
+          end
+
+          context 'regular user' do
+            let(:user) { create(:user) }
+
+            it_behaves_like 'can download code only'
+
+            it 'downloads from other project get status 404' do
+              clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+              expect(response).to have_http_status(404)
+            end
+          end
         end
       end
     end
@@ -425,7 +496,7 @@ describe 'Git HTTP requests', lib: true do
   end
 
   def auth_env(user, password, spnego_request_token)
-    env = {}
+    env = workhorse_internal_api_request_header
     if user && password
       env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password)
     elsif spnego_request_token
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index c6172b9..6b956e6 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -22,19 +22,22 @@ describe JwtController do
 
   context 'when using authorized request' do
     context 'using CI token' do
-      let(:project) { create(:empty_project, runners_token: 'token', builds_enabled: builds_enabled) }
-      let(:headers) { { authorization: credentials('gitlab-ci-token', project.runners_token) } }
-
-      subject! { get '/jwt/auth', parameters, headers }
+      let(:build) { create(:ci_build, :running) }
+      let(:project) { build.project }
+      let(:headers) { { authorization: credentials('gitlab-ci-token', build.token) } }
 
       context 'project with enabled CI' do
-        let(:builds_enabled) { true }
+        subject! { get '/jwt/auth', parameters, headers }
 
         it { expect(service_class).to have_received(:new).with(project, nil, parameters) }
       end
 
       context 'project with disabled CI' do
-        let(:builds_enabled) { false }
+        before do
+          project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
+        end
+
+        subject! { get '/jwt/auth', parameters, headers }
 
         it { expect(response).to have_http_status(403) }
       end
@@ -42,13 +45,31 @@ describe JwtController do
 
     context 'using User login' do
       let(:user) { create(:user) }
-      let(:headers) { { authorization: credentials('user', 'password') } }
-
-      before { expect(Gitlab::Auth).to receive(:find_with_user_password).with('user', 'password').and_return(user) }
+      let(:headers) { { authorization: credentials(user.username, user.password) } }
 
       subject! { get '/jwt/auth', parameters, headers }
 
       it { expect(service_class).to have_received(:new).with(nil, user, parameters) }
+
+      context 'when user has 2FA enabled' do
+        let(:user) { create(:user, :two_factor) }
+
+        context 'without personal token' do
+          it 'rejects the authorization attempt' do
+            expect(response).to have_http_status(401)
+            expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
+          end
+        end
+
+        context 'with personal token' do
+          let(:access_token) { create(:personal_access_token, user: user) }
+          let(:headers) { { authorization: credentials(user.username, access_token.token) } }
+
+          it 'rejects the authorization attempt' do
+            expect(response).to have_http_status(200)
+          end
+        end
+      end
     end
 
     context 'using invalid login' do
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 4c9b4a8..09e4e26 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe 'Git LFS API and storage' do
+  include WorkhorseHelpers
+
   let(:user) { create(:user) }
   let!(:lfs_object) { create(:lfs_object, :with_file) }
 
@@ -12,6 +14,7 @@ describe 'Git LFS API and storage' do
   end
   let(:authorization) { }
   let(:sendfile) { }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project) }
 
   let(:sample_oid) { lfs_object.oid }
   let(:sample_size) { lfs_object.size }
@@ -44,6 +47,113 @@ describe 'Git LFS API and storage' do
     end
   end
 
+  context 'project specific LFS settings' do
+    let(:project) { create(:empty_project) }
+    let(:body) do
+      {
+        'objects' => [
+          { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+            'size' => 1575078
+          },
+          { 'oid' => sample_oid,
+            'size' => sample_size
+          }
+        ],
+        'operation' => 'upload'
+      }
+    end
+    let(:authorization) { authorize_user }
+
+    context 'with LFS disabled globally' do
+      before do
+        project.team << [user, :master]
+        allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
+      end
+
+      describe 'LFS disabled in project' do
+        before do
+          project.update_attribute(:lfs_enabled, false)
+        end
+
+        it 'responds with a 501 message on upload' do
+          post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+
+          expect(response).to have_http_status(501)
+        end
+
+        it 'responds with a 501 message on download' do
+          get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+
+          expect(response).to have_http_status(501)
+        end
+      end
+
+      describe 'LFS enabled in project' do
+        before do
+          project.update_attribute(:lfs_enabled, true)
+        end
+
+        it 'responds with a 501 message on upload' do
+          post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+
+          expect(response).to have_http_status(501)
+        end
+
+        it 'responds with a 501 message on download' do
+          get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+
+          expect(response).to have_http_status(501)
+        end
+      end
+    end
+
+    context 'with LFS enabled globally' do
+      before do
+        project.team << [user, :master]
+        enable_lfs
+      end
+
+      describe 'LFS disabled in project' do
+        before do
+          project.update_attribute(:lfs_enabled, false)
+        end
+
+        it 'responds with a 403 message on upload' do
+          post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+
+          expect(response).to have_http_status(403)
+          expect(json_response).to include('message' => 'Access forbidden. Check your access level.')
+        end
+
+        it 'responds with a 403 message on download' do
+          get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+
+          expect(response).to have_http_status(403)
+          expect(json_response).to include('message' => 'Access forbidden. Check your access level.')
+        end
+      end
+
+      describe 'LFS enabled in project' do
+        before do
+          project.update_attribute(:lfs_enabled, true)
+        end
+
+        it 'responds with a 200 message on upload' do
+          post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+
+          expect(response).to have_http_status(200)
+          expect(json_response['objects'].first['size']).to eq(1575078)
+        end
+
+        it 'responds with a 200 message on download' do
+          get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+
+          expect(response).to have_http_status(200)
+        end
+      end
+    end
+  end
+
   describe 'deprecated API' do
     let(:project) { create(:empty_project) }
 
@@ -135,15 +245,76 @@ describe 'Git LFS API and storage' do
           end
         end
 
-        context 'when CI is authorized' do
-          let(:authorization) { authorize_ci_project }
+        context 'when deploy key is authorized' do
+          let(:key) { create(:deploy_key) }
+          let(:authorization) { authorize_deploy_key }
 
           let(:update_permissions) do
+            project.deploy_keys << key
             project.lfs_objects << lfs_object
           end
 
           it_behaves_like 'responds with a file'
         end
+
+        context 'when build is authorized as' do
+          let(:authorization) { authorize_ci_project }
+
+          shared_examples 'can download LFS only from own projects' do
+            context 'for own project' do
+              let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+              let(:update_permissions) do
+                project.team << [user, :reporter]
+                project.lfs_objects << lfs_object
+              end
+
+              it_behaves_like 'responds with a file'
+            end
+
+            context 'for other project' do
+              let(:other_project) { create(:empty_project) }
+              let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+
+              let(:update_permissions) do
+                project.lfs_objects << lfs_object
+              end
+
+              it 'rejects downloading code' do
+                expect(response).to have_http_status(other_project_status)
+              end
+            end
+          end
+
+          context 'administrator' do
+            let(:user) { create(:admin) }
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            it_behaves_like 'can download LFS only from own projects' do
+              # We render 403, because administrator does have normally access
+              let(:other_project_status) { 403 }
+            end
+          end
+
+          context 'regular user' do
+            let(:user) { create(:user) }
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            it_behaves_like 'can download LFS only from own projects' do
+              # We render 404, to prevent data leakage about existence of the project
+              let(:other_project_status) { 404 }
+            end
+          end
+
+          context 'does not have user' do
+            let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+            it_behaves_like 'can download LFS only from own projects' do
+              # We render 404, to prevent data leakage about existence of the project
+              let(:other_project_status) { 404 }
+            end
+          end
+        end
       end
 
       context 'without required headers' do
@@ -322,10 +493,62 @@ describe 'Git LFS API and storage' do
         end
       end
 
-      context 'when CI is authorized' do
+      context 'when build is authorized as' do
         let(:authorization) { authorize_ci_project }
 
-        it_behaves_like 'an authorized requests'
+        let(:update_lfs_permissions) do
+          project.lfs_objects << lfs_object
+        end
+
+        shared_examples 'can download LFS only from own projects' do
+          context 'for own project' do
+            let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+            let(:update_user_permissions) do
+              project.team << [user, :reporter]
+            end
+
+            it_behaves_like 'an authorized requests'
+          end
+
+          context 'for other project' do
+            let(:other_project) { create(:empty_project) }
+            let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+
+            it 'rejects downloading code' do
+              expect(response).to have_http_status(other_project_status)
+            end
+          end
+        end
+
+        context 'administrator' do
+          let(:user) { create(:admin) }
+          let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+          it_behaves_like 'can download LFS only from own projects' do
+            # We render 403, because administrator does have normally access
+            let(:other_project_status) { 403 }
+          end
+        end
+
+        context 'regular user' do
+          let(:user) { create(:user) }
+          let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+          it_behaves_like 'can download LFS only from own projects' do
+            # We render 404, to prevent data leakage about existence of the project
+            let(:other_project_status) { 404 }
+          end
+        end
+
+        context 'does not have user' do
+          let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+          it_behaves_like 'can download LFS only from own projects' do
+            # We render 404, to prevent data leakage about existence of the project
+            let(:other_project_status) { 404 }
+          end
+        end
       end
 
       context 'when user is not authenticated' do
@@ -474,11 +697,37 @@ describe 'Git LFS API and storage' do
           end
         end
 
-        context 'when CI is authorized' do
+        context 'when build is authorized' do
           let(:authorization) { authorize_ci_project }
 
-          it 'responds with 401' do
-            expect(response).to have_http_status(401)
+          context 'build has an user' do
+            let(:user) { create(:user) }
+
+            context 'tries to push to own project' do
+              let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+              it 'responds with 401' do
+                expect(response).to have_http_status(401)
+              end
+            end
+
+            context 'tries to push to other project' do
+              let(:other_project) { create(:empty_project) }
+              let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+              let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+              it 'responds with 401' do
+                expect(response).to have_http_status(401)
+              end
+            end
+          end
+
+          context 'does not have user' do
+            let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
           end
         end
       end
@@ -500,14 +749,6 @@ describe 'Git LFS API and storage' do
           end
         end
       end
-
-      context 'when CI is authorized' do
-        let(:authorization) { authorize_ci_project }
-
-        it 'responds with status 403' do
-          expect(response).to have_http_status(401)
-        end
-      end
     end
 
     describe 'unsupported' do
@@ -608,6 +849,12 @@ describe 'Git LFS API and storage' do
             project.team << [user, :developer]
           end
 
+          context 'and the request bypassed workhorse' do
+            it 'raises an exception' do
+              expect { put_authorize(verified: false) }.to raise_error JWT::DecodeError
+            end
+          end
+
           context 'and request is sent by gitlab-workhorse to authorize the request' do
             before do
               put_authorize
@@ -617,6 +864,10 @@ describe 'Git LFS API and storage' do
               expect(response).to have_http_status(200)
             end
 
+            it 'uses the gitlab-workhorse content type' do
+              expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+            end
+
             it 'responds with status 200, location of lfs store and object details' do
               expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
               expect(json_response['LfsOid']).to eq(sample_oid)
@@ -660,10 +911,51 @@ describe 'Git LFS API and storage' do
         end
       end
 
-      context 'when CI is authenticated' do
+      context 'when build is authorized' do
         let(:authorization) { authorize_ci_project }
 
-        it_behaves_like 'unauthorized'
+        context 'build has an user' do
+          let(:user) { create(:user) }
+
+          context 'tries to push to own project' do
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            before do
+              project.team << [user, :developer]
+              put_authorize
+            end
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
+          end
+
+          context 'tries to push to other project' do
+            let(:other_project) { create(:empty_project) }
+            let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            before do
+              put_authorize
+            end
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
+          end
+        end
+
+        context 'does not have user' do
+          let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+          before do
+            put_authorize
+          end
+
+          it 'responds with 401' do
+            expect(response).to have_http_status(401)
+          end
+        end
       end
 
       context 'for unauthenticated' do
@@ -720,10 +1012,42 @@ describe 'Git LFS API and storage' do
         end
       end
 
-      context 'when CI is authenticated' do
+      context 'when build is authorized' do
         let(:authorization) { authorize_ci_project }
 
-        it_behaves_like 'unauthorized'
+        before do
+          put_authorize
+        end
+
+        context 'build has an user' do
+          let(:user) { create(:user) }
+
+          context 'tries to push to own project' do
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
+          end
+
+          context 'tries to push to other project' do
+            let(:other_project) { create(:empty_project) }
+            let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
+          end
+        end
+
+        context 'does not have user' do
+          let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+          it 'responds with 401' do
+            expect(response).to have_http_status(401)
+          end
+        end
       end
 
       context 'for unauthenticated' do
@@ -756,8 +1080,11 @@ describe 'Git LFS API and storage' do
       end
     end
 
-    def put_authorize
-      put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, headers
+    def put_authorize(verified: true)
+      authorize_headers = headers
+      authorize_headers.merge!(workhorse_internal_api_request_header) if verified
+
+      put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, authorize_headers
     end
 
     def put_finalize(lfs_tmp = lfs_tmp_file)
@@ -775,13 +1102,17 @@ describe 'Git LFS API and storage' do
   end
 
   def authorize_ci_project
-    ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token)
+    ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', build.token)
   end
 
   def authorize_user
     ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
   end
 
+  def authorize_deploy_key
+    ActionController::HttpAuthentication::Basic.encode_credentials("lfs+deploy-key-#{key.id}", Gitlab::LfsToken.new(key).generate)
+  end
+
   def fork_project(project, user, object = nil)
     allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
     Projects::ForkService.new(project, user, {}).execute
diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb
new file mode 100644
index 0000000..e02f0ea
--- /dev/null
+++ b/spec/requests/projects/artifacts_controller_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+
+describe Projects::ArtifactsController do
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+
+  let(:pipeline) do
+    create(:ci_pipeline,
+            project: project,
+            sha: project.commit.sha,
+            ref: project.default_branch,
+            status: 'success')
+  end
+
+  let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+
+  describe 'GET /:project/builds/artifacts/:ref_name/browse?job=name' do
+    before do
+      project.team << [user, :developer]
+
+      login_as(user)
+    end
+
+    def path_from_ref(
+      ref = pipeline.ref, job = build.name, path = 'browse')
+      latest_succeeded_namespace_project_artifacts_path(
+        project.namespace,
+        project,
+        [ref, path].join('/'),
+        job: job)
+    end
+
+    context 'cannot find the build' do
+      shared_examples 'not found' do
+        it { expect(response).to have_http_status(:not_found) }
+      end
+
+      context 'has no such ref' do
+        before do
+          get path_from_ref('TAIL', build.name)
+        end
+
+        it_behaves_like 'not found'
+      end
+
+      context 'has no such build' do
+        before do
+          get path_from_ref(pipeline.ref, 'NOBUILD')
+        end
+
+        it_behaves_like 'not found'
+      end
+
+      context 'has no path' do
+        before do
+          get path_from_ref(pipeline.sha, build.name, '')
+        end
+
+        it_behaves_like 'not found'
+      end
+    end
+
+    context 'found the build and redirect' do
+      shared_examples 'redirect to the build' do
+        it 'redirects' do
+          path = browse_namespace_project_build_artifacts_path(
+            project.namespace,
+            project,
+            build)
+
+          expect(response).to redirect_to(path)
+        end
+      end
+
+      context 'with regular branch' do
+        before do
+          pipeline.update(ref: 'master',
+                          sha: project.commit('master').sha)
+
+          get path_from_ref('master')
+        end
+
+        it_behaves_like 'redirect to the build'
+      end
+
+      context 'with branch name containing slash' do
+        before do
+          pipeline.update(ref: 'improve/awesome',
+                          sha: project.commit('improve/awesome').sha)
+
+          get path_from_ref('improve/awesome')
+        end
+
+        it_behaves_like 'redirect to the build'
+      end
+
+      context 'with branch name and path containing slashes' do
+        before do
+          pipeline.update(ref: 'improve/awesome',
+                          sha: project.commit('improve/awesome').sha)
+
+          get path_from_ref('improve/awesome', build.name, 'file/README.md')
+        end
+
+        it 'redirects' do
+          path = file_namespace_project_build_artifacts_path(
+            project.namespace,
+            project,
+            build,
+            'README.md')
+
+          expect(response).to redirect_to(path)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index d65648d..4bc3cdd 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -107,9 +107,9 @@ describe HelpController, "routing" do
   end
 
   it 'to #show' do
-    path = '/help/markdown/markdown.md'
+    path = '/help/user/markdown.md'
     expect(get(path)).to route_to('help#show',
-                                  path: 'markdown/markdown',
+                                  path: 'user/markdown',
                                   format: 'md')
 
     path = '/help/workflow/protected_branches/protected_branches1.png'
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 7cc71f7..c64df49 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -6,8 +6,14 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
   let(:current_params) { {} }
   let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
   let(:payload) { JWT.decode(subject[:token], rsa_key).first }
+  let(:authentication_abilities) do
+    [
+      :read_container_image,
+      :create_container_image
+    ]
+  end
 
-  subject { described_class.new(current_project, current_user, current_params).execute }
+  subject { described_class.new(current_project, current_user, current_params).execute(authentication_abilities: authentication_abilities) }
 
   before do
     allow(Gitlab.config.registry).to receive_messages(enabled: true, issuer: 'rspec', key: nil)
@@ -189,13 +195,22 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
     end
   end
 
-  context 'project authorization' do
+  context 'build authorized as user' do
     let(:current_project) { create(:empty_project) }
+    let(:current_user) { create(:user) }
+    let(:authentication_abilities) do
+      [
+        :build_read_container_image,
+        :build_create_container_image
+      ]
+    end
 
-    context 'allow to use scope-less authentication' do
-      it_behaves_like 'a valid token'
+    before do
+      current_project.team << [current_user, :developer]
     end
 
+    it_behaves_like 'a valid token'
+
     context 'allow to pull and push images' do
       let(:current_params) do
         { scope: "repository:#{current_project.path_with_namespace}:pull,push" }
@@ -214,12 +229,44 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
 
         context 'allow for public' do
           let(:project) { create(:empty_project, :public) }
+
           it_behaves_like 'a pullable'
         end
 
-        context 'disallow for private' do
+        shared_examples 'pullable for being team member' do
+          context 'when you are not member' do
+            it_behaves_like 'an inaccessible'
+          end
+
+          context 'when you are member' do
+            before do
+              project.team << [current_user, :developer]
+            end
+
+            it_behaves_like 'a pullable'
+          end
+        end
+
+        context 'for private' do
           let(:project) { create(:empty_project, :private) }
-          it_behaves_like 'an inaccessible'
+
+          it_behaves_like 'pullable for being team member'
+
+          context 'when you are admin' do
+            let(:current_user) { create(:admin) }
+
+            context 'when you are not member' do
+              it_behaves_like 'an inaccessible'
+            end
+
+            context 'when you are member' do
+              before do
+                project.team << [current_user, :developer]
+              end
+
+              it_behaves_like 'a pullable'
+            end
+          end
         end
       end
 
@@ -230,6 +277,11 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
 
         context 'disallow for all' do
           let(:project) { create(:empty_project, :public) }
+
+          before do
+            project.team << [current_user, :developer]
+          end
+
           it_behaves_like 'an inaccessible'
         end
       end
diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb
index 5e7e145..90764b8 100644
--- a/spec/services/boards/lists/create_service_spec.rb
+++ b/spec/services/boards/lists/create_service_spec.rb
@@ -5,7 +5,7 @@ describe Boards::Lists::CreateService, services: true do
     let(:project) { create(:project_with_board) }
     let(:board)   { project.board }
     let(:user)    { create(:user) }
-    let(:label)   { create(:label, name: 'in-progress') }
+    let(:label)   { create(:label, project: project, name: 'in-progress') }
 
     subject(:service) { described_class.new(project, user, label_id: label.id) }
 
@@ -50,5 +50,14 @@ describe Boards::Lists::CreateService, services: true do
         expect(list2.reload.position).to eq 1
       end
     end
+
+    context 'when provided label does not belongs to the project' do
+      it 'raises an error' do
+        label = create(:label, name: 'in-development')
+        service = described_class.new(project, user, label_id: label.id)
+
+        expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound)
+      end
+    end
   end
 end
diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb
index c931c3e..b3e0a7b 100644
--- a/spec/services/ci/image_for_build_service_spec.rb
+++ b/spec/services/ci/image_for_build_service_spec.rb
@@ -5,7 +5,7 @@ module Ci
     let(:service) { ImageForBuildService.new }
     let(:project) { FactoryGirl.create(:empty_project) }
     let(:commit_sha) { '01234567890123456789' }
-    let(:pipeline) { project.ensure_pipeline(commit_sha, 'master') }
+    let(:pipeline) { project.ensure_pipeline('master', commit_sha) }
     let(:build) { FactoryGirl.create(:ci_build, pipeline: pipeline) }
 
     describe '#execute' do
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb
index 026d0ca..1e21a32 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_build_service_spec.rb
@@ -151,6 +151,25 @@ module Ci
           it { expect(build.runner).to eq(specific_runner) }
         end
       end
+
+      context 'disallow when builds are disabled' do
+        before do
+          project.update(shared_runners_enabled: true)
+          project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
+        end
+
+        context 'and uses shared runner' do
+          let(:build) { service.execute(shared_runner) }
+
+          it { expect(build).to be_nil }
+        end
+
+        context 'and uses specific runner' do
+          let(:build) { service.execute(specific_runner) }
+
+          it { expect(build).to be_nil }
+        end
+      end
     end
   end
 end
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index 8da2a2b..343b438 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -41,7 +41,7 @@ describe CreateDeploymentService, services: true do
 
     context 'for environment with invalid name' do
       let(:params) do
-        { environment: 'name with spaces',
+        { environment: 'name,with,commas',
           ref: 'master',
           tag: false,
           sha: '97de212e80737a608d939f648d959671fb0a0142',
@@ -56,8 +56,36 @@ describe CreateDeploymentService, services: true do
         expect(subject).not_to be_persisted
       end
     end
+
+    context 'when variables are used' do
+      let(:params) do
+        { environment: 'review-apps/$CI_BUILD_REF_NAME',
+          ref: 'master',
+          tag: false,
+          sha: '97de212e80737a608d939f648d959671fb0a0142',
+          options: {
+            name: 'review-apps/$CI_BUILD_REF_NAME',
+            url: 'http://$CI_BUILD_REF_NAME.review-apps.gitlab.com'
+          },
+          variables: [
+            { key: 'CI_BUILD_REF_NAME', value: 'feature-review-apps' }
+          ]
+        }
+      end
+
+      it 'does create a new environment' do
+        expect { subject }.to change { Environment.count }.by(1)
+
+        expect(subject.environment.name).to eq('review-apps/feature-review-apps')
+        expect(subject.environment.external_url).to eq('http://feature-review-apps.review-apps.gitlab.com')
+      end
+
+      it 'does create a new deployment' do
+        expect(subject).to be_persisted
+      end
+    end
   end
-  
+
   describe 'processing of builds' do
     let(:environment) { nil }
     
@@ -95,6 +123,12 @@ describe CreateDeploymentService, services: true do
 
         expect(Deployment.last.deployable).to eq(deployable)
       end
+
+      it 'create environment has URL set' do
+        subject
+
+        expect(Deployment.last.environment.external_url).not_to be_nil
+      end
     end
 
     context 'without environment specified' do
@@ -107,7 +141,10 @@ describe CreateDeploymentService, services: true do
     
     context 'when environment is specified' do
       let(:pipeline) { create(:ci_pipeline, project: project) }
-      let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production') }
+      let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production', options: options) }
+      let(:options) do
+        { environment: { name: 'production', url: 'http://gitlab.com' } }
+      end
 
       context 'when build succeeds' do
         it_behaves_like 'does create environment and deployment' do
@@ -132,4 +169,83 @@ describe CreateDeploymentService, services: true do
       end
     end
   end
+
+  describe "merge request metrics" do
+    let(:params) do
+      {
+        environment: 'production',
+        ref: 'master',
+        tag: false,
+        sha: '97de212e80737a608d939f648d959671fb0a0142b',
+      }
+    end
+
+    let(:merge_request) { create(:merge_request, target_branch: 'master', source_branch: 'feature', source_project: project) }
+
+    context "while updating the 'first_deployed_to_production_at' time" do
+      before { merge_request.mark_as_merged }
+
+      context "for merge requests merged before the current deploy" do
+        it "sets the time if the deploy's environment is 'production'" do
+          time = Time.now
+          Timecop.freeze(time) { service.execute }
+
+          expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time)
+        end
+
+        it "doesn't set the time if the deploy's environment is not 'production'" do
+          staging_params = params.merge(environment: 'staging')
+          service = described_class.new(project, user, staging_params)
+          service.execute
+
+          expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil
+        end
+
+        it 'does not raise errors if the merge request does not have a metrics record' do
+          merge_request.metrics.destroy
+
+          expect(merge_request.reload.metrics).to be_nil
+          expect { service.execute }.not_to raise_error
+        end
+      end
+
+      context "for merge requests merged before the previous deploy" do
+        context "if the 'first_deployed_to_production_at' time is already set" do
+          it "does not overwrite the older 'first_deployed_to_production_at' time" do
+            # Previous deploy
+            time = Time.now
+            Timecop.freeze(time) { service.execute }
+
+            expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time)
+
+            # Current deploy
+            service = described_class.new(project, user, params)
+            Timecop.freeze(time + 12.hours) { service.execute }
+
+            expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time)
+          end
+        end
+
+        context "if the 'first_deployed_to_production_at' time is not already set" do
+          it "does not overwrite the older 'first_deployed_to_production_at' time" do
+            # Previous deploy
+            time = 5.minutes.from_now
+            Timecop.freeze(time) { service.execute }
+
+            expect(merge_request.reload.metrics.merged_at).to be < merge_request.reload.metrics.first_deployed_to_production_at
+
+            merge_request.reload.metrics.update(first_deployed_to_production_at: nil)
+
+            expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil
+
+            # Current deploy
+            service = described_class.new(project, user, params)
+            Timecop.freeze(time + 12.hours) { service.execute }
+
+            expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil
+          end
+        end
+      end
+    end
+  end
 end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 6ac1fa8..22991c5 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -253,6 +253,21 @@ describe GitPushService, services: true do
         expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
       end
 
+      it "when pushing a branch for the first time with an existing branch permission configured" do
+        stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+        create(:protected_branch, :no_one_can_push, :developers_can_merge, project: project, name: 'master')
+        expect(project).to receive(:execute_hooks)
+        expect(project.default_branch).to eq("master")
+        expect_any_instance_of(ProtectedBranches::CreateService).not_to receive(:execute)
+
+        execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+
+        expect(project.protected_branches).not_to be_empty
+        expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::NO_ACCESS])
+        expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
+      end
+
       it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do
         stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
 
@@ -324,6 +339,43 @@ describe GitPushService, services: true do
     end
   end
 
+  describe "issue metrics" do
+    let(:issue) { create :issue, project: project }
+    let(:commit_author) { create :user }
+    let(:commit) { project.commit }
+    let(:commit_time) { Time.now }
+
+    before do
+      project.team << [commit_author, :developer]
+      project.team << [user, :developer]
+
+      allow(commit).to receive_messages(
+        safe_message: "this commit \n mentions #{issue.to_reference}",
+        references: [issue],
+        author_name: commit_author.name,
+        author_email: commit_author.email,
+        committed_date: commit_time
+      )
+
+      allow(project.repository).to receive(:commits_between).and_return([commit])
+    end
+
+    context "while saving the 'first_mentioned_in_commit_at' metric for an issue" do
+      it 'sets the metric for referenced issues' do
+        execute_service(project, user, @oldrev, @newrev, @ref)
+
+        expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_within(1.second).of(commit_time)
+      end
+
+      it 'does not set the metric for non-referenced issues' do
+        non_referenced_issue = create(:issue, project: project)
+        execute_service(project, user, @oldrev, @newrev, @ref)
+
+        expect(non_referenced_issue.reload.metrics.first_mentioned_in_commit_at).to be_nil
+      end
+    end
+  end
+
   describe "closing issues from pushed commits containing a closing reference" do
     let(:issue) { create :issue, project: project }
     let(:other_issue) { create :issue, project: project }
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
new file mode 100644
index 0000000..6f7ce8c
--- /dev/null
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -0,0 +1,282 @@
+require 'spec_helper'
+
+describe Issuable::BulkUpdateService, services: true do
+  let(:user)    { create(:user) }
+  let(:project) { create(:empty_project, namespace: user.namespace) }
+
+  def bulk_update(issues, extra_params = {})
+    bulk_update_params = extra_params
+      .reverse_merge(issuable_ids: Array(issues).map(&:id).join(','))
+
+    Issuable::BulkUpdateService.new(project, user, bulk_update_params).execute('issue')
+  end
+
+  describe 'close issues' do
+    let(:issues) { create_list(:issue, 2, project: project) }
+
+    it 'succeeds and returns the correct number of issues updated' do
+      result = bulk_update(issues, state_event: 'close')
+
+      expect(result[:success]).to be_truthy
+      expect(result[:count]).to eq(issues.count)
+    end
+
+    it 'closes all the issues passed' do
+      bulk_update(issues, state_event: 'close')
+
+      expect(project.issues.opened).to be_empty
+      expect(project.issues.closed).not_to be_empty
+    end
+  end
+
+  describe 'reopen issues' do
+    let(:issues) { create_list(:closed_issue, 2, project: project) }
+
+    it 'succeeds and returns the correct number of issues updated' do
+      result = bulk_update(issues, state_event: 'reopen')
+
+      expect(result[:success]).to be_truthy
+      expect(result[:count]).to eq(issues.count)
+    end
+
+    it 'reopens all the issues passed' do
+      bulk_update(issues, state_event: 'reopen')
+
+      expect(project.issues.closed).to be_empty
+      expect(project.issues.opened).not_to be_empty
+    end
+  end
+
+  describe 'updating assignee' do
+    let(:issue) { create(:issue, project: project, assignee: user) }
+
+    context 'when the new assignee ID is a valid user' do
+      it 'succeeds' do
+        result = bulk_update(issue, assignee_id: create(:user).id)
+
+        expect(result[:success]).to be_truthy
+        expect(result[:count]).to eq(1)
+      end
+
+      it 'updates the assignee to the use ID passed' do
+        assignee = create(:user)
+
+        expect { bulk_update(issue, assignee_id: assignee.id) }
+          .to change { issue.reload.assignee }.from(user).to(assignee)
+      end
+    end
+
+    context 'when the new assignee ID is -1' do
+      it 'unassigns the issues' do
+        expect { bulk_update(issue, assignee_id: -1) }
+          .to change { issue.reload.assignee }.to(nil)
+      end
+    end
+
+    context 'when the new assignee ID is not present' do
+      it 'does not unassign' do
+        expect { bulk_update(issue, assignee_id: nil) }
+          .not_to change { issue.reload.assignee }
+      end
+    end
+  end
+
+  describe 'updating milestones' do
+    let(:issue)     { create(:issue, project: project) }
+    let(:milestone) { create(:milestone, project: project) }
+
+    it 'succeeds' do
+      result = bulk_update(issue, milestone_id: milestone.id)
+
+      expect(result[:success]).to be_truthy
+      expect(result[:count]).to eq(1)
+    end
+
+    it 'updates the issue milestone' do
+      expect { bulk_update(issue, milestone_id: milestone.id) }
+        .to change { issue.reload.milestone }.from(nil).to(milestone)
+    end
+  end
+
+  describe 'updating labels' do
+    def create_issue_with_labels(labels)
+      create(:labeled_issue, project: project, labels: labels)
+    end
+
+    let(:bug) { create(:label, project: project) }
+    let(:regression) { create(:label, project: project) }
+    let(:merge_requests) { create(:label, project: project) }
+
+    let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
+    let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
+    let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
+    let(:issue_no_labels) { create(:issue, project: project) }
+    let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
+
+    let(:labels) { [] }
+    let(:add_labels) { [] }
+    let(:remove_labels) { [] }
+
+    let(:bulk_update_params) do
+      {
+        label_ids:        labels.map(&:id),
+        add_label_ids:    add_labels.map(&:id),
+        remove_label_ids: remove_labels.map(&:id),
+      }
+    end
+
+    before do
+      bulk_update(issues, bulk_update_params)
+    end
+
+    context 'when label_ids are passed' do
+      let(:issues) { [issue_all_labels, issue_no_labels] }
+      let(:labels) { [bug, regression] }
+
+      it 'updates the labels of all issues passed to the labels passed' do
+        expect(issues.map(&:reload).map(&:label_ids)).to all(eq(labels.map(&:id)))
+      end
+
+      it 'does not update issues not passed in' do
+        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+      end
+
+      context 'when those label IDs are empty' do
+        let(:labels) { [] }
+
+        it 'updates the issues passed to have no labels' do
+          expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+        end
+      end
+    end
+
+    context 'when add_label_ids are passed' do
+      let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+      let(:add_labels) { [bug, regression, merge_requests] }
+
+      it 'adds those label IDs to all issues passed' do
+        expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
+      end
+
+      it 'does not update issues not passed in' do
+        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+      end
+    end
+
+    context 'when remove_label_ids are passed' do
+      let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+      let(:remove_labels) { [bug, regression, merge_requests] }
+
+      it 'removes those label IDs from all issues passed' do
+        expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+      end
+
+      it 'does not update issues not passed in' do
+        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+      end
+    end
+
+    context 'when add_label_ids and remove_label_ids are passed' do
+      let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+      let(:add_labels) { [bug] }
+      let(:remove_labels) { [merge_requests] }
+
+      it 'adds the label IDs to all issues passed' do
+        expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+      end
+
+      it 'removes the label IDs from all issues passed' do
+        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+      end
+
+      it 'does not update issues not passed in' do
+        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+      end
+    end
+
+    context 'when add_label_ids and label_ids are passed' do
+      let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
+      let(:labels) { [merge_requests] }
+      let(:add_labels) { [regression] }
+
+      it 'adds the label IDs to all issues passed' do
+        expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
+      end
+
+      it 'ignores the label IDs parameter' do
+        expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+      end
+
+      it 'does not update issues not passed in' do
+        expect(issue_no_labels.label_ids).to be_empty
+      end
+    end
+
+    context 'when remove_label_ids and label_ids are passed' do
+      let(:issues) { [issue_no_labels, issue_bug_and_regression] }
+      let(:labels) { [merge_requests] }
+      let(:remove_labels) { [regression] }
+
+      it 'removes the label IDs from all issues passed' do
+        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
+      end
+
+      it 'ignores the label IDs parameter' do
+        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+      end
+
+      it 'does not update issues not passed in' do
+        expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
+      end
+    end
+
+    context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
+      let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
+      let(:labels) { [regression] }
+      let(:add_labels) { [bug] }
+      let(:remove_labels) { [merge_requests] }
+
+      it 'adds the label IDs to all issues passed' do
+        expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+      end
+
+      it 'removes the label IDs from all issues passed' do
+        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+      end
+
+      it 'ignores the label IDs parameter' do
+        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
+      end
+
+      it 'does not update issues not passed in' do
+        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+      end
+    end
+  end
+
+  describe 'subscribe to issues' do
+    let(:issues) { create_list(:issue, 2, project: project) }
+
+    it 'subscribes the given user' do
+      bulk_update(issues, subscription_event: 'subscribe')
+
+      expect(issues).to all(be_subscribed(user))
+    end
+  end
+
+  describe 'unsubscribe from issues' do
+    let(:issues) do
+      create_list(:closed_issue, 2, project: project) do |issue|
+        issue.subscriptions.create(user: user, subscribed: true)
+      end
+    end
+
+    it 'unsubscribes the given user' do
+      bulk_update(issues, subscription_event: 'unsubscribe')
+
+      issues.each do |issue|
+        expect(issue).not_to be_subscribed(user)
+      end
+    end
+  end
+end
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
deleted file mode 100644
index ac08aa5..0000000
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ /dev/null
@@ -1,282 +0,0 @@
-require 'spec_helper'
-
-describe Issues::BulkUpdateService, services: true do
-  let(:user)    { create(:user) }
-  let(:project) { create(:empty_project, namespace: user.namespace) }
-
-  def bulk_update(issues, extra_params = {})
-    bulk_update_params = extra_params
-      .reverse_merge(issues_ids: Array(issues).map(&:id).join(','))
-
-    Issues::BulkUpdateService.new(project, user, bulk_update_params).execute
-  end
-
-  describe 'close issues' do
-    let(:issues) { create_list(:issue, 2, project: project) }
-
-    it 'succeeds and returns the correct number of issues updated' do
-      result = bulk_update(issues, state_event: 'close')
-
-      expect(result[:success]).to be_truthy
-      expect(result[:count]).to eq(issues.count)
-    end
-
-    it 'closes all the issues passed' do
-      bulk_update(issues, state_event: 'close')
-
-      expect(project.issues.opened).to be_empty
-      expect(project.issues.closed).not_to be_empty
-    end
-  end
-
-  describe 'reopen issues' do
-    let(:issues) { create_list(:closed_issue, 2, project: project) }
-
-    it 'succeeds and returns the correct number of issues updated' do
-      result = bulk_update(issues, state_event: 'reopen')
-
-      expect(result[:success]).to be_truthy
-      expect(result[:count]).to eq(issues.count)
-    end
-
-    it 'reopens all the issues passed' do
-      bulk_update(issues, state_event: 'reopen')
-
-      expect(project.issues.closed).to be_empty
-      expect(project.issues.opened).not_to be_empty
-    end
-  end
-
-  describe 'updating assignee' do
-    let(:issue) { create(:issue, project: project, assignee: user) }
-
-    context 'when the new assignee ID is a valid user' do
-      it 'succeeds' do
-        result = bulk_update(issue, assignee_id: create(:user).id)
-
-        expect(result[:success]).to be_truthy
-        expect(result[:count]).to eq(1)
-      end
-
-      it 'updates the assignee to the use ID passed' do
-        assignee = create(:user)
-
-        expect { bulk_update(issue, assignee_id: assignee.id) }
-          .to change { issue.reload.assignee }.from(user).to(assignee)
-      end
-    end
-
-    context 'when the new assignee ID is -1' do
-      it 'unassigns the issues' do
-        expect { bulk_update(issue, assignee_id: -1) }
-          .to change { issue.reload.assignee }.to(nil)
-      end
-    end
-
-    context 'when the new assignee ID is not present' do
-      it 'does not unassign' do
-        expect { bulk_update(issue, assignee_id: nil) }
-          .not_to change { issue.reload.assignee }
-      end
-    end
-  end
-
-  describe 'updating milestones' do
-    let(:issue)     { create(:issue, project: project) }
-    let(:milestone) { create(:milestone, project: project) }
-
-    it 'succeeds' do
-      result = bulk_update(issue, milestone_id: milestone.id)
-
-      expect(result[:success]).to be_truthy
-      expect(result[:count]).to eq(1)
-    end
-
-    it 'updates the issue milestone' do
-      expect { bulk_update(issue, milestone_id: milestone.id) }
-        .to change { issue.reload.milestone }.from(nil).to(milestone)
-    end
-  end
-
-  describe 'updating labels' do
-    def create_issue_with_labels(labels)
-      create(:labeled_issue, project: project, labels: labels)
-    end
-
-    let(:bug) { create(:label, project: project) }
-    let(:regression) { create(:label, project: project) }
-    let(:merge_requests) { create(:label, project: project) }
-
-    let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
-    let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
-    let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
-    let(:issue_no_labels) { create(:issue, project: project) }
-    let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
-
-    let(:labels) { [] }
-    let(:add_labels) { [] }
-    let(:remove_labels) { [] }
-
-    let(:bulk_update_params) do
-      {
-        label_ids:        labels.map(&:id),
-        add_label_ids:    add_labels.map(&:id),
-        remove_label_ids: remove_labels.map(&:id),
-      }
-    end
-
-    before do
-      bulk_update(issues, bulk_update_params)
-    end
-
-    context 'when label_ids are passed' do
-      let(:issues) { [issue_all_labels, issue_no_labels] }
-      let(:labels) { [bug, regression] }
-
-      it 'updates the labels of all issues passed to the labels passed' do
-        expect(issues.map(&:reload).map(&:label_ids)).to all(eq(labels.map(&:id)))
-      end
-
-      it 'does not update issues not passed in' do
-        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
-      end
-
-      context 'when those label IDs are empty' do
-        let(:labels) { [] }
-
-        it 'updates the issues passed to have no labels' do
-          expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
-        end
-      end
-    end
-
-    context 'when add_label_ids are passed' do
-      let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
-      let(:add_labels) { [bug, regression, merge_requests] }
-
-      it 'adds those label IDs to all issues passed' do
-        expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
-      end
-
-      it 'does not update issues not passed in' do
-        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
-      end
-    end
-
-    context 'when remove_label_ids are passed' do
-      let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
-      let(:remove_labels) { [bug, regression, merge_requests] }
-
-      it 'removes those label IDs from all issues passed' do
-        expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
-      end
-
-      it 'does not update issues not passed in' do
-        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
-      end
-    end
-
-    context 'when add_label_ids and remove_label_ids are passed' do
-      let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
-      let(:add_labels) { [bug] }
-      let(:remove_labels) { [merge_requests] }
-
-      it 'adds the label IDs to all issues passed' do
-        expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
-      end
-
-      it 'removes the label IDs from all issues passed' do
-        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
-      end
-
-      it 'does not update issues not passed in' do
-        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
-      end
-    end
-
-    context 'when add_label_ids and label_ids are passed' do
-      let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
-      let(:labels) { [merge_requests] }
-      let(:add_labels) { [regression] }
-
-      it 'adds the label IDs to all issues passed' do
-        expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
-      end
-
-      it 'ignores the label IDs parameter' do
-        expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
-      end
-
-      it 'does not update issues not passed in' do
-        expect(issue_no_labels.label_ids).to be_empty
-      end
-    end
-
-    context 'when remove_label_ids and label_ids are passed' do
-      let(:issues) { [issue_no_labels, issue_bug_and_regression] }
-      let(:labels) { [merge_requests] }
-      let(:remove_labels) { [regression] }
-
-      it 'removes the label IDs from all issues passed' do
-        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
-      end
-
-      it 'ignores the label IDs parameter' do
-        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
-      end
-
-      it 'does not update issues not passed in' do
-        expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
-      end
-    end
-
-    context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
-      let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
-      let(:labels) { [regression] }
-      let(:add_labels) { [bug] }
-      let(:remove_labels) { [merge_requests] }
-
-      it 'adds the label IDs to all issues passed' do
-        expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
-      end
-
-      it 'removes the label IDs from all issues passed' do
-        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
-      end
-
-      it 'ignores the label IDs parameter' do
-        expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
-      end
-
-      it 'does not update issues not passed in' do
-        expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
-      end
-    end
-  end
-
-  describe 'subscribe to issues' do
-    let(:issues) { create_list(:issue, 2, project: project) }
-
-    it 'subscribes the given user' do
-      bulk_update(issues, subscription_event: 'subscribe')
-
-      expect(issues).to all(be_subscribed(user))
-    end
-  end
-
-  describe 'unsubscribe from issues' do
-    let(:issues) do
-      create_list(:closed_issue, 2, project: project) do |issue|
-        issue.subscriptions.create(user: user, subscribed: true)
-      end
-    end
-
-    it 'unsubscribes the given user' do
-      bulk_update(issues, subscription_event: 'unsubscribe')
-
-      issues.each do |issue|
-        expect(issue).not_to be_subscribed(user)
-      end
-    end
-  end
-end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index aff022a..5dfb33f 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -18,12 +18,12 @@ describe Issues::CloseService, services: true do
     context "valid params" do
       before do
         perform_enqueued_jobs do
-          @issue = described_class.new(project, user, {}).execute(issue)
+          described_class.new(project, user).execute(issue)
         end
       end
 
-      it { expect(@issue).to be_valid }
-      it { expect(@issue).to be_closed }
+      it { expect(issue).to be_valid }
+      it { expect(issue).to be_closed }
 
       it 'sends email to user2 about assign of new issue' do
         email = ActionMailer::Base.deliveries.last
@@ -32,7 +32,7 @@ describe Issues::CloseService, services: true do
       end
 
       it 'creates system note about issue reassign' do
-        note = @issue.notes.last
+        note = issue.notes.last
         expect(note.note).to include "Status changed to closed"
       end
 
@@ -44,23 +44,43 @@ describe Issues::CloseService, services: true do
     context 'current user is not authorized to close issue' do
       before do
         perform_enqueued_jobs do
-          @issue = described_class.new(project, guest).execute(issue)
+          described_class.new(project, guest).execute(issue)
         end
       end
 
       it 'does not close the issue' do
-        expect(@issue).to be_open
+        expect(issue).to be_open
       end
     end
 
-    context "external issue tracker" do
+    context 'when issue is not confidential' do
+      it 'executes issue hooks' do
+        expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
+        expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
+
+        described_class.new(project, user).execute(issue)
+      end
+    end
+
+    context 'when issue is confidential' do
+      it 'executes confidential issue hooks' do
+        issue = create(:issue, :confidential, project: project)
+
+        expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
+        expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
+
+        described_class.new(project, user).execute(issue)
+      end
+    end
+
+    context 'external issue tracker' do
       before do
         allow(project).to receive(:default_issues_tracker?).and_return(false)
-        @issue = described_class.new(project, user, {}).execute(issue)
+        described_class.new(project, user).execute(issue)
       end
 
-      it { expect(@issue).to be_valid }
-      it { expect(@issue).to be_opened }
+      it { expect(issue).to be_valid }
+      it { expect(issue).to be_opened }
       it { expect(todo.reload).to be_pending }
     end
   end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index fcc3c0a..58569ba 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -72,6 +72,24 @@ describe Issues::CreateService, services: true do
           expect(issue.milestone).not_to eq milestone
         end
       end
+
+      it 'executes issue hooks when issue is not confidential' do
+        opts = { title: 'Title', description: 'Description', confidential: false }
+
+        expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
+        expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
+
+        described_class.new(project, user, opts).execute
+      end
+
+      it 'executes confidential issue hooks when issue is confidential' do
+        opts = { title: 'Title', description: 'Description', confidential: true }
+
+        expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
+        expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
+
+        described_class.new(project, user, opts).execute
+      end
     end
 
     it_behaves_like 'new issuable record that supports slash commands'
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index 34a89fc..93a8270 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -1,24 +1,50 @@
 require 'spec_helper'
 
 describe Issues::ReopenService, services: true do
-  let(:guest) { create(:user) }
-  let(:issue) { create(:issue, :closed) }
-  let(:project) { issue.project }
-
-  before do
-    project.team << [guest, :guest]
-  end
+  let(:project) { create(:empty_project) }
+  let(:issue) { create(:issue, :closed, project: project) }
 
   describe '#execute' do
-    context 'current user is not authorized to reopen issue' do
+    context 'when user is not authorized to reopen issue' do
       before do
+        guest = create(:user)
+        project.team << [guest, :guest]
+
         perform_enqueued_jobs do
-          @issue = described_class.new(project, guest).execute(issue)
+          described_class.new(project, guest).execute(issue)
         end
       end
 
       it 'does not reopen the issue' do
-        expect(@issue).to be_closed
+        expect(issue).to be_closed
+      end
+    end
+
+    context 'when user is authrized to reopen issue' do
+      let(:user) { create(:user) }
+
+      before do
+        project.team << [user, :master]
+      end
+
+      context 'when issue is not confidential' do
+        it 'executes issue hooks' do
+          expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
+          expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
+
+          described_class.new(project, user).execute(issue)
+        end
+      end
+
+      context 'when issue is confidential' do
+        it 'executes confidential issue hooks' do
+          issue = create(:issue, :confidential, :closed, project: project)
+
+          expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
+          expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
+
+          described_class.new(project, user).execute(issue)
+        end
       end
     end
   end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 0313f42..4f5375a 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -23,11 +23,15 @@ describe Issues::UpdateService, services: true do
 
   describe 'execute' do
     def find_note(starting_with)
-      @issue.notes.find do |note|
+      issue.notes.find do |note|
         note && note.note.start_with?(starting_with)
       end
     end
 
+    def update_issue(opts)
+      described_class.new(project, user, opts).execute(issue)
+    end
+
     context "valid params" do
       before do
         opts = {
@@ -35,23 +39,20 @@ describe Issues::UpdateService, services: true do
           description: 'Also please fix',
           assignee_id: user2.id,
           state_event: 'close',
-          label_ids: [label.id],
-          confidential: true
+          label_ids: [label.id]
         }
 
         perform_enqueued_jobs do
-          @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+          update_issue(opts)
         end
-
-        @issue.reload
       end
 
-      it { expect(@issue).to be_valid }
-      it { expect(@issue.title).to eq('New title') }
-      it { expect(@issue.assignee).to eq(user2) }
-      it { expect(@issue).to be_closed }
-      it { expect(@issue.labels.count).to eq(1) }
-      it { expect(@issue.labels.first.title).to eq(label.name) }
+      it { expect(issue).to be_valid }
+      it { expect(issue.title).to eq('New title') }
+      it { expect(issue.assignee).to eq(user2) }
+      it { expect(issue).to be_closed }
+      it { expect(issue.labels.count).to eq(1) }
+      it { expect(issue.labels.first.title).to eq(label.name) }
 
       it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
         deliveries = ActionMailer::Base.deliveries
@@ -81,18 +82,35 @@ describe Issues::UpdateService, services: true do
         expect(note).not_to be_nil
         expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
       end
+    end
+
+    context 'when issue turns confidential' do
+      let(:opts) do
+        {
+          title: 'New title',
+          description: 'Also please fix',
+          assignee_id: user2.id,
+          state_event: 'close',
+          label_ids: [label.id],
+          confidential: true
+        }
+      end
 
       it 'creates system note about confidentiality change' do
+        update_issue(confidential: true)
+
         note = find_note('Made the issue confidential')
 
         expect(note).not_to be_nil
         expect(note.note).to eq 'Made the issue confidential'
       end
-    end
 
-    def update_issue(opts)
-      @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
-      @issue.reload
+      it 'executes confidential issue hooks' do
+        expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
+        expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
+
+        update_issue(confidential: true)
+      end
     end
 
     context 'todos' do
@@ -100,7 +118,7 @@ describe Issues::UpdateService, services: true do
 
       context 'when the title change' do
         before do
-          update_issue({ title: 'New title' })
+          update_issue(title: 'New title')
         end
 
         it 'marks pending todos as done' do
@@ -110,7 +128,7 @@ describe Issues::UpdateService, services: true do
 
       context 'when the description change' do
         before do
-          update_issue({ description: 'Also please fix' })
+          update_issue(description: 'Also please fix')
         end
 
         it 'marks todos as done' do
@@ -120,7 +138,7 @@ describe Issues::UpdateService, services: true do
 
       context 'when is reassigned' do
         before do
-          update_issue({ assignee: user2 })
+          update_issue(assignee: user2)
         end
 
         it 'marks previous assignee todos as done' do
@@ -144,7 +162,7 @@ describe Issues::UpdateService, services: true do
 
       context 'when the milestone change' do
         before do
-          update_issue({ milestone: create(:milestone) })
+          update_issue(milestone: create(:milestone))
         end
 
         it 'marks todos as done' do
@@ -154,7 +172,7 @@ describe Issues::UpdateService, services: true do
 
       context 'when the labels change' do
         before do
-          update_issue({ label_ids: [label.id] })
+          update_issue(label_ids: [label.id])
         end
 
         it 'marks todos as done' do
@@ -165,6 +183,7 @@ describe Issues::UpdateService, services: true do
 
     context 'when the issue is relabeled' do
       let!(:non_subscriber) { create(:user) }
+
       let!(:subscriber) do
         create(:user).tap do |u|
           label.toggle_subscription(u)
@@ -176,7 +195,7 @@ describe Issues::UpdateService, services: true do
         opts = { label_ids: [label.id] }
 
         perform_enqueued_jobs do
-          @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+          @issue = described_class.new(project, user, opts).execute(issue)
         end
 
         should_email(subscriber)
@@ -190,7 +209,7 @@ describe Issues::UpdateService, services: true do
           opts = { label_ids: [label.id, label2.id] }
 
           perform_enqueued_jobs do
-            @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+            @issue = described_class.new(project, user, opts).execute(issue)
           end
 
           should_not_email(subscriber)
@@ -201,7 +220,7 @@ describe Issues::UpdateService, services: true do
           opts = { label_ids: [label2.id] }
 
           perform_enqueued_jobs do
-            @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+            @issue = described_class.new(project, user, opts).execute(issue)
           end
 
           should_not_email(subscriber)
@@ -210,13 +229,15 @@ describe Issues::UpdateService, services: true do
       end
     end
 
-    context 'when Issue has tasks' do
-      before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
+    context 'when issue has tasks' do
+      before do
+        update_issue(description: "- [ ] Task 1\n- [ ] Task 2")
+      end
 
-      it { expect(@issue.tasks?).to eq(true) }
+      it { expect(issue.tasks?).to eq(true) }
 
       context 'when tasks are marked as completed' do
-        before { update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) }
+        before { update_issue(description: "- [x] Task 1\n- [X] Task 2") }
 
         it 'creates system note about task status change' do
           note1 = find_note('Marked the task **Task 1** as completed')
@@ -229,8 +250,8 @@ describe Issues::UpdateService, services: true do
 
       context 'when tasks are marked as incomplete' do
         before do
-          update_issue({ description: "- [x] Task 1\n- [X] Task 2" })
-          update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" })
+          update_issue(description: "- [x] Task 1\n- [X] Task 2")
+          update_issue(description: "- [ ] Task 1\n- [ ] Task 2")
         end
 
         it 'creates system note about task status change' do
@@ -244,8 +265,8 @@ describe Issues::UpdateService, services: true do
 
       context 'when tasks position has been modified' do
         before do
-          update_issue({ description: "- [x] Task 1\n- [X] Task 2" })
-          update_issue({ description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2" })
+          update_issue(description: "- [x] Task 1\n- [X] Task 2")
+          update_issue(description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2")
         end
 
         it 'does not create a system note' do
@@ -257,8 +278,8 @@ describe Issues::UpdateService, services: true do
 
       context 'when a Task list with a completed item is totally replaced' do
         before do
-          update_issue({ description: "- [ ] Task 1\n- [X] Task 2" })
-          update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" })
+          update_issue(description: "- [ ] Task 1\n- [X] Task 2")
+          update_issue(description: "- [ ] One\n- [ ] Two\n- [ ] Three")
         end
 
         it 'does not create a system note referencing the position the old item' do
@@ -269,7 +290,7 @@ describe Issues::UpdateService, services: true do
 
         it 'does not generate a new note at all' do
           expect do
-            update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" })
+            update_issue(description: "- [ ] One\n- [ ] Two\n- [ ] Three")
           end.not_to change { Note.count }
         end
       end
@@ -277,7 +298,7 @@ describe Issues::UpdateService, services: true do
 
     context 'updating labels' do
       let(:label3) { create(:label, project: project) }
-      let(:result) { Issues::UpdateService.new(project, user, params).execute(issue).reload }
+      let(:result) { described_class.new(project, user, params).execute(issue).reload }
 
       context 'when add_label_ids and label_ids are passed' do
         let(:params) { { label_ids: [label.id], add_label_ids: [label3.id] } }
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 232508c..0d586e2 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -99,14 +99,14 @@ describe MergeRequests::BuildService, services: true do
         let(:source_branch) { "#{issue.iid}-fix-issue" }
 
         it 'appends "Closes #$issue-iid" to the description' do
-          expect(merge_request.description).to eq("#{commit_1.safe_message.split(/\n+/, 2).last}\nCloses ##{issue.iid}")
+          expect(merge_request.description).to eq("#{commit_1.safe_message.split(/\n+/, 2).last}\n\nCloses ##{issue.iid}")
         end
 
         context 'merge request already has a description set' do
           let(:description) { 'Merge request description' }
 
           it 'appends "Closes #$issue-iid" to the description' do
-            expect(merge_request.description).to eq("#{description}\nCloses ##{issue.iid}")
+            expect(merge_request.description).to eq("#{description}\n\nCloses ##{issue.iid}")
           end
         end
 
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index c1e4f8b..b814288 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -83,5 +83,34 @@ describe MergeRequests::CreateService, services: true do
         }
       end
     end
+
+    context 'while saving references to issues that the created merge request closes' do
+      let(:first_issue) { create(:issue, project: project) }
+      let(:second_issue) { create(:issue, project: project) }
+
+      let(:opts) do
+        {
+          title: 'Awesome merge_request',
+          source_branch: 'feature',
+          target_branch: 'master',
+          force_remove_source_branch: '1'
+        }
+      end
+
+      before do
+        project.team << [user, :master]
+        project.team << [assignee, :developer]
+      end
+
+      it 'creates a `MergeRequestsClosingIssues` record for each issue' do
+        issue_closing_opts = opts.merge(description: "Closes #{first_issue.to_reference} and #{second_issue.to_reference}")
+        service = described_class.new(project, user, issue_closing_opts)
+        allow(service).to receive(:execute_hooks)
+        merge_request = service.execute
+
+        issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
+        expect(issue_ids).to match_array([first_issue.id, second_issue.id])
+      end
+    end
   end
 end
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 8a4b763..3a71776 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -50,7 +50,7 @@ describe MergeRequests::GetUrlsService do
       let(:changes) { new_branch_changes }
 
       before do
-        project.merge_requests_enabled = false
+        project.project_feature.update_attribute(:merge_requests_access_level, ProjectFeature::DISABLED)
       end
 
       it_behaves_like 'no_merge_request_url'
diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
index c4b8746..807f89e 100644
--- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
+++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
@@ -6,7 +6,7 @@ describe MergeRequests::MergeRequestDiffCacheService do
   describe '#execute' do
     it 'retrieves the diff files to cache the highlighted result' do
       merge_request = create(:merge_request)
-      cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequest.default_options]
+      cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequestDiff.default_options]
 
       expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
       expect(Rails.cache).to receive(:write).with(cache_key, anything)
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index fff8648..a162df5 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -174,6 +174,58 @@ describe MergeRequests::RefreshService, services: true do
       end
     end
 
+    context 'merge request metrics' do
+      let(:issue) { create :issue, project: @project }
+      let(:commit_author) { create :user }
+      let(:commit) { project.commit }
+
+      before do
+        project.team << [commit_author, :developer]
+        project.team << [user, :developer]
+
+        allow(commit).to receive_messages(
+          safe_message: "Closes #{issue.to_reference}",
+          references: [issue],
+          author_name: commit_author.name,
+          author_email: commit_author.email,
+          committed_date: Time.now
+        )
+
+        allow_any_instance_of(MergeRequest).to receive(:commits).and_return([commit])
+      end
+
+      context 'when the merge request is sourced from the same project' do
+        it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do
+          merge_request = create(:merge_request, target_branch: 'master', source_branch: 'feature', source_project: @project)
+          refresh_service = service.new(@project, @user)
+          allow(refresh_service).to receive(:execute_hooks)
+          refresh_service.execute(@oldrev, @newrev, 'refs/heads/feature')
+
+          issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
+          expect(issue_ids).to eq([issue.id])
+        end
+      end
+
+      context 'when the merge request is sourced from a different project' do
+        it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do
+          forked_project = create(:project)
+          create(:forked_project_link, forked_to_project: forked_project, forked_from_project: @project)
+
+          merge_request = create(:merge_request,
+                                 target_branch: 'master',
+                                 source_branch: 'feature',
+                                 target_project: @project,
+                                 source_project: forked_project)
+          refresh_service = service.new(@project, @user)
+          allow(refresh_service).to receive(:execute_hooks)
+          refresh_service.execute(@oldrev, @newrev, 'refs/heads/feature')
+
+          issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
+          expect(issue_ids).to eq([issue.id])
+        end
+      end
+    end
+
     def reload_mrs
       @merge_request.reload
       @fork_merge_request.reload
diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb
new file mode 100644
index 0000000..d719324
--- /dev/null
+++ b/spec/services/merge_requests/resolve_service_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe MergeRequests::ResolveService do
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+
+  let(:fork_project) do
+    create(:forked_project_with_submodules) do |fork_project|
+      fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+      fork_project.save
+    end
+  end
+
+  let(:merge_request) do
+    create(:merge_request,
+           source_branch: 'conflict-resolvable', source_project: project,
+           target_branch: 'conflict-start')
+  end
+
+  let(:merge_request_from_fork) do
+    create(:merge_request,
+           source_branch: 'conflict-resolvable-fork', source_project: fork_project,
+           target_branch: 'conflict-start', target_project: project)
+  end
+
+  describe '#execute' do
+    context 'with valid params' do
+      let(:params) do
+        {
+          sections: {
+            '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head',
+            '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+            '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+            '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+          },
+          commit_message: 'This is a commit message!'
+        }
+      end
+
+      context 'when the source and target project are the same' do
+        before do
+          MergeRequests::ResolveService.new(project, user, params).execute(merge_request)
+        end
+
+        it 'creates a commit with the message' do
+          expect(merge_request.source_branch_head.message).to eq(params[:commit_message])
+        end
+
+        it 'creates a commit with the correct parents' do
+          expect(merge_request.source_branch_head.parents.map(&:id)).
+            to eq(['1450cd639e0bc6721eb02800169e464f212cde06',
+                   '75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b'])
+        end
+      end
+
+      context 'when the source project is a fork and does not contain the HEAD of the target branch' do
+        let!(:target_head) do
+          project.repository.commit_file(user, 'new-file-in-target', '', 'Add new file in target', 'conflict-start', false)
+        end
+
+        before do
+          MergeRequests::ResolveService.new(fork_project, user, params).execute(merge_request_from_fork)
+        end
+
+        it 'creates a commit with the message' do
+          expect(merge_request_from_fork.source_branch_head.message).to eq(params[:commit_message])
+        end
+
+        it 'creates a commit with the correct parents' do
+          expect(merge_request_from_fork.source_branch_head.parents.map(&:id)).
+            to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813',
+                   target_head])
+        end
+      end
+    end
+
+    context 'when a resolution is missing' do
+      let(:invalid_params) { { sections: { '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' } } }
+      let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
+
+      it 'raises a MissingResolution error' do
+        expect { service.execute(merge_request) }.
+          to raise_error(Gitlab::Conflict::File::MissingResolution)
+      end
+    end
+  end
+end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 6dfeb58..33db34c 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -263,5 +263,42 @@ describe MergeRequests::UpdateService, services: true do
         end
       end
     end
+
+    context 'while saving references to issues that the updated merge request closes' do
+      let(:first_issue) { create(:issue, project: project) }
+      let(:second_issue) { create(:issue, project: project) }
+
+      it 'creates a `MergeRequestsClosingIssues` record for each issue' do
+        issue_closing_opts = { description: "Closes #{first_issue.to_reference} and #{second_issue.to_reference}" }
+        service = described_class.new(project, user, issue_closing_opts)
+        allow(service).to receive(:execute_hooks)
+        service.execute(merge_request)
+
+        issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
+        expect(issue_ids).to match_array([first_issue.id, second_issue.id])
+      end
+
+      it 'removes `MergeRequestsClosingIssues` records when issues are not closed anymore' do
+        opts = {
+          title: 'Awesome merge_request',
+          description: "Closes #{first_issue.to_reference} and #{second_issue.to_reference}",
+          source_branch: 'feature',
+          target_branch: 'master',
+          force_remove_source_branch: '1'
+        }
+
+        merge_request = MergeRequests::CreateService.new(project, user, opts).execute
+
+        issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
+        expect(issue_ids).to match_array([first_issue.id, second_issue.id])
+
+        service = described_class.new(project, user, description: "not closing any issues")
+        allow(service).to receive(:execute_hooks)
+        service.execute(merge_request.reload)
+
+        issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
+        expect(issue_ids).to be_empty
+      end
+    end
   end
 end
diff --git a/spec/services/notes/slash_commands_service_spec.rb b/spec/services/notes/slash_commands_service_spec.rb
index 4f231aa..d109988 100644
--- a/spec/services/notes/slash_commands_service_spec.rb
+++ b/spec/services/notes/slash_commands_service_spec.rb
@@ -122,6 +122,75 @@ describe Notes::SlashCommandsService, services: true do
     end
   end
 
+  describe '.noteable_update_service' do
+    include_context 'note on noteable'
+
+    it 'returns Issues::UpdateService for a note on an issue' do
+      note = create(:note_on_issue, project: project)
+
+      expect(described_class.noteable_update_service(note)).to eq(Issues::UpdateService)
+    end
+
+    it 'returns Issues::UpdateService for a note on a merge request' do
+      note = create(:note_on_merge_request, project: project)
+
+      expect(described_class.noteable_update_service(note)).to eq(MergeRequests::UpdateService)
+    end
+
+    it 'returns nil for a note on a commit' do
+      note = create(:note_on_commit, project: project)
+
+      expect(described_class.noteable_update_service(note)).to be_nil
+    end
+  end
+
+  describe '.supported?' do
+    include_context 'note on noteable'
+
+    let(:note) { create(:note_on_issue, project: project) }
+
+    context 'with no current_user' do
+      it 'returns false' do
+        expect(described_class.supported?(note, nil)).to be_falsy
+      end
+    end
+
+    context 'when current_user cannot update the noteable' do
+      it 'returns false' do
+        user = create(:user)
+
+        expect(described_class.supported?(note, user)).to be_falsy
+      end
+    end
+
+    context 'when current_user can update the noteable' do
+      it 'returns true' do
+        expect(described_class.supported?(note, master)).to be_truthy
+      end
+
+      context 'with a note on a commit' do
+        let(:note) { create(:note_on_commit, project: project) }
+
+        it 'returns false' do
+          expect(described_class.supported?(note, nil)).to be_falsy
+        end
+      end
+    end
+  end
+
+  describe '#supported?' do
+    include_context 'note on noteable'
+
+    it 'delegates to the class method' do
+      service = described_class.new(project, master)
+      note = create(:note_on_issue, project: project)
+
+      expect(described_class).to receive(:supported?).with(note, master)
+
+      service.supported?(note)
+    end
+  end
+
   describe '#execute' do
     let(:service) { described_class.new(project, master) }
 
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index f81a588..0d15253 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -379,6 +379,7 @@ describe NotificationService, services: true do
       it "emails subscribers of the issue's labels" do
         subscriber = create(:user)
         label = create(:label, issues: [issue])
+        issue.reload
         label.toggle_subscription(subscriber)
         notification.new_issue(issue, @u_disabled)
 
@@ -399,6 +400,7 @@ describe NotificationService, services: true do
           project.team << [guest, :guest]
 
           label = create(:label, issues: [confidential_issue])
+          confidential_issue.reload
           label.toggle_subscription(non_member)
           label.toggle_subscription(author)
           label.toggle_subscription(assignee)
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index bbced59..3ea1273 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -69,7 +69,7 @@ describe Projects::CreateService, services: true do
 
       context 'wiki_enabled false does not create wiki repository directory' do
         before do
-          @opts.merge!(wiki_enabled: false)
+          @opts.merge!( { project_feature_attributes: { wiki_access_level: ProjectFeature::DISABLED } })
           @project = create_project(@user, @opts)
           @path = ProjectWiki.new(@project, @user).send(:path_to_repo)
         end
@@ -85,7 +85,7 @@ describe Projects::CreateService, services: true do
 
       context 'global builds_enabled false does not enable CI by default' do
         before do
-          @opts.merge!(builds_enabled: false)
+          project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
         end
 
         it { is_expected.to be_falsey }
@@ -93,7 +93,7 @@ describe Projects::CreateService, services: true do
 
       context 'global builds_enabled true does enable CI by default' do
         before do
-          @opts.merge!(builds_enabled: true)
+          project.project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
         end
 
         it { is_expected.to be_truthy }
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index ad0d586..cf90b33 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -4,12 +4,15 @@ describe Projects::HousekeepingService do
   subject { Projects::HousekeepingService.new(project) }
   let(:project) { create :project }
 
-  describe 'execute' do
-    before do
-      project.pushes_since_gc = 3
-      project.save!
-    end
+  before do
+    project.reset_pushes_since_gc
+  end
+
+  after do
+    project.reset_pushes_since_gc
+  end
 
+  describe '#execute' do
     it 'enqueues a sidekiq job' do
       expect(subject).to receive(:try_obtain_lease).and_return(true)
       expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id)
@@ -32,12 +35,12 @@ describe Projects::HousekeepingService do
       it 'does not reset pushes_since_gc' do
         expect do
           expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken)
-        end.not_to change { project.pushes_since_gc }.from(3)
+        end.not_to change { project.pushes_since_gc }
       end
     end
   end
 
-  describe 'needed?' do
+  describe '#needed?' do
     it 'when the count is low enough' do
       expect(subject.needed?).to eq(false)
     end
@@ -48,25 +51,11 @@ describe Projects::HousekeepingService do
     end
   end
 
-  describe 'increment!' do
-    let(:lease_key) { "project_housekeeping:increment!:#{project.id}" }
-
+  describe '#increment!' do
     it 'increments the pushes_since_gc counter' do
-      lease = double(:lease, try_obtain: true)
-      expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease)
-
       expect do
         subject.increment!
       end.to change { project.pushes_since_gc }.from(0).to(1)
     end
-
-    it 'does not increment when no lease can be obtained' do
-      lease = double(:lease, try_obtain: false)
-      expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease)
-
-      expect do
-        subject.increment!
-      end.not_to change { project.pushes_since_gc }
-    end
   end
 end
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
new file mode 100644
index 0000000..7d4eff3
--- /dev/null
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe ProtectedBranches::CreateService, services: true do
+  let(:project) { create(:empty_project) }
+  let(:user) { project.owner }
+  let(:params) do
+    {
+      name: 'master',
+      merge_access_levels_attributes: [ { access_level: Gitlab::Access::MASTER } ],
+      push_access_levels_attributes: [ { access_level: Gitlab::Access::MASTER } ]
+    }
+  end
+
+  describe '#execute' do
+    subject(:service) { described_class.new(project, user, params) }
+
+    it 'creates a new protected branch' do
+      expect { service.execute }.to change(ProtectedBranch, :count).by(1)
+      expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
+      expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
+    end
+  end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 00427d6..3d854a9 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -330,13 +330,13 @@ describe SystemNoteService, services: true do
             let(:mentioner) { project2.repository.commit }
 
             it 'references the mentioning commit' do
-              expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference(project)}"
+              expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference(project)}"
             end
           end
 
           context 'from non-Commit' do
             it 'references the mentioning object' do
-              expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference(project)}"
+              expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference(project)}"
             end
           end
         end
@@ -346,13 +346,13 @@ describe SystemNoteService, services: true do
             let(:mentioner) { project.repository.commit }
 
             it 'references the mentioning commit' do
-              expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference}"
+              expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference}"
             end
           end
 
           context 'from non-Commit' do
             it 'references the mentioning object' do
-              expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference}"
+              expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference}"
             end
           end
         end
@@ -362,7 +362,7 @@ describe SystemNoteService, services: true do
 
   describe '.cross_reference?' do
     it 'is truthy when text begins with expected text' do
-      expect(described_class.cross_reference?('mentioned in something')).to be_truthy
+      expect(described_class.cross_reference?('Mentioned in something')).to be_truthy
     end
 
     it 'is falsey when text does not begin with expected text' do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 296fd1b..b41f6f1 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -145,6 +145,14 @@ describe TodoService, services: true do
       end
     end
 
+    describe '#destroy_issue' do
+      it 'refresh the todos count cache for the user' do
+        expect(john_doe).to receive(:update_todos_count_cache).and_call_original
+
+        service.destroy_issue(issue, john_doe)
+      end
+    end
+
     describe '#reassigned_issue' do
       it 'creates a pending todo for new assignee' do
         unassigned_issue.update_attribute(:assignee, john_doe)
@@ -394,6 +402,14 @@ describe TodoService, services: true do
       end
     end
 
+    describe '#destroy_merge_request' do
+      it 'refresh the todos count cache for the user' do
+        expect(john_doe).to receive(:update_todos_count_cache).and_call_original
+
+        service.destroy_merge_request(mr_assigned, john_doe)
+      end
+    end
+
     describe '#reassigned_merge_request' do
       it 'creates a pending todo for new assignee' do
         mr_unassigned.update_attribute(:assignee, john_doe)
@@ -496,6 +512,7 @@ describe TodoService, services: true do
 
   describe '#mark_todos_as_done' do
     let(:issue) { create(:issue, project: project, author: author, assignee: john_doe) }
+    let(:another_issue) { create(:issue, project: project, author: author, assignee: john_doe) }
 
     it 'marks a relation of todos as done' do
       create(:todo, :mentioned, user: john_doe, target: issue, project: project)
@@ -518,6 +535,26 @@ describe TodoService, services: true do
       expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq(1)
     end
 
+    context 'when some of the todos are done already' do
+      before do
+        create(:todo, :mentioned, user: john_doe, target: issue, project: project)
+        create(:todo, :mentioned, user: john_doe, target: another_issue, project: project)
+      end
+
+      it 'returns the number of those still pending' do
+        TodoService.new.mark_pending_todos_as_done(issue, john_doe)
+
+        expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(1)
+      end
+
+      it 'returns 0 if all are done' do
+        TodoService.new.mark_pending_todos_as_done(issue, john_doe)
+        TodoService.new.mark_pending_todos_as_done(another_issue, john_doe)
+
+        expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(0)
+      end
+    end
+
     it 'caches the number of todos of a user', :caching do
       create(:todo, :mentioned, user: john_doe, target: issue, project: project)
       todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index c144cd8..02b2b3c 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -26,9 +26,9 @@ RSpec.configure do |config|
   config.verbose_retry = true
   config.display_try_failure_messages = true
 
-  config.include Devise::TestHelpers, type: :controller
-  config.include LoginHelpers,        type: :feature
-  config.include LoginHelpers,        type: :request
+  config.include Devise::TestHelpers,   type: :controller
+  config.include Warden::Test::Helpers, type: :request
+  config.include LoginHelpers,          type: :feature
   config.include StubConfiguration
   config.include EmailHelpers
   config.include TestEnv
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
new file mode 100644
index 0000000..e8e760a
--- /dev/null
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -0,0 +1,64 @@
+module CycleAnalyticsHelpers
+  def create_commit_referencing_issue(issue, branch_name: random_git_name)
+    project.repository.add_branch(user, branch_name, 'master')
+    create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
+  end
+
+  def create_commit(message, project, user, branch_name)
+    filename = random_git_name
+    oldrev = project.repository.commit(branch_name).sha
+
+    options = {
+      committer: project.repository.user_to_committer(user),
+      author: project.repository.user_to_committer(user),
+      commit: { message: message, branch: branch_name, update_ref: true },
+      file: { content: "content", path: filename, update: false }
+    }
+
+    commit_sha = Gitlab::Git::Blob.commit(project.repository, options)
+    project.repository.commit(commit_sha)
+
+    GitPushService.new(project,
+                       user,
+                       oldrev: oldrev,
+                       newrev: commit_sha,
+                       ref: 'refs/heads/master').execute
+  end
+
+  def create_merge_request_closing_issue(issue, message: nil, source_branch: nil)
+    if !source_branch || project.repository.commit(source_branch).blank?
+      source_branch = random_git_name
+      project.repository.add_branch(user, source_branch, 'master')
+    end
+
+    sha = project.repository.commit_file(user, random_git_name, "content", "commit message", source_branch, false)
+    project.repository.commit(sha)
+
+    opts = {
+      title: 'Awesome merge_request',
+      description: message || "Fixes #{issue.to_reference}",
+      source_branch: source_branch,
+      target_branch: 'master'
+    }
+
+    MergeRequests::CreateService.new(project, user, opts).execute
+  end
+
+  def merge_merge_requests_closing_issue(issue)
+    merge_requests = issue.closed_by_merge_requests
+    merge_requests.each { |merge_request| MergeRequests::MergeService.new(project, user).execute(merge_request) }
+  end
+
+  def deploy_master(environment: 'production')
+    CreateDeploymentService.new(project, user, {
+                                  environment: environment,
+                                  ref: 'master',
+                                  tag: false,
+                                  sha: project.repository.commit('master').sha
+                                }).execute
+  end
+end
+
+RSpec.configure do |config|
+  config.include CycleAnalyticsHelpers
+end
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
new file mode 100644
index 0000000..8e19a6c
--- /dev/null
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -0,0 +1,161 @@
+# rubocop:disable Metrics/AbcSize
+
+# Note: The ABC size is large here because we have a method generating test cases with
+#       multiple nested contexts. This shouldn't count as a violation.
+
+module CycleAnalyticsHelpers
+  module TestGeneration
+    # Generate the most common set of specs that all cycle analytics phases need to have.
+    #
+    # Arguments:
+    #
+    #                  phase: Which phase are we testing? Will call `CycleAnalytics.new.send(phase)` for the final assertion
+    #                data_fn: A function that returns a hash, constituting initial data for the test case
+    #  start_time_conditions: An array of `conditions`. Each condition is an tuple of `condition_name` and `condition_fn`. `condition_fn` is called with
+    #                         `context` (no lexical scope, so need to do `context.create` for factories, for example) and `data` (from the `data_fn`).
+    #                         Each `condition_fn` is expected to implement a case which consitutes the start of the given cycle analytics phase.
+    #    end_time_conditions: An array of `conditions`. Each condition is an tuple of `condition_name` and `condition_fn`. `condition_fn` is called with
+    #                         `context` (no lexical scope, so need to do `context.create` for factories, for example) and `data` (from the `data_fn`).
+    #                         Each `condition_fn` is expected to implement a case which consitutes the end of the given cycle analytics phase.
+    #          before_end_fn: This function is run before calling the end time conditions. Used for setup that needs to be run between the start and end conditions.
+    #                post_fn: Code that needs to be run after running the end time conditions.
+
+    def generate_cycle_analytics_spec(phase:, data_fn:, start_time_conditions:, end_time_conditions:, before_end_fn: nil, post_fn: nil)
+      combinations_of_start_time_conditions = (1..start_time_conditions.size).flat_map { |size| start_time_conditions.combination(size).to_a }
+      combinations_of_end_time_conditions = (1..end_time_conditions.size).flat_map { |size| end_time_conditions.combination(size).to_a }
+
+      scenarios = combinations_of_start_time_conditions.product(combinations_of_end_time_conditions)
+      scenarios.each do |start_time_conditions, end_time_conditions|
+        context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do
+          context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do
+            it "finds the median of available durations between the two conditions" do
+              time_differences = Array.new(5) do |index|
+                data = data_fn[self]
+                start_time = (index * 10).days.from_now
+                end_time = start_time + rand(1..5).days
+
+                start_time_conditions.each do |condition_name, condition_fn|
+                  Timecop.freeze(start_time) { condition_fn[self, data] }
+                end
+
+                # Run `before_end_fn` at the midpoint between `start_time` and `end_time`
+                Timecop.freeze(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn
+
+                end_time_conditions.each do |condition_name, condition_fn|
+                  Timecop.freeze(end_time) { condition_fn[self, data] }
+                end
+
+                Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+
+                end_time - start_time
+              end
+
+              median_time_difference = time_differences.sort[2]
+              expect(subject.send(phase)).to be_within(5).of(median_time_difference)
+            end
+
+            context "when the data belongs to another project" do
+              let(:other_project) { create(:project) }
+
+              it "returns nil" do
+                # Use a stub to "trick" the data/condition functions
+                # into using another project. This saves us from having to
+                # define separate data/condition functions for this particular
+                # test case.
+                allow(self).to receive(:project) { other_project }
+
+                5.times do
+                  data = data_fn[self]
+                  start_time = Time.now
+                  end_time = rand(1..10).days.from_now
+
+                  start_time_conditions.each do |condition_name, condition_fn|
+                    Timecop.freeze(start_time) { condition_fn[self, data] }
+                  end
+
+                  end_time_conditions.each do |condition_name, condition_fn|
+                    Timecop.freeze(end_time) { condition_fn[self, data] }
+                  end
+
+                  Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+                end
+
+                # Turn off the stub before checking assertions
+                allow(self).to receive(:project).and_call_original
+
+                expect(subject.send(phase)).to be_nil
+              end
+            end
+
+            context "when the end condition happens before the start condition" do
+              it 'returns nil' do
+                data = data_fn[self]
+                start_time = Time.now
+                end_time = start_time + rand(1..5).days
+
+                # Run `before_end_fn` at the midpoint between `start_time` and `end_time`
+                Timecop.freeze(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn
+
+                end_time_conditions.each do |condition_name, condition_fn|
+                  Timecop.freeze(start_time) { condition_fn[self, data] }
+                end
+
+                start_time_conditions.each do |condition_name, condition_fn|
+                  Timecop.freeze(end_time) { condition_fn[self, data] }
+                end
+
+                Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+
+                expect(subject.send(phase)).to be_nil
+              end
+            end
+          end
+        end
+
+        context "start condition NOT PRESENT: #{start_time_conditions.map(&:first).to_sentence}" do
+          context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do
+            it "returns nil" do
+              5.times do
+                data = data_fn[self]
+                end_time = rand(1..10).days.from_now
+
+                end_time_conditions.each_with_index do |(condition_name, condition_fn), index|
+                  Timecop.freeze(end_time + index.days) { condition_fn[self, data] }
+                end
+
+                Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+              end
+
+              expect(subject.send(phase)).to be_nil
+            end
+          end
+        end
+
+        context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do
+          context "end condition NOT PRESENT: #{end_time_conditions.map(&:first).to_sentence}" do
+            it "returns nil" do
+              5.times do
+                data = data_fn[self]
+                start_time = Time.now
+
+                start_time_conditions.each do |condition_name, condition_fn|
+                  Timecop.freeze(start_time) { condition_fn[self, data] }
+                end
+
+                post_fn[self, data] if post_fn
+              end
+
+              expect(subject.send(phase)).to be_nil
+            end
+          end
+        end
+      end
+
+      context "when none of the start / end conditions are matched" do
+        it "returns nil" do
+          expect(subject.send(phase)).to be_nil
+        end
+      end
+    end
+  end
+end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index e0dbc9a..ac38e31 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -15,7 +15,7 @@ RSpec.configure do |config|
     DatabaseCleaner.start
   end
 
-  config.after(:each) do
+  config.append_after(:each) do
     DatabaseCleaner.clean
   end
 end
diff --git a/spec/support/git_helpers.rb b/spec/support/git_helpers.rb
new file mode 100644
index 0000000..9342239
--- /dev/null
+++ b/spec/support/git_helpers.rb
@@ -0,0 +1,9 @@
+module GitHelpers
+  def random_git_name
+    "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
+  end
+end
+
+RSpec.configure do |config|
+  config.include GitHelpers
+end
diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb
new file mode 100644
index 0000000..f752508
--- /dev/null
+++ b/spec/support/import_export/configuration_helper.rb
@@ -0,0 +1,29 @@
+module ConfigurationHelper
+  # Returns a list of models from hashes/arrays contained in +project_tree+
+  def names_from_tree(project_tree)
+    project_tree.map do |branch_or_model|
+      branch_or_model =  branch_or_model.to_s if branch_or_model.is_a?(Symbol)
+
+      branch_or_model.is_a?(String) ? branch_or_model : names_from_tree(branch_or_model)
+    end
+  end
+
+  def relation_class_for_name(relation_name)
+    relation_name = Gitlab::ImportExport::RelationFactory::OVERRIDES[relation_name.to_sym] || relation_name
+    relation_name.to_s.classify.constantize
+  end
+
+  def parsed_attributes(relation_name, attributes)
+    excluded_attributes = config_hash['excluded_attributes'][relation_name]
+    included_attributes = config_hash['included_attributes'][relation_name]
+
+    attributes = attributes - JSON[excluded_attributes.to_json] if excluded_attributes
+    attributes = attributes & JSON[included_attributes.to_json] if included_attributes
+
+    attributes
+  end
+
+  def associations_for(safe_model)
+    safe_model.reflect_on_all_associations.map { |assoc| assoc.name.to_s }
+  end
+end
diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb
new file mode 100644
index 0000000..be0772d
--- /dev/null
+++ b/spec/support/import_export/export_file_helper.rb
@@ -0,0 +1,133 @@
+require './spec/support/import_export/configuration_helper'
+
+module ExportFileHelper
+  include ConfigurationHelper
+
+  ObjectWithParent = Struct.new(:object, :parent, :key_found)
+
+  def setup_project
+    project = create(:project, :public)
+
+    create(:release, project: project)
+
+    issue = create(:issue, assignee: user, project: project)
+    snippet = create(:project_snippet, project: project)
+    label = create(:label, project: project)
+    milestone = create(:milestone, project: project)
+    merge_request = create(:merge_request, source_project: project, milestone: milestone)
+    commit_status = create(:commit_status, project: project)
+
+    create(:label_link, label: label, target: issue)
+
+    ci_pipeline = create(:ci_pipeline,
+                         project: project,
+                         sha: merge_request.diff_head_sha,
+                         ref: merge_request.source_branch,
+                         statuses: [commit_status])
+
+    create(:ci_build, pipeline: ci_pipeline, project: project)
+    create(:milestone, project: project)
+    create(:note, noteable: issue, project: project)
+    create(:note, noteable: merge_request, project: project)
+    create(:note, noteable: snippet, project: project)
+    create(:note_on_commit,
+           author: user,
+           project: project,
+           commit_id: ci_pipeline.sha)
+
+    create(:event, target: milestone, project: project, action: Event::CREATED, author: user)
+    create(:project_member, :master, user: user, project: project)
+    create(:ci_variable, project: project)
+    create(:ci_trigger, project: project)
+    key = create(:deploy_key)
+    key.projects << project
+    create(:service, project: project)
+    create(:project_hook, project: project, token: 'token')
+    create(:protected_branch, project: project)
+
+    project
+  end
+
+  # Expands the compressed file for an exported project into +tmpdir+
+  def in_directory_with_expanded_export(project)
+    Dir.mktmpdir do |tmpdir|
+      export_file = project.export_project_path
+      _output, exit_status = Gitlab::Popen.popen(%W{tar -zxf #{export_file} -C #{tmpdir}})
+
+      yield(exit_status, tmpdir)
+    end
+  end
+
+  # Recursively finds key/values including +key+ as part of the key, inside a nested hash
+  def deep_find_with_parent(sensitive_key_word, object, found = nil)
+    sensitive_key_found = object_contains_key?(object, sensitive_key_word)
+
+    # Returns the parent object and the object found containing a sensitive word as part of the key
+    if sensitive_key_found && object[sensitive_key_found]
+      ObjectWithParent.new(object[sensitive_key_found], object, sensitive_key_found)
+    elsif object.is_a?(Enumerable)
+      # Recursively lookup for keys containing sensitive words in a Hash or Array
+      object_with_parent = nil
+
+      object.find do |*hash_or_array|
+        object_with_parent = deep_find_with_parent(sensitive_key_word, hash_or_array.last, found)
+      end
+
+      object_with_parent
+    end
+  end
+
+  # Return true if the hash has a key containing a sensitive word
+  def object_contains_key?(object, sensitive_key_word)
+    return false unless object.is_a?(Hash)
+
+    object.keys.find { |key| key.include?(sensitive_key_word) }
+  end
+
+  # Returns the offended ObjectWithParent object if a sensitive word is found inside a hash,
+  # excluding the whitelisted safe hashes.
+  def find_sensitive_attributes(sensitive_word, project_hash)
+    loop do
+      object_with_parent = deep_find_with_parent(sensitive_word, project_hash)
+
+      return nil unless object_with_parent && object_with_parent.object
+
+      if is_safe_hash?(object_with_parent.parent, sensitive_word)
+        # It's in the safe list, remove hash and keep looking
+        object_with_parent.parent.delete(object_with_parent.key_found)
+      else
+        return object_with_parent
+      end
+
+      nil
+    end
+  end
+
+  # Returns true if it's one of the excluded models in +safe_list+
+  def is_safe_hash?(parent, sensitive_word)
+    return false unless parent && safe_list[sensitive_word.to_sym]
+
+    # Extra attributes that appear in a model but not in the exported hash.
+    excluded_attributes = ['type']
+
+    safe_list[sensitive_word.to_sym].each do |model|
+      # Check whether this is a hash attribute inside a model
+      if model.is_a?(Symbol)
+        return true if (safe_hashes[model] - parent.keys).empty?
+      else
+        return true if safe_model?(model, excluded_attributes, parent)
+      end
+    end
+
+    false
+  end
+
+  # Compares model attributes with those those found in the hash
+  # and returns true if there is a match, ignoring some excluded attributes.
+  def safe_model?(model, excluded_attributes, parent)
+    excluded_attributes += associations_for(model)
+    parsed_model_attributes = parsed_attributes(model.name.underscore, model.attribute_names)
+
+    (parsed_model_attributes - parent.keys - excluded_attributes).empty?
+  end
+end
diff --git a/spec/support/issuable_slash_commands_shared_examples.rb b/spec/support/issuable_slash_commands_shared_examples.rb
index d2a49ea..5e3b8f2 100644
--- a/spec/support/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/issuable_slash_commands_shared_examples.rb
@@ -2,6 +2,9 @@
 # It takes a `issuable_type`, and expect an `issuable`.
 
 shared_examples 'issuable record that supports slash commands in its description and notes' do |issuable_type|
+  include SlashCommandsHelpers
+  include WaitForAjax
+
   let(:master) { create(:user) }
   let(:assignee) { create(:user, username: 'bob') }
   let(:guest) { create(:user) }
@@ -18,6 +21,11 @@ shared_examples 'issuable record that supports slash commands in its description
     login_with(master)
   end
 
+  after do
+    # Ensure all outstanding Ajax requests are complete to avoid database deadlocks
+    wait_for_ajax
+  end
+
   describe "new #{issuable_type}" do
     context 'with commands in the description' do
       it "creates the #{issuable_type} and interpret commands accordingly" do
@@ -44,10 +52,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
     context 'with a note containing commands' do
       it 'creates a note without the commands and interpret the commands accordingly' do
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\""
-          click_button 'Comment'
-        end
+        write_note("Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
 
         expect(page).to have_content 'Awesome!'
         expect(page).not_to have_content '/assign @bob'
@@ -66,10 +71,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
     context 'with a note containing only commands' do
       it 'does not create a note but interpret the commands accordingly' do
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/assign @bob\n/label ~bug\n/milestone %\"ASAP\""
-          click_button 'Comment'
-        end
+        write_note("/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
 
         expect(page).not_to have_content '/assign @bob'
         expect(page).not_to have_content '/label ~bug'
@@ -92,10 +94,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
       context "when current user can close #{issuable_type}" do
         it "closes the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/close"
-            click_button 'Comment'
-          end
+          write_note("/close")
 
           expect(page).not_to have_content '/close'
           expect(page).to have_content 'Your commands have been executed!'
@@ -112,10 +111,7 @@ shared_examples 'issuable record that supports slash commands in its description
         end
 
         it "does not close the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/close"
-            click_button 'Comment'
-          end
+          write_note("/close")
 
           expect(page).not_to have_content '/close'
           expect(page).not_to have_content 'Your commands have been executed!'
@@ -133,10 +129,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
       context "when current user can reopen #{issuable_type}" do
         it "reopens the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/reopen"
-            click_button 'Comment'
-          end
+          write_note("/reopen")
 
           expect(page).not_to have_content '/reopen'
           expect(page).to have_content 'Your commands have been executed!'
@@ -153,10 +146,7 @@ shared_examples 'issuable record that supports slash commands in its description
         end
 
         it "does not reopen the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/reopen"
-            click_button 'Comment'
-          end
+          write_note("/reopen")
 
           expect(page).not_to have_content '/reopen'
           expect(page).not_to have_content 'Your commands have been executed!'
@@ -169,10 +159,7 @@ shared_examples 'issuable record that supports slash commands in its description
     context "with a note changing the #{issuable_type}'s title" do
       context "when current user can change title of #{issuable_type}" do
         it "reopens the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/title Awesome new title"
-            click_button 'Comment'
-          end
+          write_note("/title Awesome new title")
 
           expect(page).not_to have_content '/title'
           expect(page).to have_content 'Your commands have been executed!'
@@ -189,10 +176,7 @@ shared_examples 'issuable record that supports slash commands in its description
         end
 
         it "does not reopen the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/title Awesome new title"
-            click_button 'Comment'
-          end
+          write_note("/title Awesome new title")
 
           expect(page).not_to have_content '/title'
           expect(page).not_to have_content 'Your commands have been executed!'
@@ -204,10 +188,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
     context "with a note marking the #{issuable_type} as todo" do
       it "creates a new todo for the #{issuable_type}" do
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/todo"
-          click_button 'Comment'
-        end
+        write_note("/todo")
 
         expect(page).not_to have_content '/todo'
         expect(page).to have_content 'Your commands have been executed!'
@@ -238,10 +219,7 @@ shared_examples 'issuable record that supports slash commands in its description
         expect(todo.author).to eq master
         expect(todo.user).to eq master
 
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/done"
-          click_button 'Comment'
-        end
+        write_note("/done")
 
         expect(page).not_to have_content '/done'
         expect(page).to have_content 'Your commands have been executed!'
@@ -254,10 +232,7 @@ shared_examples 'issuable record that supports slash commands in its description
       it "creates a new todo for the #{issuable_type}" do
         expect(issuable.subscribed?(master)).to be_falsy
 
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/subscribe"
-          click_button 'Comment'
-        end
+        write_note("/subscribe")
 
         expect(page).not_to have_content '/subscribe'
         expect(page).to have_content 'Your commands have been executed!'
@@ -274,10 +249,7 @@ shared_examples 'issuable record that supports slash commands in its description
       it "creates a new todo for the #{issuable_type}" do
         expect(issuable.subscribed?(master)).to be_truthy
 
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/unsubscribe"
-          click_button 'Comment'
-        end
+        write_note("/unsubscribe")
 
         expect(page).not_to have_content '/unsubscribe'
         expect(page).to have_content 'Your commands have been executed!'
diff --git a/spec/support/ldap_helpers.rb b/spec/support/ldap_helpers.rb
new file mode 100644
index 0000000..079f244
--- /dev/null
+++ b/spec/support/ldap_helpers.rb
@@ -0,0 +1,47 @@
+module LdapHelpers
+  def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap))
+    ::Gitlab::LDAP::Adapter.new(provider, ldap)
+  end
+
+  def user_dn(uid)
+    "uid=#{uid},ou=users,dc=example,dc=com"
+  end
+
+  # Accepts a hash of Gitlab::LDAP::Config keys and values.
+  #
+  # Example:
+  #   stub_ldap_config(
+  #     group_base: 'ou=groups,dc=example,dc=com',
+  #     admin_group: 'my-admin-group'
+  #   )
+  def stub_ldap_config(messages)
+    messages.each do |config, value|
+      allow_any_instance_of(::Gitlab::LDAP::Config)
+        .to receive(config.to_sym).and_return(value)
+    end
+  end
+
+  # Stub an LDAP person search and provide the return entry. Specify `nil` for
+  # `entry` to simulate when an LDAP person is not found
+  #
+  # Example:
+  #  adapter = ::Gitlab::LDAP::Adapter.new('ldapmain', double(:ldap))
+  #  ldap_user_entry = ldap_user_entry('john_doe')
+  #
+  #  stub_ldap_person_find_by_uid('john_doe', ldap_user_entry, adapter)
+  def stub_ldap_person_find_by_uid(uid, entry, provider = 'ldapmain')
+    return_value = ::Gitlab::LDAP::Person.new(entry, provider) if entry.present?
+
+    allow(::Gitlab::LDAP::Person)
+      .to receive(:find_by_uid).with(uid, any_args).and_return(return_value)
+  end
+
+  # Create a simple LDAP user entry.
+  def ldap_user_entry(uid)
+    entry = Net::LDAP::Entry.new
+    entry['dn'] = user_dn(uid)
+    entry['uid'] = uid
+
+    entry
+  end
+end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index e5f76af..c0b3e83 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -75,6 +75,7 @@ module LoginHelpers
   def logout
     find(".header-user-dropdown-toggle").click
     click_link "Sign out"
+    expect(page).to have_content('Signed out successfully')
   end
 
   # Logout without JavaScript driver
diff --git a/spec/support/slash_commands_helpers.rb b/spec/support/slash_commands_helpers.rb
new file mode 100644
index 0000000..df483af
--- /dev/null
+++ b/spec/support/slash_commands_helpers.rb
@@ -0,0 +1,10 @@
+module SlashCommandsHelpers
+  def write_note(text)
+    Sidekiq::Testing.fake! do
+      page.within('.js-main-target-form') do
+        fill_in 'note[note]', with: text
+        click_button 'Comment'
+      end
+    end
+  end
+end
diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb
index 927c72c..201614e 100644
--- a/spec/support/taskable_shared_examples.rb
+++ b/spec/support/taskable_shared_examples.rb
@@ -3,30 +3,57 @@
 # Requires a context containing:
 #   subject { Issue or MergeRequest }
 shared_examples 'a Taskable' do
-  before do
-    subject.description = <<-EOT.strip_heredoc
-      * [ ] Task 1
-      * [x] Task 2
-      * [x] Task 3
-      * [ ] Task 4
-      * [ ] Task 5
-    EOT
+  describe 'with multiple tasks' do
+    before do
+      subject.description = <<-EOT.strip_heredoc
+        * [ ] Task 1
+        * [x] Task 2
+        * [x] Task 3
+        * [ ] Task 4
+        * [ ] Task 5
+      EOT
+    end
+
+    it 'returns the correct task status' do
+      expect(subject.task_status).to match('2 of')
+      expect(subject.task_status).to match('5 tasks completed')
+    end
+
+    describe '#tasks?' do
+      it 'returns true when object has tasks' do
+        expect(subject.tasks?).to eq true
+      end
+
+      it 'returns false when object has no tasks' do
+        subject.description = 'Now I have no tasks'
+        expect(subject.tasks?).to eq false
+      end
+    end
   end
 
-  it 'returns the correct task status' do
-    expect(subject.task_status).to match('5 tasks')
-    expect(subject.task_status).to match('2 completed')
-    expect(subject.task_status).to match('3 remaining')
+  describe 'with an incomplete task' do
+    before do
+      subject.description = <<-EOT.strip_heredoc
+        * [ ] Task 1
+      EOT
+    end
+
+    it 'returns the correct task status' do
+      expect(subject.task_status).to match('0 of')
+      expect(subject.task_status).to match('1 task completed')
+    end
   end
 
-  describe '#tasks?' do
-    it 'returns true when object has tasks' do
-      expect(subject.tasks?).to eq true
+  describe 'with a complete task' do
+    before do
+      subject.description = <<-EOT.strip_heredoc
+        * [x] Task 1
+      EOT
     end
 
-    it 'returns false when object has no tasks' do
-      subject.description = 'Now I have no tasks'
-      expect(subject.tasks?).to eq false
+    it 'returns the correct task status' do
+      expect(subject.task_status).to match('1 of')
+      expect(subject.task_status).to match('1 task completed')
     end
   end
 end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index c7a45fc..0097dbf 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -6,7 +6,7 @@ module TestEnv
   # When developing the seed repository, comment out the branch you will modify.
   BRANCH_SHA = {
     'empty-branch'                       => '7efb185',
-    'ends-with.json'                     => '98b0d8b3',
+    'ends-with.json'                     => '98b0d8b',
     'flatten-dir'                        => 'e56497b',
     'feature'                            => '0b4bc9a',
     'feature_conflict'                   => 'bb5206f',
@@ -37,9 +37,10 @@ module TestEnv
   # need to keep all the branches in sync.
   # We currently only need a subset of the branches
   FORKED_BRANCH_SHA = {
-    'add-submodule-version-bump' => '3f547c08',
-    'master' => '5937ac0',
-    'remove-submodule' => '2a33e0c0'
+    'add-submodule-version-bump' => '3f547c0',
+    'master'                     => '5937ac0',
+    'remove-submodule'           => '2a33e0c',
+    'conflict-resolvable-fork'   => '404fa3f'
   }
 
   # Test environment
@@ -117,22 +118,7 @@ module TestEnv
       system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
     end
 
-    Dir.chdir(repo_path) do
-      branch_sha.each do |branch, sha|
-        # Try to reset without fetching to avoid using the network.
-        reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
-        unless system(*reset)
-          if system(*%W(#{Gitlab.config.git.bin_path} 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
+    set_repo_refs(repo_path, branch_sha)
 
     # We must copy bare repositories because we will push to them.
     system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare}))
@@ -144,6 +130,7 @@ module TestEnv
     FileUtils.mkdir_p(target_repo_path)
     FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
     FileUtils.chmod_R 0755, target_repo_path
+    set_repo_refs(target_repo_path, BRANCH_SHA)
   end
 
   def repos_path
@@ -160,6 +147,7 @@ module TestEnv
     FileUtils.mkdir_p(target_repo_path)
     FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
     FileUtils.chmod_R 0755, target_repo_path
+    set_repo_refs(target_repo_path, FORKED_BRANCH_SHA)
   end
 
   # When no cached assets exist, manually hit the root path to create them
@@ -209,4 +197,23 @@ module TestEnv
   def git_env
     { 'GIT_TEMPLATE_DIR' => '' }
   end
+
+  def set_repo_refs(repo_path, branch_sha)
+    Dir.chdir(repo_path) do
+      branch_sha.each do |branch, sha|
+        # Try to reset without fetching to avoid using the network.
+        reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
+        unless system(*reset)
+          if system(*%W(#{Gitlab.config.git.bin_path} 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
+  end
 end
diff --git a/spec/support/wait_for_vue_resource.rb b/spec/support/wait_for_vue_resource.rb
new file mode 100644
index 0000000..1029f84
--- /dev/null
+++ b/spec/support/wait_for_vue_resource.rb
@@ -0,0 +1,7 @@
+module WaitForVueResource
+  def wait_for_vue_resource(spinner: true)
+    Timeout.timeout(Capybara.default_max_wait_time) do
+      loop until page.evaluate_script('Vue.activeResources').zero?
+    end
+  end
+end
diff --git a/spec/support/workhorse_helpers.rb b/spec/support/workhorse_helpers.rb
index 107b6e3..47673cd 100644
--- a/spec/support/workhorse_helpers.rb
+++ b/spec/support/workhorse_helpers.rb
@@ -13,4 +13,9 @@ module WorkhorseHelpers
       ]
     end
   end
+
+  def workhorse_internal_api_request_header
+    jwt_token = JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256')
+    { 'HTTP_' + Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER.upcase.tr('-', '_') => jwt_token }
+  end
 end
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb
index 4640510..446ba3b 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -59,14 +59,10 @@ describe 'projects/builds/show' do
     end
 
     it 'shows trigger variables in separate lines' do
-      expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_1', 'TRIGGER_VALUE_1'))
-      expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_2', 'TRIGGER_VALUE_2'))
+      expect(rendered).to have_css('.js-build-variable', visible: false, text: 'TRIGGER_KEY_1')
+      expect(rendered).to have_css('.js-build-variable', visible: false, text: 'TRIGGER_KEY_2')
+      expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_1')
+      expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_2')
     end
   end
-
-  private
-
-  def variable_regexp(key, value)
-    /\A#{Regexp.escape("#{key}=#{value}")}\Z/
-  end
 end
diff --git a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
index 733b2df..21f49d3 100644
--- a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
@@ -15,6 +15,8 @@ describe 'projects/merge_requests/widget/_heading' do
       assign(:merge_request, merge_request)
       assign(:project, project)
 
+      allow(view).to receive(:can?).and_return(true)
+
       render
     end
 
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
new file mode 100644
index 0000000..31bbb15
--- /dev/null
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe 'projects/merge_requests/edit.html.haml' do
+  include Devise::TestHelpers
+
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+  let(:fork_project) { create(:project, forked_from_project: project) }
+  let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
+
+  let(:closed_merge_request) do
+    create(:closed_merge_request,
+      source_project: fork_project,
+      target_project: project,
+      author: user)
+  end
+
+  before do
+    assign(:project, project)
+    assign(:merge_request, closed_merge_request)
+
+    allow(view).to receive(:can?).and_return(true)
+    allow(view).to receive(:current_user)
+      .and_return(User.find(closed_merge_request.author_id))
+  end
+
+  context 'when a merge request without fork' do
+    it "shows editable fields" do
+      unlink_project.execute
+      closed_merge_request.reload
+
+      render
+
+      expect(rendered).to have_field('merge_request[title]')
+      expect(rendered).to have_field('merge_request[description]')
+      expect(rendered).to have_selector('#merge_request_assignee_id', visible: false)
+      expect(rendered).to have_selector('#merge_request_milestone_id', visible: false)
+      expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false)
+    end
+  end
+
+  context 'when a merge request with an existing source project is closed' do
+    it "shows editable fields" do
+      render
+
+      expect(rendered).to have_field('merge_request[title]')
+      expect(rendered).to have_field('merge_request[description]')
+      expect(rendered).to have_selector('#merge_request_assignee_id', visible: false)
+      expect(rendered).to have_selector('#merge_request_milestone_id', visible: false)
+      expect(rendered).to have_selector('#merge_request_target_branch', visible: false)
+    end
+  end
+end
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
new file mode 100644
index 0000000..fe0780e
--- /dev/null
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe 'projects/merge_requests/show.html.haml' do
+  include Devise::TestHelpers
+
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+  let(:fork_project) { create(:project, forked_from_project: project) }
+  let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
+
+  let(:closed_merge_request) do
+    create(:closed_merge_request,
+      source_project: fork_project,
+      target_project: project,
+      author: user)
+  end
+
+  before do
+    assign(:project, project)
+    assign(:merge_request, closed_merge_request)
+    assign(:commits_count, 0)
+
+    allow(view).to receive(:can?).and_return(true)
+  end
+
+  context 'when the merge request is closed' do
+    it 'shows the "Reopen" button' do
+      render
+
+      expect(rendered).to have_css('a', visible: true, text: 'Reopen')
+      expect(rendered).to have_css('a', visible: false, text: 'Close')
+    end
+
+    it 'does not show the "Reopen" button when the source project does not exist' do
+      unlink_project.execute
+      closed_merge_request.reload
+
+      render
+
+      expect(rendered).to have_css('a', visible: false, text: 'Reopen')
+      expect(rendered).to have_css('a', visible: false, text: 'Close')
+    end
+  end
+end
diff --git a/spec/views/projects/notes/_form.html.haml_spec.rb b/spec/views/projects/notes/_form.html.haml_spec.rb
new file mode 100644
index 0000000..932d675
--- /dev/null
+++ b/spec/views/projects/notes/_form.html.haml_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe 'projects/notes/_form' do
+  include Devise::TestHelpers
+
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project) }
+
+  before do
+    project.team << [user, :master]
+    assign(:project, project)
+    assign(:note, note)
+
+    allow(view).to receive(:current_user).and_return(user)
+
+    render
+  end
+
+  %w[issue merge_request].each do |noteable|
+    context "with a note on #{noteable}" do
+      let(:note) { build(:"note_on_#{noteable}", project: project) }
+
+      it 'says that only markdown is supported, not slash commands' do
+        expect(rendered).to have_content('Styling with Markdown and slash commands are supported')
+      end
+    end
+  end
+
+  context 'with a note on a commit' do
+    let(:note) { build(:note_on_commit, project: project) }
+
+    it 'says that only markdown is supported, not slash commands' do
+      expect(rendered).to have_content('Styling with Markdown is supported')
+    end
+  end
+end
diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb
new file mode 100644
index 0000000..ac7f3ff
--- /dev/null
+++ b/spec/views/projects/pipelines/show.html.haml_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe 'projects/pipelines/show' do
+  include Devise::TestHelpers
+
+  let(:project) { create(:project) }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
+
+  before do
+    controller.prepend_view_path('app/views/projects')
+
+    create_build('build', 0, 'build', :success)
+    create_build('test', 1, 'rspec 0:2', :pending)
+    create_build('test', 1, 'rspec 1:2', :running)
+    create_build('test', 1, 'spinach 0:2', :created)
+    create_build('test', 1, 'spinach 1:2', :created)
+    create_build('test', 1, 'audit', :created)
+    create_build('deploy', 2, 'production', :created)
+
+    create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3)
+
+    assign(:project, project)
+    assign(:pipeline, pipeline)
+
+    allow(view).to receive(:can?).and_return(true)
+  end
+
+  it 'shows a graph with grouped stages' do
+    render
+
+    expect(rendered).to have_css('.pipeline-graph')
+    expect(rendered).to have_css('.grouped-pipeline-dropdown')
+
+    # stages
+    expect(rendered).to have_text('Build')
+    expect(rendered).to have_text('Test')
+    expect(rendered).to have_text('Deploy')
+    expect(rendered).to have_text('External')
+
+    # builds
+    expect(rendered).to have_text('rspec')
+    expect(rendered).to have_text('spinach')
+    expect(rendered).to have_text('rspec 0:2')
+    expect(rendered).to have_text('production')
+    expect(rendered).to have_text('jenkins')
+  end
+
+  private
+
+  def create_build(stage, stage_idx, name, status)
+    create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name, status: status)
+  end
+end
diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb
new file mode 100644
index 0000000..35e1518
--- /dev/null
+++ b/spec/workers/prune_old_events_worker_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe PruneOldEventsWorker do
+  describe '#perform' do
+    let!(:expired_event) { create(:event, author_id: 0, created_at: 13.months.ago) }
+    let!(:not_expired_event) { create(:event, author_id: 0,  created_at: 1.day.ago) }
+    let!(:exactly_12_months_event) { create(:event, author_id: 0, created_at: 12.months.ago) }
+
+    it 'prunes events older than 12 months' do
+      expect { subject.perform }.to change { Event.count }.by(-1)
+      expect(Event.find_by(id: expired_event.id)).to be_nil
+    end
+
+    it 'leaves fresh events' do
+      subject.perform
+      expect(not_expired_event.reload).to be_present
+    end
+
+    it 'leaves events from exactly 12 months ago' do
+      subject.perform
+      expect(exactly_12_months_event).to be_present
+    end
+  end
+end
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
index 05e0778..59cfb2c 100644
--- a/spec/workers/repository_check/single_repository_worker_spec.rb
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -5,7 +5,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
   subject { described_class.new }
 
   it 'passes when the project has no push events' do
-    project = create(:project_empty_repo, wiki_enabled: false)
+    project = create(:project_empty_repo, wiki_access_level: ProjectFeature::DISABLED)
     project.events.destroy_all
     break_repo(project)
 
@@ -25,7 +25,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
   end
 
   it 'fails if the wiki repository is broken' do
-    project = create(:project_empty_repo, wiki_enabled: true)
+    project = create(:project_empty_repo, wiki_access_level: ProjectFeature::ENABLED)
     project.create_wiki
 
     # Test sanity: everything should be fine before the wiki repo is broken
@@ -39,7 +39,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
   end
 
   it 'skips wikis when disabled' do
-    project = create(:project_empty_repo, wiki_enabled: false)
+    project = create(:project_empty_repo, wiki_access_level: ProjectFeature::DISABLED)
     # Make sure the test would fail if the wiki repo was checked
     break_wiki(project)
 
@@ -49,7 +49,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
   end
 
   it 'creates missing wikis' do
-    project = create(:project_empty_repo, wiki_enabled: true)
+    project = create(:project_empty_repo, wiki_access_level: ProjectFeature::ENABLED)
     FileUtils.rm_rf(wiki_path(project))
 
     subject.perform(project.id)
diff --git a/vendor/assets/javascripts/Chart.js b/vendor/assets/javascripts/Chart.js
old mode 100755
new mode 100644
diff --git a/vendor/assets/javascripts/autosize.js b/vendor/assets/javascripts/autosize.js
old mode 100755
new mode 100644
diff --git a/vendor/assets/javascripts/jquery.scrollTo.js b/vendor/assets/javascripts/jquery.scrollTo.js
old mode 100755
new mode 100644
diff --git a/vendor/assets/javascripts/task_list.js b/vendor/assets/javascripts/task_list.js
index bc45150..9fbfef0 100644
--- a/vendor/assets/javascripts/task_list.js
+++ b/vendor/assets/javascripts/task_list.js
@@ -1,15 +1,118 @@
-
+// The MIT License (MIT)
+//
+// Copyright (c) 2014 GitHub, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+// TaskList Behavior
+//
 /*= provides tasklist:enabled */
-
-
 /*= provides tasklist:disabled */
-
-
 /*= provides tasklist:change */
-
-
 /*= provides tasklist:changed */
-
+//
+//
+// Enables Task List update behavior.
+//
+// ### Example Markup
+//
+//   <div class="js-task-list-container">
+//     <ul class="task-list">
+//       <li class="task-list-item">
+//         <input type="checkbox" class="js-task-list-item-checkbox" disabled />
+//         text
+//       </li>
+//     </ul>
+//     <form>
+//       <textarea class="js-task-list-field">- [ ] text</textarea>
+//     </form>
+//   </div>
+//
+// ### Specification
+//
+// TaskLists MUST be contained in a `(div).js-task-list-container`.
+//
+// TaskList Items SHOULD be an a list (`UL`/`OL`) element.
+//
+// Task list items MUST match `(input).task-list-item-checkbox` and MUST be
+// `disabled` by default.
+//
+// TaskLists MUST have a `(textarea).js-task-list-field` form element whose
+// `value` attribute is the source (Markdown) to be udpated. The source MUST
+// follow the syntax guidelines.
+//
+// TaskList updates trigger `tasklist:change` events. If the change is
+// successful, `tasklist:changed` is fired. The change can be canceled.
+//
+// jQuery is required.
+//
+// ### Methods
+//
+// `.taskList('enable')` or `.taskList()`
+//
+// Enables TaskList updates for the container.
+//
+// `.taskList('disable')`
+//
+// Disables TaskList updates for the container.
+//
+//# ### Events
+//
+// `tasklist:enabled`
+//
+// Fired when the TaskList is enabled.
+//
+// * **Synchronicity** Sync
+// * **Bubbles** Yes
+// * **Cancelable** No
+// * **Target** `.js-task-list-container`
+//
+// `tasklist:disabled`
+//
+// Fired when the TaskList is disabled.
+//
+// * **Synchronicity** Sync
+// * **Bubbles** Yes
+// * **Cancelable** No
+// * **Target** `.js-task-list-container`
+//
+// `tasklist:change`
+//
+// Fired before the TaskList item change takes affect.
+//
+// * **Synchronicity** Sync
+// * **Bubbles** Yes
+// * **Cancelable** Yes
+// * **Target** `.js-task-list-field`
+//
+// `tasklist:changed`
+//
+// Fired once the TaskList item change has taken affect.
+//
+// * **Synchronicity** Sync
+// * **Bubbles** Yes
+// * **Cancelable** No
+// * **Target** `.js-task-list-field`
+//
+// ### NOTE
+//
+// Task list checkboxes are rendered as disabled by default because rendered
+// user content is cached without regard for the viewer.
 (function() {
   var codeFencesPattern, complete, completePattern, disableTaskList, disableTaskLists, enableTaskList, enableTaskLists, escapePattern, incomplete, incompletePattern, itemPattern, itemsInParasPattern, updateTaskList, updateTaskListItem,
     indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
@@ -18,20 +121,48 @@
 
   complete = "[x]";
 
+  // Escapes the String for regular expression matching.
   escapePattern = function(str) {
     return str.replace(/([\[\]])/g, "\\$1").replace(/\s/, "\\s").replace("x", "[xX]");
   };
 
-  incompletePattern = RegExp("" + (escapePattern(incomplete)));
-
-  completePattern = RegExp("" + (escapePattern(complete)));
+  incompletePattern = RegExp("" + (escapePattern(incomplete))); // escape square brackets
+ // match all white space
+  completePattern = RegExp("" + (escapePattern(complete))); // match all cases
 
+  // Pattern used to identify all task list items.
+  // Useful when you need iterate over all items.
   itemPattern = RegExp("^(?:\\s*(?:>\\s*)*(?:[-+*]|(?:\\d+\\.)))\\s*(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ")\\s+(?!\\(.*?\\))(?=(?:\\[.*?\\]\\s*(?:\\[.*?\\]|\\(.*?\\))\\s*)*(?:[^\\[]|$))");
 
+  // prefix, consisting of
+  // optional leading whitespace
+  // zero or more blockquotes
+  // list item indicator
+  // optional whitespace prefix
+  // checkbox
+  // is followed by whitespace
+  // is not part of a [foo](url) link
+  // and is followed by zero or more links
+  // and either a non-link or the end of the string
+  // Used to filter out code fences from the source for comparison only.
+  // http://rubular.com/r/x5EwZVrloI
+  // Modified slightly due to issues with JS
   codeFencesPattern = /^`{3}(?:\s*\w+)?[\S\s].*[\S\s]^`{3}$/mg;
 
+  // ```
+  // followed by optional language
+  // whitespace
+  // code
+  // whitespace
+  // ```
+  // Used to filter out potential mismatches (items not in lists).
+  // http://rubular.com/r/OInl6CiePy
   itemsInParasPattern = RegExp("^(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ").+$", "g");
 
+  // Given the source text, updates the appropriate task list item to match the
+  // given checked value.
+  //
+  // Returns the updated String text.
   updateTaskListItem = function(source, itemIndex, checked) {
     var clean, index, line, result;
     clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').replace(itemsInParasPattern, '').split("\n");
@@ -55,6 +186,9 @@
     return result.join("\n");
   };
 
+  // Updates the $field value to reflect the state of $item.
+  // Triggers the `tasklist:change` event before the value has changed, and fires
+  // a `tasklist:changed` event once the value has changed.
   updateTaskList = function($item) {
     var $container, $field, checked, event, index;
     $container = $item.closest('.js-task-list-container');
@@ -70,10 +204,12 @@
     }
   };
 
+  // When the task list item checkbox is updated, submit the change
   $(document).on('change', '.task-list-item-checkbox', function() {
     return updateTaskList($(this));
   });
 
+  // Enables TaskList item changes.
   enableTaskList = function($container) {
     if ($container.find('.js-task-list-field').length > 0) {
       $container.find('.task-list-item').addClass('enabled').find('.task-list-item-checkbox').attr('disabled', null);
@@ -81,6 +217,7 @@
     }
   };
 
+  // Enables a collection of TaskList containers.
   enableTaskLists = function($containers) {
     var container, i, len, results;
     results = [];
@@ -91,11 +228,13 @@
     return results;
   };
 
+  // Disable TaskList item changes.
   disableTaskList = function($container) {
     $container.find('.task-list-item').removeClass('enabled').find('.task-list-item-checkbox').attr('disabled', 'disabled');
     return $container.removeClass('is-task-list-enabled').trigger('tasklist:disabled');
   };
 
+  // Disables a collection of TaskList containers.
   disableTaskLists = function($containers) {
     var container, i, len, results;
     results = [];
diff --git a/vendor/gitignore/Global/NetBeans.gitignore b/vendor/gitignore/Global/NetBeans.gitignore
index 520d91f..254108c 100644
--- a/vendor/gitignore/Global/NetBeans.gitignore
+++ b/vendor/gitignore/Global/NetBeans.gitignore
@@ -3,5 +3,4 @@ build/
 nbbuild/
 dist/
 nbdist/
-nbactions.xml
 .nb-gradle/
diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/OSX.gitignore
deleted file mode 100644
index 5972fe5..0000000
--- a/vendor/gitignore/Global/OSX.gitignore
+++ /dev/null
@@ -1,25 +0,0 @@
-*.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon

-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
diff --git a/vendor/gitignore/Global/Tags.gitignore b/vendor/gitignore/Global/Tags.gitignore
index c031816..91927af 100644
--- a/vendor/gitignore/Global/Tags.gitignore
+++ b/vendor/gitignore/Global/Tags.gitignore
@@ -9,6 +9,7 @@ gtags.files
 GTAGS
 GRTAGS
 GPATH
+GSYMS
 cscope.files
 cscope.out
 cscope.in.out
diff --git a/vendor/gitignore/Global/macOS.gitignore b/vendor/gitignore/Global/macOS.gitignore
new file mode 100644
index 0000000..828a509
--- /dev/null
+++ b/vendor/gitignore/Global/macOS.gitignore
@@ -0,0 +1,26 @@
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore
index a4ee41a..450f32e 100644
--- a/vendor/gitignore/Haskell.gitignore
+++ b/vendor/gitignore/Haskell.gitignore
@@ -17,3 +17,4 @@ cabal.sandbox.config
 *.eventlog
 .stack-work/
 cabal.project.local
+.HTF/
diff --git a/vendor/gitignore/Joomla.gitignore b/vendor/gitignore/Joomla.gitignore
index 0d7a0de..93103fd 100644
--- a/vendor/gitignore/Joomla.gitignore
+++ b/vendor/gitignore/Joomla.gitignore
@@ -52,6 +52,7 @@
 /administrator/language/en-GB/en-GB.plg_content_contact.sys.ini
 /administrator/language/en-GB/en-GB.plg_content_finder.ini
 /administrator/language/en-GB/en-GB.plg_content_finder.sys.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_module*
 /administrator/language/en-GB/en-GB.plg_finder_categories.ini
 /administrator/language/en-GB/en-GB.plg_finder_categories.sys.ini
 /administrator/language/en-GB/en-GB.plg_finder_contacts.ini
@@ -64,6 +65,10 @@
 /administrator/language/en-GB/en-GB.plg_finder_tags.sys.ini
 /administrator/language/en-GB/en-GB.plg_finder_weblinks.ini
 /administrator/language/en-GB/en-GB.plg_finder_weblinks.sys.ini
+/administrator/language/en-GB/en-GB.plg_installer_folderinstaller*
+/administrator/language/en-GB/en-GB.plg_installer_packageinstaller*
+/administrator/language/en-GB/en-GB.plg_installer_packageinstaller
+/administrator/language/en-GB/en-GB.plg_installer_urlinstaller*
 /administrator/language/en-GB/en-GB.plg_installer_webinstaller.ini
 /administrator/language/en-GB/en-GB.plg_installer_webinstaller.sys.ini
 /administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.ini
@@ -72,6 +77,8 @@
 /administrator/language/en-GB/en-GB.plg_search_tags.sys.ini
 /administrator/language/en-GB/en-GB.plg_system_languagecode.ini
 /administrator/language/en-GB/en-GB.plg_system_languagecode.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_stats*
+/administrator/language/en-GB/en-GB.plg_system_updatenotification*
 /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.ini
 /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.sys.ini
 /administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.ini
@@ -249,8 +256,10 @@
 /administrator/language/en-GB/en-GB.tpl_hathor.sys.ini
 /administrator/language/en-GB/en-GB.xml
 /administrator/language/en-GB/index.html
+/administrator/language/ru-RU/index.html
 /administrator/language/overrides/*
 /administrator/language/index.html
+/administrator/logs/index.html
 /administrator/manifests/*
 /administrator/modules/mod_custom/*
 /administrator/modules/mod_feed/*
@@ -289,6 +298,7 @@
 /components/com_finder/*
 /components/com_mailto/*
 /components/com_media/*
+/components/com_modules/*
 /components/com_newsfeeds/*
 /components/com_search/*
 /components/com_users/*
@@ -407,6 +417,7 @@
 /libraries/idna_convert/*
 /libraries/joomla/*
 /libraries/legacy/*
+/libraries/php-encryption/*
 /libraries/phpass/*
 /libraries/phpmailer/*
 /libraries/phputf8/*
@@ -431,9 +442,11 @@
 /media/media/*
 /media/mod_languages/*
 /media/overrider/*
+/media/plg_captcha_recaptcha/*
 /media/plg_quickicon_extensionupdate/*
 /media/plg_quickicon_joomlaupdate/*
 /media/plg_system_highlight/*
+/media/plg_system_stats/*
 /media/system/*
 /media/index.html
 /modules/mod_articles_archive/*
@@ -486,6 +499,7 @@
 /plugins/editors/none/*
 /plugins/editors/tinymce/*
 /plugins/editors/index.html
+/plugins/editors-xtd/module/*
 /plugins/editors-xtd/article/*
 /plugins/editors-xtd/image/*
 /plugins/editors-xtd/pagebreak/*
@@ -523,6 +537,8 @@
 /plugins/system/redirect/*
 /plugins/system/remember/*
 /plugins/system/sef/*
+/plugins/system/stats/*
+/plugins/system/updatenotification/*
 /plugins/system/index.html
 /plugins/twofactorauth/*
 /plugins/user/contactcreator/*
diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore
index aea5294..bf7525f 100644
--- a/vendor/gitignore/Node.gitignore
+++ b/vendor/gitignore/Node.gitignore
@@ -34,5 +34,8 @@ jspm_packages
 # Optional npm cache directory
 .npm
 
+# Optional eslint cache
+.eslintcache
+
 # Optional REPL history
 .node_repl_history
diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore
index 2059208..58c51ec 100644
--- a/vendor/gitignore/Objective-C.gitignore
+++ b/vendor/gitignore/Objective-C.gitignore
@@ -50,7 +50,9 @@ Carthage/Build
 # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
 
 fastlane/report.xml
+fastlane/Preview.html
 fastlane/screenshots
+fastlane/test_output
 
 # Code Injection
 #
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index 72364f9..37fc9d4 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -79,6 +79,7 @@ celerybeat-schedule
 .env
 
 # virtualenv
+.venv/
 venv/
 ENV/
 
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index d8c256c..e974276 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -12,9 +12,11 @@ capybara-*.html
 rerun.txt
 pickle-email-*.html
 
-# TODO Comment out these rules if you are OK with secrets being uploaded to the repo
+# TODO Comment out this rule if you are OK with secrets being uploaded to the repo
 config/initializers/secret_token.rb
-config/secrets.yml
+
+# Only include if you have production secrets in this file, which is no longer a Rails default
+# config/secrets.yml
 
 # dotenv
 # TODO Comment out this rule if environment variables can be committed
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index 67acbf4..d56f8b5 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -251,3 +251,10 @@ paket-files/
 # JetBrains Rider
 .idea/
 *.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
index 396d3f1..f3fa394 100644
--- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
@@ -1,7 +1,12 @@
 # Official docker image.
 image: docker:latest
 
+services:
+  - docker:dind
+
 build:
   stage: build
   script:
-    - docker build -t test .
+    - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" $CI_REGISTRY
+    - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME" .
+    - docker push "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME"
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
index 166f146..08b57c8 100644
--- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -43,3 +43,12 @@ rails:
   - bundle exec rake db:migrate
   - bundle exec rake db:seed
   - bundle exec rake test
+
+# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk
+# are supported too: https://github.com/travis-ci/dpl
+deploy:
+  type: deploy
+  environment: production
+  script:
+  - gem install dpl
+  - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY
diff --git a/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml
new file mode 100644
index 0000000..c9c3590
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml
@@ -0,0 +1,30 @@
+# Lifted from: https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/
+# This file assumes an own GitLab CI runner, setup on an OS X system.
+stages:
+  - build
+  - archive
+
+build_project:
+  stage: build
+  script:
+    - xcodebuild clean -project ProjectName.xcodeproj -scheme SchemeName | xcpretty
+    - xcodebuild test -project ProjectName.xcodeproj -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.2' | xcpretty -s
+  tags:
+    - ios_9-2
+    - xcode_7-2
+    - osx_10-11
+
+archive_project:
+  stage: archive
+  script:
+    - xcodebuild clean archive -archivePath build/ProjectName -scheme SchemeName
+    - xcodebuild -exportArchive -exportFormat ipa -archivePath "build/ProjectName.xcarchive" -exportPath "build/ProjectName.ipa" -exportProvisioningProfile "ProvisioningProfileName"
+  only:
+    - master
+  artifacts:
+    paths:
+    - build/ProjectName.ipa
+  tags:
+    - ios_9-2
+    - xcode_7-2
+    - osx_10-11

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/gitlab.git



More information about the Pkg-ruby-extras-commits mailing list