diff --git a/CHANGELOG b/CHANGELOG
index 5b84d136d2d8820c51c4d8c90faf2f0bdd133048..3b3259a7a233d9b6f0bd9223d25b8e8da85ce6d6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date.
 
 v 8.9.0 (unreleased)
   - Set import_url validation to be more strict
+  - Fix builds API response not including commit data
   - Fix error when CI job variables key specified but not defined
   - Fix pipeline status when there are no builds in pipeline
   - Fix Error 500 when using closes_issues API with an external issue tracker
@@ -9,17 +10,22 @@ v 8.9.0 (unreleased)
   - Bulk assign/unassign labels to issues.
   - Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
   - Show Star and Fork buttons on mobile.
+  - Performance improvements on RelativeLinkFilter
   - Fix endless redirections when accessing user OAuth applications when they are disabled
   - Allow enabling wiki page events from Webhook management UI
   - Bump rouge to 1.11.0
+  - Fix MR-auto-close text added to description
   - Fix issue with arrow keys not working in search autocomplete dropdown
   - Fix an issue where note polling stopped working if a window was in the
     background during a refresh.
+  - Pre-processing Markdown now only happens when needed
   - Make EmailsOnPushWorker use Sidekiq mailers queue
   - Redesign all Devise emails. !4297
   - Don't show 'Leave Project' to group members
   - Fix wiki page events' webhook to point to the wiki repository
+  - Add a border around images to differentiate them from the background.
   - Don't show tags for revert and cherry-pick operations
+  - Show image ID on registry page
   - Fix issue todo not remove when leave project !4150 (Long Nguyen)
   - Allow customisable text on the 'nearly there' page after a user signs up
   - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
@@ -32,6 +38,7 @@ v 8.9.0 (unreleased)
   - Implement a fair usage of shared runners
   - Remove project notification settings associated with deleted projects
   - Fix 404 page when viewing TODOs that contain milestones or labels in different projects
+  - Wrap code blocks on Activies and Todos page !4783 (winniehell)
   - Add a metric for the number of new Redis connections created by a transaction
   - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark
   - Redesign navigation for project pages
@@ -56,6 +63,7 @@ v 8.9.0 (unreleased)
   - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged
   - Don't allow MRs to be merged when commits were added since the last review / page load
   - Add DB index on users.state
+  - Limit email on push diff size to 30 files / 150 KB
   - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database
   - Changed the Slack build message to use the singular duration if necessary (Aran Koning)
   - Fix race condition on merge when build succeeds
@@ -70,6 +78,7 @@ v 8.9.0 (unreleased)
   - Todos will display target state if issuable target is 'Closed' or 'Merged'
   - Validate only and except regexp
   - Fix bug when sorting issues by milestone due date and filtering by two or more labels
+  - POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project
   - Add support for using Yubikeys (U2F) for two-factor authentication
   - Link to blank group icon doesn't throw a 404 anymore
   - Remove 'main language' feature
@@ -77,13 +86,16 @@ v 8.9.0 (unreleased)
   - Pipelines can be canceled only when there are running builds
   - Allow authentication using personal access tokens
   - Use downcased path to container repository as this is expected path by Docker
+  - Allow to use CI token to fetch LFS objects
   - Custom notification settings
   - Projects pending deletion will render a 404 page
   - Measure queue duration between gitlab-workhorse and Rails
   - Added Gfm autocomplete for labels
+  - Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114
   - Make Omniauth providers specs to not modify global configuration
   - Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir)
   - Make authentication service for Container Registry to be compatible with < Docker 1.11
+  - Make it possible to lock a runner from being enabled for other projects
   - Add Application Setting to configure Container Registry token expire delay (default 5min)
   - Cache assigned issue and merge request counts in sidebar nav
   - Use Knapsack only in CI environment
@@ -101,6 +113,7 @@ v 8.9.0 (unreleased)
   - An indicator is now displayed at the top of the comment field for confidential issues.
   - Show categorised search queries in the search autocomplete
   - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
+  - Dropdown for `.gitlab-ci.yml` templates
   - Improve issuables APIs performance when accessing notes !4471
   - Add sorting dropdown to tags page !4423
   - External links now open in a new tab
@@ -129,10 +142,17 @@ v 8.9.0 (unreleased)
   - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
   - Set inverse_of for Project/Service association to reduce the number of queries
   - Update tanuki logo highlight/loading colors
+  - Remove explicit Gitlab::Metrics.action assignments, are already automatic.
   - Use Git cached counters for branches and tags on project page
+  - Cache participable participants in an instance variable.
   - Filter parameters for request_uri value on instrumented transactions.
+  - Remove duplicated keys add UNIQUE index to keys fingerprint column
+  - ExtractsPath get ref_names from repository cache, if not there access git.
   - Cache user todo counts from TodoService
   - Ensure Todos counters doesn't count Todos for projects pending delete
+  - Add left/right arrows horizontal navigation
+  - Add tooltip to pin/unpin navbar
+  - Add new sub nav style to Wiki and Graphs sub navigation
 
 v 8.8.5
   - Import GitHub repositories respecting the API rate limit !4166
diff --git a/Gemfile b/Gemfile
index bc1223e1bbcaced025a0bf994bdb31dd89c04996..092ea9d69b0b300052dd11c5e07555968f710c1b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -48,7 +48,7 @@ gem 'attr_encrypted', '~> 3.0.0'
 gem 'u2f', '~> 0.2.1'
 
 # Browser detection
-gem "browser", '~> 2.0.3'
+gem "browser", '~> 2.2'
 
 # Extracting information from a git repository
 # Provide access to Gitlab::Git library
@@ -330,7 +330,7 @@ gem "newrelic_rpm", '~> 3.14'
 
 gem 'octokit', '~> 4.3.0'
 
-gem "mail_room", "~> 0.7"
+gem "mail_room", "~> 0.8"
 
 gem 'email_reply_parser', '~> 0.5.8'
 
diff --git a/Gemfile.lock b/Gemfile.lock
index 49e548fb94f0d0327e95a7cb122120476ba92ca1..ba16e4bf3378be01879b9136f96739abe5b71b58 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -98,7 +98,7 @@ GEM
       autoprefixer-rails (>= 5.2.1)
       sass (>= 3.3.4)
     brakeman (3.3.2)
-    browser (2.0.3)
+    browser (2.2.0)
     builder (3.2.2)
     bullet (5.0.0)
       activesupport (>= 3.0.0)
@@ -398,7 +398,7 @@ GEM
       systemu (~> 2.6.2)
     mail (2.6.4)
       mime-types (>= 1.16, < 4)
-    mail_room (0.7.0)
+    mail_room (0.8.0)
     method_source (0.8.2)
     mime-types (2.99.2)
     mimemagic (0.3.0)
@@ -833,7 +833,7 @@ DEPENDENCIES
   binding_of_caller (~> 0.7.2)
   bootstrap-sass (~> 3.3.0)
   brakeman (~> 3.3.0)
-  browser (~> 2.0.3)
+  browser (~> 2.2)
   bullet
   bundler-audit
   byebug
@@ -899,7 +899,7 @@ DEPENDENCIES
   license_finder
   licensee (~> 8.0.0)
   loofah (~> 2.0.3)
-  mail_room (~> 0.7)
+  mail_room (~> 0.8)
   method_source (~> 0.8)
   minitest (~> 5.7.0)
   mousetrap-rails (~> 1.4.6)
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 3f61ea1eaf4320ff9e7d2341c235ab660fc93c4b..cf46f15a15602455aa13154d6b994db8718c9dfc 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -7,6 +7,7 @@
   labelsPath: "/api/:version/projects/:id/labels"
   licensePath: "/api/:version/licenses/:key"
   gitignorePath: "/api/:version/gitignores/:key"
+  gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
 
   group: (group_id, callback) ->
     url = Api.buildUrl(Api.groupPath)
@@ -110,6 +111,12 @@
     $.get url, (gitignore) ->
       callback(gitignore)
 
+  gitlabCiYml: (key, callback) ->
+    url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
+
+    $.get url, (file) ->
+      callback(file)
+
   buildUrl: (url) ->
     url = gon.relative_url_root + url if gon.relative_url_root?
     return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 2f9f6c3ef5b2abbc8486e728f8ddb2932eafc8cc..0206db461da3f588ad2a0313d5d22129cdf3aed4 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -121,6 +121,11 @@ window.onload = ->
     setTimeout shiftWindow, 100
 
 $ ->
+
+  $document = $(document)
+  $window   = $(window)
+  $body     = $('body')
+
   gl.utils.preventDisabledButtons()
   bootstrapBreakpoint = bp.getBreakpointSize()
 
@@ -152,7 +157,7 @@ $ ->
     ), 1
 
   # Initialize tooltips
-  $('body').tooltip(
+  $body.tooltip(
     selector: '.has-tooltip, [data-toggle="tooltip"]'
     placement: (_, el) ->
       $el = $(el)
@@ -171,7 +176,7 @@ $ ->
     flash.show()
 
   # Disable form buttons while a form is submitting
-  $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
+  $body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
     buttons = $('[type="submit"]', @)
 
     switch e.type
@@ -184,7 +189,7 @@ $ ->
   $('.account-box').hover -> $(@).toggleClass('hover')
 
   # Commit show suppressed diff
-  $(document).on 'click', '.diff-content .js-show-suppressed-diff', ->
+  $document.on 'click', '.diff-content .js-show-suppressed-diff', ->
     $container = $(@).parent()
     $container.next('table').show()
     $container.remove()
@@ -197,13 +202,13 @@ $ ->
     $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
 
   # Show/hide comments on diff
-  $("body").on "click", ".js-toggle-diff-comments", (e) ->
+  $body.on "click", ".js-toggle-diff-comments", (e) ->
     $(@).toggleClass('active')
     $(@).closest(".diff-file").find(".notes_holder").toggle()
     e.preventDefault()
 
-  $(document).off "click", '.js-confirm-danger'
-  $(document).on "click", '.js-confirm-danger', (e) ->
+  $document.off "click", '.js-confirm-danger'
+  $document.on "click", '.js-confirm-danger', (e) ->
     e.preventDefault()
     btn = $(e.target)
     text = btn.data("confirm-danger-message")
@@ -211,7 +216,7 @@ $ ->
     new ConfirmDangerModal(form, text)
 
 
-  $(document).on 'click', 'button', ->
+  $document.on 'click', 'button', ->
     $(this).blur()
 
   $('input[type="search"]').each ->
@@ -219,7 +224,7 @@ $ ->
     $this.attr 'value', $this.val()
     return
 
-  $(document)
+  $document
     .off 'keyup', 'input[type="search"]'
     .on 'keyup', 'input[type="search"]' , (e) ->
       $this = $(this)
@@ -227,7 +232,7 @@ $ ->
 
   $sidebarGutterToggle = $('.js-sidebar-toggle')
 
-  $(document)
+  $document
     .off 'breakpoint:change'
     .on 'breakpoint:change', (e, breakpoint) ->
       if breakpoint is 'sm' or breakpoint is 'xs'
@@ -239,14 +244,14 @@ $ ->
     oldBootstrapBreakpoint = bootstrapBreakpoint
     bootstrapBreakpoint = bp.getBreakpointSize()
     if bootstrapBreakpoint != oldBootstrapBreakpoint
-      $(document).trigger('breakpoint:change', [bootstrapBreakpoint])
+      $document.trigger('breakpoint:change', [bootstrapBreakpoint])
 
   checkInitialSidebarSize = ->
     bootstrapBreakpoint = bp.getBreakpointSize()
     if bootstrapBreakpoint is "xs" or "sm"
-      $(document).trigger('breakpoint:change', [bootstrapBreakpoint])
+      $document.trigger('breakpoint:change', [bootstrapBreakpoint])
 
-  $(window)
+  $window
     .off "resize.app"
     .on "resize.app", (e) ->
       fitSidebarForSize()
@@ -256,29 +261,45 @@ $ ->
   new Aside()
 
   # Sidenav pinning
-  if $(window).width() < 1440 and $.cookie('pin_nav') is 'true'
-    $.cookie('pin_nav', 'false')
+  if $window.width() < 1440 and $.cookie('pin_nav') is 'true'
+    $.cookie('pin_nav', 'false', { path: '/' })
     $('.page-with-sidebar')
       .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
       .removeClass('page-sidebar-pinned')
     $('.navbar-fixed-top').removeClass('header-pinned-nav')
 
-  $(document)
+  $document
     .off 'click', '.js-nav-pin'
     .on 'click', '.js-nav-pin', (e) ->
       e.preventDefault()
 
+      $pinBtn = $(e.currentTarget)
+      $page = $ '.page-with-sidebar'
+      $topNav = $ '.navbar-fixed-top'
+      $tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
+      doPinNav = not $page.is('.page-sidebar-pinned')
+      tooltipText = 'Pin navigation'
+
       $(this).toggleClass 'is-active'
 
-      if $.cookie('pin_nav') is 'true'
-        $.cookie 'pin_nav', 'false'
-        $('.page-with-sidebar')
-          .removeClass('page-sidebar-pinned')
-          .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
-        $('.navbar-fixed-top')
-          .removeClass('header-pinned-nav')
-          .toggleClass('header-collapsed header-expanded')
+      if doPinNav
+        $page.addClass('page-sidebar-pinned')
+        $topNav.addClass('header-pinned-nav')
       else
-        $.cookie 'pin_nav', 'true'
-        $('.page-with-sidebar').addClass('page-sidebar-pinned')
-        $('.navbar-fixed-top').addClass('header-pinned-nav')
+        $tooltip.remove() # Remove it immediately when collapsing the sidebar
+        $page.removeClass('page-sidebar-pinned')
+             .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
+        $topNav.removeClass('header-pinned-nav')
+               .toggleClass('header-collapsed header-expanded')
+
+      # Save settings
+      $.cookie 'pin_nav', doPinNav, { path: '/' }
+
+      if $.cookie('pin_nav') is 'true' or doPinNav
+        tooltipText = 'Unpin navigation'
+
+      # Update tooltip text immediately
+      $tooltip.find('.tooltip-inner').text(tooltipText)
+
+      # Persist tooltip title
+      $pinBtn.attr('title', tooltipText).tooltip('fixTitle')
diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.coffee b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..d9a03d055290a2e4c6cd35809b52149db802c07d
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
@@ -0,0 +1,23 @@
+#= require blob/template_selector
+
+class @BlobCiYamlSelector extends TemplateSelector
+  requestFile: (query) ->
+    Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
+
+class @BlobCiYamlSelectors
+  constructor: (opts) ->
+    {
+      @$dropdowns = $('.js-gitlab-ci-yml-selector')
+      @editor
+    } = opts
+
+    @$dropdowns.each (i, dropdown) =>
+      $dropdown = $(dropdown)
+
+      new BlobCiYamlSelector(
+        pattern: /(.gitlab-ci.yml)/,
+        data: $dropdown.data('data'),
+        wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
+        dropdown: $dropdown,
+        editor: @editor
+      )
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index 636f909dbd0b9ffacd07e41c7289559395b52199..19e584519d7470f84ba811502bd564c1b0148085 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -15,6 +15,7 @@ class @EditBlob
 
     new BlobLicenseSelectors { @editor }
     new BlobGitignoreSelectors { @editor }
+    new BlobCiYamlSelectors { @editor }
 
   initModePanesAndLinks: ->
     @$editModePanes = $(".js-edit-mode-pane")
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 2a8a1f05b352ff274568dd3d40ffa9eb05f84827..2a7bf0bc3061cf850fd70a33c6bbce6092c02cb4 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -58,7 +58,7 @@ class GitLabDropdownFilter
   filter: (search_text) ->
     data = @options.data()
 
-    if data?
+    if data? and not @options.filterByText
       results = data
 
       if search_text isnt ''
@@ -102,10 +102,11 @@ class GitLabDropdownFilter
           $el = $(@)
           matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
 
-          if matches.length
-            $el.show()
-          else
-            $el.hide()
+          unless $el.is('.dropdown-header')
+            if matches.length
+              $el.show()
+            else
+              $el.hide()
       else
         elements.show()
 
@@ -191,6 +192,7 @@ class GitLabDropdown
     if @options.filterable
       @filter = new GitLabDropdownFilter @filterInput,
         filterInputBlur: @filterInputBlur
+        filterByText: @options.filterByText
         remote: @options.filterRemote
         query: @options.data
         keys: searchFields
diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee
index d540cc4dc467f1802055bc5a32d06267acd4181d..77512d187c9537ec6a767703c02079589df3bd3e 100644
--- a/app/assets/javascripts/gl_form.js.coffee
+++ b/app/assets/javascripts/gl_form.js.coffee
@@ -34,6 +34,8 @@ class @GLForm
       # form and textarea event listeners
       @addEventListeners()
 
+      gl.text.init(@form)
+
     # hide discard button
     @form.find('.js-note-discard').hide()
 
@@ -42,6 +44,7 @@ class @GLForm
   clearEventListeners: ->
     @textarea.off 'focus'
     @textarea.off 'blur'
+    gl.text.removeListeners(@form)
 
   addEventListeners: ->
     @textarea.on 'focus', ->
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
index 584d281a510e06e2b4078938c3ade902b6f216ad..834a81af459c4c3ea5587489a6c0db989c8d201e 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
@@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph
 
 class @ContributorsAuthorGraph extends ContributorsGraph
   constructor: (@data) ->
-    @width = $('.content').width()/2 - 100
+    # Don't split graph size in half for mobile devices.
+    if $(window).width() < 768
+      @width = $('.content').width() - 80
+    else
+      @width = ($('.content').width() / 2) - 100
     @height = 200
     @x = null
     @y = null
diff --git a/app/assets/javascripts/lib/text_utility.js.coffee b/app/assets/javascripts/lib/text_utility.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..bb2772dfed2efa36a577e46cb91d3d4961add07f
--- /dev/null
+++ b/app/assets/javascripts/lib/text_utility.js.coffee
@@ -0,0 +1,79 @@
+((w) ->
+  w.gl ?= {}
+  w.gl.text ?= {}
+
+  gl.text.randomString = -> Math.random().toString(36).substring(7)
+
+  gl.text.replaceRange = (s, start, end, substitute) ->
+    s.substring(0, start) + substitute + s.substring(end);
+
+  gl.text.selectedText = (text, textarea) ->
+    text.substring(textarea.selectionStart, textarea.selectionEnd)
+
+  gl.text.insertText = (textArea, text, tag, selected, wrap) ->
+    selectedSplit = selected.split('\n')
+    startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
+
+    if selectedSplit.length > 1 and not wrap
+      insertText = selectedSplit.map((val) ->
+        if val.indexOf(tag) is 0
+          "#{val.replace(tag, '')}"
+        else
+          "#{tag}#{val}"
+      ).join('\n')
+    else
+      insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
+
+    if document.queryCommandSupported('insertText')
+      document.execCommand 'insertText', false, insertText
+    else
+      try
+        document.execCommand("ms-beginUndoUnit")
+
+      textArea.value = @replaceRange(
+          text,
+          textArea.selectionStart,
+          textArea.selectionEnd,
+          insertText)
+      try
+        document.execCommand("ms-endUndoUnit")
+
+    @moveCursor(textArea, tag, wrap)
+
+  gl.text.moveCursor = (textArea, tag, wrapped) ->
+    return unless textArea.setSelectionRange
+
+    if textArea.selectionStart is textArea.selectionEnd
+      if wrapped
+        pos = textArea.selectionStart - tag.length
+      else
+        pos = textArea.selectionStart
+
+      textArea.setSelectionRange pos, pos
+
+  gl.text.updateText = (textArea, tag, wrap) ->
+    $textArea = $(textArea)
+    oldVal = $textArea.val()
+    textArea = $textArea.get(0)
+    text = $textArea.val()
+    selected = @selectedText(text, textArea)
+    $textArea.focus()
+
+    @insertText(textArea, text, tag, selected, wrap)
+
+  gl.text.init = (form) ->
+    self = @
+    $('.js-md', form)
+      .off 'click'
+      .on 'click', ->
+        $this = $(@)
+        self.updateText(
+          $this.closest('.md-area').find('textarea'),
+          $this.data('md-tag'),
+          not $this.data('md-prepend')
+        )
+
+  gl.text.removeListeners = (form) ->
+    $('.js-md', form).off()
+
+) window
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index e2d3241437b73b37000808aa5abeabf866c7b3d0..17f7e18012738cc8c33888b9da8d943682941414 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -102,12 +102,15 @@ class @Notes
 
   keydownNoteText: (e) ->
     $this = $(this)
-    if $this.val() is '' and e.which is 38 #aka the up key
+    if $this.val() is '' and e.which is 38 and not isMetaKey e
       myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
       if myLastNote.length
         myLastNoteEditBtn = myLastNote.find('.js-note-edit')
         myLastNoteEditBtn.trigger('click', [true, myLastNote])
 
+  isMetaKey = (e) ->
+    (e.metaKey or e.ctrlKey or e.altKey or e.shiftKey)
+
   initRefresh: ->
     clearInterval(Notes.interval)
     Notes.interval = setInterval =>
diff --git a/app/assets/javascripts/notifications_dropdown.js.coffee b/app/assets/javascripts/notifications_dropdown.js.coffee
index 74d2298c1fa74bbf4f5110b90340e51519d89a4c..0bbd082c156c3d5ac6a3ddb3b4ecc153d6c14cfa 100644
--- a/app/assets/javascripts/notifications_dropdown.js.coffee
+++ b/app/assets/javascripts/notifications_dropdown.js.coffee
@@ -1,5 +1,5 @@
 class @NotificationsDropdown
-  $ ->
+  constructor: ->
     $(document)
       .off 'click', '.update-notification'
       .on 'click', '.update-notification', (e) ->
@@ -18,7 +18,8 @@ class @NotificationsDropdown
       .off 'ajax:success', '.notification-form'
       .on 'ajax:success', '.notification-form', (e, data) ->
         if data.saved
-          new Flash('Notification settings saved', 'notice')
-          $(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html)
+          $(e.currentTarget)
+            .closest('.notification-dropdown')
+            .replaceWith(data.html)
         else
           new Flash('Failed to save new settings', 'alert')
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index d12bad97a0517ebbf23b35cb2180a3651b76e45a..96e10dd7e8ae34410f8d9f35949075cf9455f08a 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -19,6 +19,7 @@ class @Project
       $('.clone').text(url)
 
     # Ref switcher
+    @initRefSwitcher()
     $('.project-refs-select').on 'change', ->
       $(@).parents('form').submit()
 
@@ -34,7 +35,6 @@ class @Project
       $(@).parents('.no-password-message').remove()
       e.preventDefault()
 
-
     @projectSelectDropdown()
 
   projectSelectDropdown: ->
@@ -50,3 +50,39 @@ class @Project
 
   changeProject: (url) ->
     window.location = url
+
+  initRefSwitcher: ->
+    $('.js-project-refs-dropdown').each ->
+      $dropdown = $(@)
+      selected = $dropdown.data('selected')
+
+      $dropdown.glDropdown(
+        data: (term, callback) ->
+          $.ajax(
+            url: $dropdown.data('refs-url')
+            data:
+              ref: $dropdown.data('ref')
+          ).done (refs) ->
+            callback(refs)
+        selectable: true
+        filterable: true
+        filterByText: true
+        fieldName: 'ref'
+        renderRow: (ref) ->
+          if ref.header?
+            "<li class='dropdown-header'>#{ref.header}</li>"
+          else
+            isActiveClass = if ref is selected then 'is-active' else ''
+
+            "<li>
+              <a href='#' data-ref='#{escape(ref)}' class='#{isActiveClass}'>
+                #{ref}
+              </a>
+            </li>"
+        id: (obj, $el) ->
+          $el.data('ref')
+        toggleLabel: (obj, $el) ->
+          $el.text().trim()
+        clicked: (e) ->
+          $dropdown.closest('form').submit()
+      )
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 3cbddc59f11af5c044cf0a9dc64b7737abea3a48..a306b8f3f2968fec8247ab8636f44f16f890ccc4 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -37,3 +37,4 @@
 @import "framework/timeline.scss";
 @import "framework/typography.scss";
 @import "framework/zen.scss";
+@import "framework/blank";
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
new file mode 100644
index 0000000000000000000000000000000000000000..40b5171a8c63aabb1d341eaa88ef9b5dea9f7825
--- /dev/null
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -0,0 +1,23 @@
+.blank-state {
+  padding-top: 20px;
+  padding-bottom: 20px;
+  text-align: center;
+}
+
+.blank-state-no-icon {
+  padding-top: 40px;
+  padding-bottom: 40px;  
+}
+
+.blank-state-title {
+  margin-top: 0;
+  margin-bottom: 5px;
+  font-size: 19px;
+  font-weight: normal;
+}
+
+.blank-state-text {
+  margin-top: 0;
+  margin-bottom: $gl-padding;
+  font-size: 15px;
+}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index d5fe5bc2ef13172af330989508e9be2990079f97..38023818709d0847059797b44c68dc338cd5fe32 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -97,6 +97,22 @@
   }
 }
 
+.sub-header-block {
+  background-color: $white-light;
+  border-bottom: 1px solid $white-dark;
+  padding: 11px 0;
+  margin-bottom: 11px;
+
+  .oneline {
+    line-height: 35px;
+  }
+
+  &.no-bottom-space {
+    border-bottom: 0;
+    margin-bottom: 0;
+  }
+}
+
 .cover-block {
   text-align: center;
   background: $background-color;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d4d579a083d65b17b2a3bf6b5f404f6511e1fe8a..00111dfa70662a3fec984118e1b2cc27efe14b51 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -461,10 +461,12 @@
       }
     }
 
-    .ui-state-active,
-    .ui-state-hover {
-      color: $md-link-color;
-      background-color: $calendar-hover-bg;
+    .ui-datepicker-calendar {
+      .ui-state-hover,
+      .ui-state-active {
+        color: #fff;
+        border: 0;
+      }
     }
 
     .ui-datepicker-prev,
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index fd885b38680c0d830d91e66f99c7e08f60302b52..fd8eaa8a691a3ee7679bce034d865bc831382b71 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -65,6 +65,11 @@
     a {
       padding-top: 0;
       line-height: 1;
+      border-bottom: 1px solid $border-color;
+
+      &.btn.btn-xs {
+        padding: 2px 5px;
+      }
     }
   }
 }
@@ -97,5 +102,30 @@
       white-space: pre-wrap;
       word-break: keep-all;
     }
+
+    @include bulleted-list;
+  }
+}
+
+.toolbar-group {
+  float: left;
+  margin-right: -5px;
+  margin-left: $gl-padding;
+
+  &:first-child {
+    margin-left: 0;
+  }
+}
+
+.toolbar-btn {
+  float: left;
+  padding: 0 5px;
+  color: #959494;
+  background: transparent;
+  border: 0;
+  outline: 0;
+
+  &:hover {
+    color: $gl-link-color;
   }
 }
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 828e72242319425fcb79d2d20824bedbacc37d26..5ec5a96a597d508767fc65fcd5b36ab4dc96577e 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -110,3 +110,17 @@
   font-size: 16px;
   line-height: 24px;
 }
+
+@mixin bulleted-list {
+  > ul {
+    list-style-type: disc;
+
+    ul {
+      list-style-type: circle;
+
+      ul {
+        list-style-type: square;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index a55918f87113544be641c98e9c79e17b5fb0a759..0281b06d3ba3e874358c892cbd47b8c6b9d459eb 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -18,6 +18,13 @@
     opacity: 0;
     transition-duration: .3s;
   }
+
+  .fa {
+    position: relative;
+    top: 3px;
+    font-size: 13px;
+    color: $btn-placeholder-gray;
+  }
 }
 
 @mixin scrolling-links() {
@@ -104,10 +111,6 @@
     width: 50%;
     line-height: 28px;
 
-    &.wiki-page {
-      padding: 16px 10px 11px;
-    }
-
     /* Small devices (phones, tablets, 768px and lower) */
     @media (max-width: $screen-sm-min) {
       width: 100%;
@@ -136,7 +139,7 @@
     }
 
     /* Small devices (phones, tablets, 768px and lower) */
-    @media (max-width: $screen-sm-max) {
+    @media (max-width: $screen-xs-max) {
       width: 100%;
     }
   }
@@ -220,6 +223,7 @@
       form {
         display: block;
         height: auto;
+        margin-bottom: 14px;
 
         input {
           width: 100%;
@@ -319,11 +323,19 @@
     .fade-right {
       @include fade(left, rgba(250, 250, 250, 0.4), $background-color);
       right: 0;
+
+      .fa {
+        right: -7px;
+      }
     }
 
     .fade-left {
       @include fade(right, rgba(250, 250, 250, 0.4), $background-color);
       left: 0;
+
+      .fa {
+        left: -7px;
+      }
     }
 
     li {
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index ae7bdf14c40165fc8e21e9e95f4d2b64ef6a0dbb..874416e10074f779020fa1dcc1651c3dcbec6551 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -9,6 +9,10 @@
       margin-top: -2px;
       float: right;
     }
+
+    .dropdown-menu-toggle {
+      line-height: 20px;
+    }
   }
 
   .panel-body {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index f242706ebe45ed4a0fa088bc242bc0067604a972..21d87cc9d341d6775b00be8e3299c811b50fd728 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -165,11 +165,6 @@
   background-size: 16px 16px !important;
 }
 
-/** Branch/tag selector **/
-.project-refs-form .select2-container {
-  width: 160px !important;
-}
-
 .select2-results .select2-no-results,
 .select2-results .select2-searching,
 .select2-results .select2-ajax-error,
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index a0bb3427af0e2771c24b2d8bac5db05fe635ee22..98f917ce69b5c64df22c68286801d12c520512ea 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -91,7 +91,6 @@
       text-decoration: none;
       font-weight: normal;
       outline: none;
-      white-space: nowrap;
 
       &:hover,
       &:active,
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 761e33f0df772e61dd43fe8bf0a51e3b5100b074..de534d284219326d0184ee708d92fbae3bfdc1fa 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -80,9 +80,14 @@
 
 .commit {
   padding: 10px 0;
+  position: relative;
 
   @media (min-width: $screen-sm-min) {
-    padding-left: 46px;
+    padding-left: 20px;
+
+    .commit-info-block {
+      padding-left: 44px;
+    }
   }
 
   &:not(:last-child) {
@@ -95,8 +100,11 @@
     vertical-align: baseline;
   }
 
+
   .avatar {
-    margin-left: -46px;
+    position: absolute;
+    top: 10px;
+    left: 16px;
   }
 
   .item-title {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 1a7d5f9666eb96957b3ec696474b1ce79e790525..5286b73cc5036884aa29ca1a9319365c3066124d 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -4,6 +4,11 @@
   margin-bottom: $gl-padding;
   border-radius: 3px;
 
+  .commit-short-id {
+    font-family: $regular_font;
+    font-weight: 400;
+  }
+
   .diff-header {
     position: relative;
     background: $background-color;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index a34b06f105466555f5d08ee5f823abf8ad9fa8f4..1aa4e06d97500edce039aa64f3373100e18758fc 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -60,13 +60,14 @@
 
   .encoding-selector,
   .license-selector,
-  .gitignore-selector {
+  .gitignore-selector,
+  .gitlab-ci-yml-selector {
     display: inline-block;
     vertical-align: top;
     font-family: $regular_font;
   }
 
-  .gitignore-selector, .license-selector {
+  .gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
     .dropdown {
       line-height: 21px;
     }
@@ -76,4 +77,10 @@
       width: 220px;
     }
   }
+
+  .gitlab-ci-yml-selector {
+    .dropdown-menu-toggle {
+      width: 250px;
+    }
+  }
 }
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 6c36f603daf5032a3b498f4de821d2dee2b3ae72..a2145956eb58abd39d89dc7a2a74a23045f20669 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -54,6 +54,10 @@
         }
       }
 
+      code {
+        white-space: pre-wrap;
+      }
+
       pre {
         border: none;
         background: #f9f9f9;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 4a95b7b852e0c21f623499033ccd823ced20282b..0b710ef168b63e34e959ec8208de0180806fcdfe 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -57,4 +57,11 @@
 
 .documentation {
   padding: 7px;
+
+  // Border around images in the help pages.
+  img:not(.emoji) {
+    border: 1px solid $table-border-gray;
+    padding: 5px;
+    margin: 5px;
+  }
 }
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 687117233f632162f076b31aebfac93a4098402b..21ff6ab71f09dbcab8a7a6ba3978015da683b669 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -4,6 +4,13 @@
       margin-right: 1px;
     }
   }
+
+  // Border around images in issue and MR descriptions.
+  .description img:not(.emoji) {
+    border: 1px solid $table-border-gray;
+    padding: 5px;
+    margin: 5px;
+  }
 }
 
 .issuable-filter-count {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 046c38aba44c65e51abf7fd5fe13645690dd338d..f5f67e2cd84e2401a8c35c3ef10a9a756f93b6ba 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -50,11 +50,10 @@
 
 .label-row {
   .label-name {
-    display: block;
+    display: inline-block;
     margin-bottom: 10px;
 
     @media (min-width: $screen-sm-min) {
-      display: inline-block;
       width: 200px;
       margin-bottom: 0;
     }
@@ -63,6 +62,7 @@
   .label-description {
     display: block;
     margin-bottom: 10px;
+    margin-left: 50px;
 
     @media (min-width: $screen-sm-min) {
       display: inline-block;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index e67271adfb1da3c2d4769c63b2c63385ed8831bf..aca82f7f7bf082b55913b4eb54100e77e6762168 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -119,7 +119,12 @@
       margin-bottom: 0;
     }
 
-    @media (max-width: $screen-sm-max) {
+    .btn-grouped {
+      margin-left: 0;
+      margin-right: 7px;
+    }
+
+    @media (max-width: $screen-xs-max) {
       h4 {
         font-size: 15px;
       }
@@ -131,10 +136,14 @@
       .btn,
       .btn-group,
       .accept-action {
-        width: 100%;
         margin-bottom: 4px;
       }
 
+      .accept-action {
+        width: 100%;
+        text-align: center;
+      }
+
       .accept-control {
         width: 100%;
         text-align: center;
@@ -284,7 +293,7 @@
     margin-bottom: 0;
   }
 
-  @media (min-width: $screen-sm-min) {
+  @media (min-width: $screen-xs-min) {
     float: left;
     width: 50%;
     margin-bottom: 0;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 577dddae74141200b683dec2a2d0a6c2f3dc2787..3784010348a0dcdbfa0876081f16283d08c7197d 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -179,6 +179,10 @@
   border-top: 1px solid $border-color;
 }
 
+.md-helper {
+  padding-top: 10px;
+}
+
 .toolbar-button {
   padding: 0;
   background: none;
@@ -219,3 +223,16 @@
     float: left;
   }
 }
+
+.note-form-actions {
+  @media (max-width: $screen-xs-max) {
+    .btn {
+      float: none;
+      width: 100%;
+
+      &:not(:last-child) {
+        margin-bottom: 10px;
+      }
+    }
+  }
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 35d728aec83f420581e418a042a52b8e9360d385..ffba3dc5bc68a8bf99ae9f553dc19a8baad1e73f 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -84,24 +84,14 @@ ul.notes {
         word-wrap: break-word;
         @include md-typography;
 
+        // Reset ul style types since we're nested inside a ul already
+        @include bulleted-list;
+
         // On diffs code should wrap nicely and not overflow
         code {
           white-space: pre-wrap;
         }
 
-        // Reset ul style types since we're nested inside a ul already
-        & > ul {
-          list-style-type: disc;
-
-          ul {
-            list-style-type: circle;
-
-            ul {
-              list-style-type: square;
-            }
-          }
-        }
-
         ul.task-list {
           ul:not(.task-list) {
             padding-left: 1.3em;
@@ -117,6 +107,13 @@ ul.notes {
         code {
           word-break: keep-all;
         }
+
+        // Border around images in issue and MR comments.
+        img:not(.emoji) {
+          border: 1px solid $table-border-gray;
+          padding: 5px;
+          margin: 5px 0;
+        }
       }
     }
 
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index f138a2f53878df1a80bcb81182c6edee457a028f..d3e59d7fdb95415f385059c5cc8fb58b6d63bcbb 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -101,7 +101,8 @@
 
   .notifications-btn {
 
-    .fa-bell {
+    .fa-bell,
+    .fa-spinner {
       margin-right: 6px;
     }
 
@@ -373,7 +374,7 @@ a.deploy-project-label {
 .project-stats {
   margin-top: $gl-padding;
   margin-bottom: 0;
-  padding: 16px 0;
+  padding: 0;
   background-color: $white-light;
   font-size: 0;
 
@@ -382,13 +383,14 @@ a.deploy-project-label {
   }
 
   .nav li {
-    display: inline;
+    display: inline-block;
+    margin: 16px 0;
+    margin-right: 16px;
   }
 
   .nav > li > a {
     background-color: transparent;
-    margin-right: 12px;
-    padding: 0 10px;
+    padding: 5px 10px;
     font-size: 15px;
     color: $notes-light-color;
   }
@@ -402,12 +404,17 @@ a.deploy-project-label {
     font-size: 17px;
   }
 
-  li.missing a {
-    color: #5a6069;
-    border: 1px dashed #dce0e5;
+  li.missing {
+    border: 1px dashed $border-gray-light;
+    border-radius: $border-radius-default;
+
+    a {
+      color: $notes-light-color;
+      display: block;
+    }
 
     &:hover {
-      background-color: #f0f2f5;
+      background-color: $gray-normal;
     }
   }
 
@@ -616,3 +623,9 @@ pre.light-well {
     color: $gl-text-green;
   }
 }
+
+.project-refs-form {
+  .dropdown-menu {
+    width: 300px;
+  }
+}
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index 85a0304196c2307a4ba5840d1c91ca7129098cf1..69288b31cc4299794d97dfe0551f062e066fdd53 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -14,24 +14,38 @@
   font-size: 10px;
 }
 
+#contributors-master {
+  @include make-md-column(12);
+
+  svg {
+    width: 100%;
+  }
+}
+
 #contributors {
   .contributors-list {
     margin: 0 0 10px;
     list-style: none;
     padding: 0;
+
+    svg {
+      width: 100%;
+    }
   }
 
   .person {
-    &:nth-child(even) {
-      float: right;
-    }
-    float: left;
+    @include make-md-column(6);
     margin-top: 10px;
+
+    @media (max-width: $screen-sm-min) {
+      width: 100%;
+    }
   }
 
   .person .spark {
     display: block;
     background: #f3f3f3;
+    width: 100%;
   }
 
   .person .area-contributor {
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index afc00a68572189d5c6c018cfc341aa80e9a44a64..cf16d070cfe6eaba477a9219e8b20ca5c7421ba3 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -62,6 +62,10 @@
         }
       }
 
+      code {
+        white-space: pre-wrap;
+      }
+
       pre {
         border: none;
         background: #f9f9f9;
diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb
index 26cf74e484927636093e93bb959f2e1b2ed84943..4b0ec54b3f4f8623cc5d71752e339966dbdc7f74 100644
--- a/app/controllers/admin/appearances_controller.rb
+++ b/app/controllers/admin/appearances_controller.rb
@@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController
   end
 
   def preview
+    render 'preview', layout: 'devise'
   end
 
   def create
diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb
index d25619d94e0be2f9be72124d2d31d1c183ffbc65..bf20c5305a7b9d821cd1d11491cdf206043e5477 100644
--- a/app/controllers/admin/runner_projects_controller.rb
+++ b/app/controllers/admin/runner_projects_controller.rb
@@ -1,15 +1,14 @@
 class Admin::RunnerProjectsController < Admin::ApplicationController
   before_action :project, only: [:create]
 
-  def index
-    @runner_projects = project.runner_projects.all
-    @runner_project = project.runner_projects.new
-  end
-
   def create
     @runner = Ci::Runner.find(params[:runner_project][:runner_id])
 
-    if @runner.assign_to(@project, current_user)
+    return head(403) if @runner.is_shared? || @runner.locked?
+
+    runner_project = @runner.assign_to(@project, current_user)
+
+    if runner_project.persisted?
       redirect_to admin_runner_path(@runner)
     else
       redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index dd1bc6f5d52bdbf53ccbd2be32c6c3f20995878e..9cc31620d9f5313d196a9736334bd58e53c3586b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base
     render_404
   end
 
+  rescue_from Gitlab::Access::AccessDeniedError do |exception|
+    render_403
+  end
+
   def redirect_back_or_default(default: root_path, options: {})
     redirect_to request.referer.present? ? :back : default, options
   end
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index a24273fad0b232c79974ff3bc5343b1a5ebe4fb7..52dc396af6af21f8b050ce361300edd5b3e64c6d 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -21,29 +21,18 @@ module MembershipActions
 
   def leave
     @member = membershipable.members.find_by(user_id: current_user)
-    return render_403 unless @member
+    Members::DestroyService.new(@member, current_user).execute
 
     source_type = @member.real_source_type.humanize(capitalize: false)
-
-    if can?(current_user, action_member_permission(:destroy, @member), @member)
-      notice =
-        if @member.request?
-          "Your access request to the #{source_type} has been withdrawn."
-        else
-          "You left the \"#{@member.source.human_name}\" #{source_type}."
-        end
-      @member.destroy
-
-      redirect_to [:dashboard, @member.real_source_type.tableize], notice: notice
-    else
-      if cannot_leave?
-        alert = "You can not leave the \"#{@member.source.human_name}\" #{source_type}."
-        alert << " Transfer or delete the #{source_type}."
-        redirect_to polymorphic_url(membershipable), alert: alert
+    notice =
+      if @member.request?
+        "Your access request to the #{source_type} has been withdrawn."
       else
-        render_403
+        "You left the \"#{@member.source.human_name}\" #{source_type}."
       end
-    end
+    redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize]
+
+    redirect_to redirect_path, notice: notice
   end
 
   protected
@@ -51,8 +40,4 @@ module MembershipActions
   def membershipable
     raise NotImplementedError
   end
-
-  def cannot_leave?
-    raise NotImplementedError
-  end
 end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index d0f2e2949f08b461ddc91f71c520d92800868cfa..2c49fe3833ecceb4d5970b720053e45fb1c0bf7e 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -36,9 +36,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
   def destroy
     @group_member = @group.group_members.find(params[:id])
 
-    return render_403 unless can?(current_user, :destroy_group_member, @group_member)
-
-    @group_member.destroy
+    Members::DestroyService.new(@group_member, current_user).execute
 
     respond_to do |format|
       format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
@@ -68,8 +66,4 @@ class Groups::GroupMembersController < Groups::ApplicationController
 
   # MembershipActions concern
   alias_method :membershipable, :group
-
-  def cannot_leave?
-    @group.last_owner?(current_user)
-  end
 end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 776ba92c9abe52eb806629f8c9f3e5c0578fd3cf..996909a28c6805156e19f26705843fad413b39a1 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController
   end
 
   def require_branch_head
-    unless @repository.branch_names.include?(@ref)
+    unless @repository.branch_exists?(@ref)
       redirect_to(
         namespace_project_tree_path(@project.namespace, @project, @ref),
         notice: "This action is not allowed unless you are on a branch"
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 127bd1a43188106d17629bd8f41b3e9b1d3a4d0c..487963fdcd7b712721bd3640c9aae714bf6c7d56 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController
   end
 
   def commit
-    @commit ||= @pipeline.commit_data
+    @commit ||= @pipeline.commit
   end
 end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 35d067cd02924c7c2481f0b861b87b9ba576be97..6ba32d33403afb8fd11346ee5ba5c1ff98db3ea0 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -50,9 +50,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
   def destroy
     @project_member = @project.project_members.find(params[:id])
 
-    return render_403 unless can?(current_user, :destroy_project_member, @project_member)
-
-    @project_member.destroy
+    Members::DestroyService.new(@project_member, current_user).execute
 
     respond_to do |format|
       format.html do
@@ -98,8 +96,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController
 
   # MembershipActions concern
   alias_method :membershipable, :project
-
-  def cannot_leave?
-    current_user == @project.owner
-  end
 end
diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb
index bedeb4a295cf7d6e83e27d5b74af87c8f2fdeff0..dc1a18f8d4209aa393520e0d59e12ef02af139f3 100644
--- a/app/controllers/projects/runner_projects_controller.rb
+++ b/app/controllers/projects/runner_projects_controller.rb
@@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
   def create
     @runner = Ci::Runner.find(params[:runner_project][:runner_id])
 
+    return head(403) if @runner.is_shared? || @runner.locked?
     return head(403) unless current_user.ci_authorized_runners.include?(@runner)
 
     path = runners_path(project)
+    runner_project = @runner.assign_to(project, current_user)
 
-    if @runner.assign_to(project, current_user)
+    if runner_project.persisted?
       redirect_to path
     else
       redirect_to path, alert: 'Failed adding runner to project'
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 0b4fa572501705e6ba9375cb333dc5014ec50ebf..53c36635efe1ecd779360bf14ead772aa2556b97 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController
   layout 'project_settings'
 
   def index
-    @runners = project.runners.ordered
-    @specific_runners = current_user.ci_authorized_runners.
-      where.not(id: project.runners).
-      ordered.page(params[:page]).per(20)
+    @project_runners = project.runners.ordered
+    @assignable_runners = current_user.ci_authorized_runners.
+      assignable_for(project).ordered.page(params[:page]).per(20)
     @shared_runners = Ci::Runner.shared.active
     @shared_runners_count = @shared_runners.count(:all)
   end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 8044c6378256f4fd1a73839fa60f4b1c91b86ec1..2b1f50fd01ef89ab0b4b63689131c55a2f464716 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,7 +1,7 @@
 class ProjectsController < Projects::ApplicationController
   include ExtractsPath
 
-  before_action :authenticate_user!, except: [:show, :activity]
+  before_action :authenticate_user!, except: [:show, :activity, :refs]
   before_action :project, except: [:new, :create]
   before_action :repository, except: [:new, :create]
   before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
@@ -251,6 +251,24 @@ class ProjectsController < Projects::ApplicationController
     }
   end
 
+  def refs
+    options = {
+      'Branches' => @repository.branch_names,
+    }
+
+    unless @repository.tag_count.zero?
+      options['Tags'] = VersionSorter.rsort(@repository.tag_names)
+    end
+
+    # If reference is commit id - we should add it to branch/tag selectbox
+    ref = Addressable::URI.unescape(params[:ref])
+    if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/
+      options['Commits'] = [ref]
+    end
+
+    render json: options.to_json
+  end
+
   private
 
   def determine_layout
@@ -285,8 +303,14 @@ class ProjectsController < Projects::ApplicationController
     project.repository_exists? && !project.empty_repo?
   end
 
-  # Override get_id from ExtractsPath, which returns the branch and file path
+  # Override extract_ref from ExtractsPath, which returns the branch and file path
   # for the blob/tree, which in this case is just the root of the default branch.
+  # This way we avoid to access the repository.ref_names.
+  def extract_ref(_id)
+    [get_id, '']
+  end
+
+  # Override get_id from ExtractsPath in this case is just the root of the default branch.
   def get_id
     project.repository.root_ref
   end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 439b015b3b827462b86407e9422c7b7727b26414..418598418348c3ed5220b9107d704d4fc1435f5b 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -101,22 +101,6 @@ module ApplicationHelper
     'Never'
   end
 
-  def grouped_options_refs
-    repository = @project.repository
-
-    options = [
-      ['Branches', repository.branch_names],
-      ['Tags', VersionSorter.rsort(repository.tag_names)]
-    ]
-
-    # If reference is commit id - we should add it to branch/tag selectbox
-    if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/
-      options << ['Commit', [@ref]]
-    end
-
-    grouped_options_for_select(options, @ref || @project.default_branch)
-  end
-
   # Define whenever show last push event
   # with suggestion to create MR
   def show_last_push_widget?(event)
@@ -132,7 +116,7 @@ module ApplicationHelper
     return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
 
     # Skip if user removed branch right after that
-    return false unless project.repository.branch_names.include?(event.branch_name)
+    return false unless project.repository.branch_exists?(event.branch_name)
 
     true
   end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 5b54b34070c542d4f257ad9083ff0fbf7fbd8d77..4b4bc3d4276b8e85de9333086b8d26a91150b2d8 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -29,7 +29,7 @@ module BlobHelper
     if !on_top_of_branch?(project, ref)
       button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
     elsif can_edit_blob?(blob, project, ref)
-      link_to "Edit", edit_path, class: 'btn btn-file-option'
+      link_to "Edit", edit_path, class: 'btn btn-sm'
     elsif can?(current_user, :fork_project, project)
       continue_params = {
         to:     edit_path,
@@ -186,12 +186,16 @@ module BlobHelper
   end
 
   def gitignore_names
-    return @gitignore_names if defined?(@gitignore_names)
+    @gitignore_names ||=
+      Gitlab::Template::Gitignore.categories.keys.map do |k|
+        [k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }]
+      end.to_h
+  end
 
-    @gitignore_names = {
-      Global: Gitlab::Gitignore.global.map { |gitignore| { name: gitignore.name } },
-      # Note that the key here doesn't cover it really
-      Languages: Gitlab::Gitignore.languages_frameworks.map{ |gitignore| { name: gitignore.name } }
-    }
+  def gitlab_ci_ymls
+    @gitlab_ci_ymls ||=
+      Gitlab::Template::GitlabCiYml.categories.keys.map do |k|
+        [k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }]
+      end.to_h
   end
 end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index 3ee3fc74f0c4efe698df739d9836c841aee0dffd..c533659b600f8d0a37b30e6374efee32cc14f6cc 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -10,7 +10,7 @@ module BranchesHelper
   end
 
   def can_push_branch?(project, branch_name)
-    return false unless project.repository.branch_names.include?(branch_name)
+    return false unless project.repository.branch_exists?(branch_name)
 
     ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
   end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 067a00660aaa6560128d6b1bdaa0156b761c03bd..1a259656f31a9e52b7eb24584ff459e95a371734 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -50,8 +50,6 @@ module GitlabMarkdownHelper
 
     context[:project] ||= @project
 
-    text = Banzai.pre_process(text, context)
-
     html = Banzai.render(text, context)
 
     context.merge!(
@@ -185,4 +183,17 @@ module GitlabMarkdownHelper
       ''
     end
   end
+
+  def markdown_toolbar_button(options = {})
+    data = options[:data].merge({ container: "body" })
+    content_tag :button,
+      type: "button",
+      class: "toolbar-btn js-md has-tooltip hidden-xs",
+      tabindex: -1,
+      data: data,
+      title: options[:title],
+      aria: { label: options[:title] } do
+      icon(options[:icon])
+    end
+  end
 end
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 6dde2e9847db95e5b408e28b8859712334c40ca1..453116902937b23f9b191ab4b5d0f11adc2c6a6f 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -12,6 +12,11 @@ module Emails
       @member_id = member_id
 
       admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email)
+      # A project in a group can have no explicit owners/masters, in that case
+      # we fallbacks to the group's owners/masters.
+      if admins.empty? && member_source.respond_to?(:group) && member_source.group
+        admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email)
+      end
 
       mail(to: admins,
            subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index d618c84e9831e995899a0fa1be71af83c7182b99..2b0bec33131a5c6979d3b17cd7f9029e3dcd9ee7 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -300,18 +300,12 @@ module Ci
       project.valid_runners_token? token
     end
 
-    def can_be_served?(runner)
-      return false unless has_tags? || runner.run_untagged?
-
-      (tag_list - runner.tag_list).empty?
-    end
-
     def has_tags?
       tag_list.any?
     end
 
     def any_runners_online?
-      project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
+      project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) }
     end
 
     def stuck?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 5b264ecffc5a823d11651f5ac478f4290dfab854..ca5a685dd1132906c04ad32f20ccfb11d31070f7 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -37,22 +37,22 @@ module Ci
     end
 
     def git_author_name
-      commit_data.author_name if commit_data
+      commit.try(:author_name)
     end
 
     def git_author_email
-      commit_data.author_email if commit_data
+      commit.try(:author_email)
     end
 
     def git_commit_message
-      commit_data.message if commit_data
+      commit.try(:message)
     end
 
     def short_sha
       Ci::Pipeline.truncate_sha(sha)
     end
 
-    def commit_data
+    def commit
       @commit ||= project.commit(sha)
     rescue
       nil
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index adb652922085d211e23d9588dc4ff9ae26a250d1..b64ec79ec2b46284cd7725242540082d869027ef 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -4,7 +4,7 @@ module Ci
 
     LAST_CONTACT_TIME = 5.minutes.ago
     AVAILABLE_SCOPES = %w[specific shared active paused online]
-    FORM_EDITABLE = %i[description tag_list active run_untagged]
+    FORM_EDITABLE = %i[description tag_list active run_untagged locked]
 
     has_many :builds, class_name: 'Ci::Build'
     has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@@ -26,6 +26,13 @@ module Ci
         .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
     end
 
+    scope :assignable_for, ->(project) do
+      # FIXME: That `to_sql` is needed to workaround a weird Rails bug.
+      #        Without that, placeholders would miss one and couldn't match.
+      where(locked: false).
+        where.not("id IN (#{project.runners.select(:id).to_sql})").specific
+    end
+
     validate :tag_constraints
 
     acts_as_taggable
@@ -56,7 +63,7 @@ module Ci
     def assign_to(project, current_user = nil)
       self.is_shared = false if shared?
       self.save
-      project.runner_projects.create!(runner_id: self.id)
+      project.runner_projects.create(runner_id: self.id)
     end
 
     def display_name
@@ -91,6 +98,10 @@ module Ci
       !shared?
     end
 
+    def can_pick?(build)
+      assignable_for?(build.project) && accepting_tags?(build)
+    end
+
     def only_for?(project)
       projects == [project]
     end
@@ -111,5 +122,13 @@ module Ci
           'can not be empty when runner is not allowed to pick untagged jobs')
       end
     end
+
+    def assignable_for?(project)
+      !locked? || projects.exists?(id: project.id)
+    end
+
+    def accepting_tags?(build)
+      (run_untagged? || build.has_tags?) && (build.tag_list - tag_list).empty?
+    end
   end
 end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d69d518fadda10c3fb610fc4677c786984803cc4..174ccbaea6ca00e42530e6c5575f55c232a56935 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -271,6 +271,32 @@ class Commit
     merged_merge_request ? 'merge request' : 'commit'
   end
 
+  # Get the URI type of the given path
+  #
+  # Used to build URLs to files in the repository in GFM.
+  #
+  # path - String path to check
+  #
+  # Examples:
+  #
+  #   uri_type('doc/README.md') # => :blob
+  #   uri_type('doc/logo.png')  # => :raw
+  #   uri_type('doc/api')       # => :tree
+  #   uri_type('not/found')     # => :nil
+  #
+  # Returns a symbol
+  def uri_type(path)
+    entry = @raw.tree.path(path)
+    if entry[:type] == :blob
+      blob = Gitlab::Git::Blob.new(name: entry[:name])
+      blob.image? ? :raw : :blob
+    else
+      entry[:type]
+    end
+  rescue Rugged::TreeError
+    nil
+  end
+
   private
 
   def repo_changes
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index ab13db4b297c2d8e2828a05ea03e5a94fd136097..e437e3417a8388a602c6870cc731ea0a6428a717 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -8,6 +8,8 @@ class CommitStatus < ActiveRecord::Base
   belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true
   belongs_to :user
 
+  delegate :commit, to: :pipeline
+
   validates :pipeline, presence: true, unless: :importing?
 
   validates_presence_of :name
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 9056722f45e44f9a18fa62e938e89db26e4ccd65..9822844357dde157dc616c078a81de29b30fed83 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -53,6 +53,16 @@ module Participable
   #
   # Returns an Array of User instances.
   def participants(current_user = nil)
+    @participants ||= Hash.new do |hash, user|
+      hash[user] = raw_participants(user)
+    end
+
+    @participants[current_user]
+  end
+
+  private
+
+  def raw_participants(current_user = nil)
     current_user ||= author
     ext = Gitlab::ReferenceExtractor.new(project, current_user)
     participants = Set.new
diff --git a/app/models/key.rb b/app/models/key.rb
index 0532e84f47d0dcd5c4e0eefdb5ff45e08ed26371..b9bc38a04364d7fafb5233ac64ee3798ac4a645b 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -9,7 +9,7 @@ class Key < ActiveRecord::Base
   before_validation :strip_white_space, :generate_fingerprint
 
   validates :title, presence: true, length: { within: 0..255 }
-  validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
+  validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }
   validates :key, format: { without: /\n|\r/, message: 'should be a single line' }
   validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
 
diff --git a/app/models/member.rb b/app/models/member.rb
index 4ee3f1bb5c26a86b7d8dc6452381b79169b6a632..c74a16367dbd9f0f8b27e071b6096ef8fceac8bf 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -48,7 +48,6 @@ class Member < ActiveRecord::Base
   after_create :post_create_hook, unless: [:pending?, :importing?]
   after_update :post_update_hook, unless: [:pending?, :importing?]
   after_destroy :post_destroy_hook, unless: :pending?
-  after_destroy :post_decline_request, if: :request?
 
   delegate :name, :username, :email, to: :user, prefix: true
 
@@ -188,7 +187,7 @@ class Member < ActiveRecord::Base
   end
 
   def send_request
-    # override in subclass
+    notification_service.new_access_request(self)
   end
 
   def post_create_hook
@@ -215,10 +214,6 @@ class Member < ActiveRecord::Base
     post_create_hook
   end
 
-  def post_decline_request
-    # override in subclass
-  end
-
   def system_hook_service
     SystemHooksService.new
   end
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 363db8779689bf47d89aad38238232e1b8910da8..2f13d339c8970af9a6936f8c59c0563cbdcfb830 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -33,12 +33,6 @@ class GroupMember < Member
     super
   end
 
-  def send_request
-    notification_service.new_group_access_request(self)
-
-    super
-  end
-
   def post_create_hook
     notification_service.new_group_member(self)
 
@@ -64,10 +58,4 @@ class GroupMember < Member
 
     super
   end
-
-  def post_decline_request
-    notification_service.decline_group_access_request(self)
-
-    super
-  end
 end
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 250ee04fd1d1f68ac1123ff295b857d1a0b50433..e9d3a82ba15b3c6112c89816801b17a739cd7a57 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -111,12 +111,6 @@ class ProjectMember < Member
     super
   end
 
-  def send_request
-    notification_service.new_project_access_request(self)
-
-    super
-  end
-
   def post_create_hook
     unless owner?
       event_service.join_project(self.project, self.user)
@@ -152,12 +146,6 @@ class ProjectMember < Member
     super
   end
 
-  def post_decline_request
-    notification_service.decline_project_access_request(self)
-
-    super
-  end
-
   def event_service
     EventCreateService.new
   end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index bbd7682d8e7965b3d671372cb742b44f175c6a73..221c87164caa4006b2ba2045c2a699430d45e6f0 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -191,8 +191,12 @@ class Repository
     end
   end
 
+  def ref_names
+    branch_names + tag_names
+  end
+
   def branch_names
-    cache.fetch(:branch_names) { branches.map(&:name) }
+    @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
   end
 
   def branch_exists?(branch_name)
@@ -267,6 +271,7 @@ class Repository
 
   def expire_branches_cache
     cache.expire(:branch_names)
+    @branch_names = nil
     @local_branches = nil
   end
 
@@ -332,10 +337,6 @@ class Repository
     @lookup_cache ||= {}
   end
 
-  def expire_branch_names
-    cache.expire(:branch_names)
-  end
-
   def expire_avatar_cache(branch_name = nil, revision = nil)
     # Avatars are pulled from the default branch, thus if somebody pushes to a
     # different branch there's no need to expire anything.
diff --git a/app/models/user.rb b/app/models/user.rb
index 2e458329cb97ed85c37bd2c503bb3608ab931ae7..876ccc69d8d0ad50cada764b52fd2ffcef6437b4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -487,9 +487,8 @@ class User < ActiveRecord::Base
     events.recent.find do |event|
       project = Project.find_by_id(event.project_id)
       next unless project
-      repo = project.repository
 
-      if repo.branch_names.include?(event.branch_name)
+      if project.repository.branch_exists?(event.branch_name)
         merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
             where(source_project_id: project.id,
                   source_branch: event.branch_name)
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index f0ed09a629ad2ebe2cbd08f8b178e96c07e7d363..9a187f5d6945e63c76d0beeb73f75829278ca9a4 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -21,7 +21,7 @@ module Ci
         end
 
       build = builds.find do |build|
-        build.can_be_served?(current_runner)
+        current_runner.can_pick?(build)
       end
 
       if build
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..15358f80208ea5f024b79c6b1ba78d2e465e486e
--- /dev/null
+++ b/app/services/members/destroy_service.rb
@@ -0,0 +1,21 @@
+module Members
+  class DestroyService < BaseService
+    attr_accessor :member, :current_user
+
+    def initialize(member, user)
+      @member, @current_user = member, user
+    end
+
+    def execute
+      unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member)
+        raise Gitlab::Access::AccessDeniedError
+      end
+
+      member.destroy
+
+      if member.request? && member.user != current_user
+        notification_service.decline_access_request(member)
+      end
+    end
+  end
+end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 1b48899bb0a0f8fdb7465a1bad40579ca9834b8c..7fe57747265156320a76b5b4ac84c1b7cd28f19b 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")
         else
           merge_request.description = closes_issue
         end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 19832a19b2b3a8d2584851642ee3e53bdd0be097..590350a11e51858762c3842662d63c64c42499fe 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -181,15 +181,16 @@ class NotificationService
     end
   end
 
-  # Project access request
-  def new_project_access_request(project_member)
-    mailer.member_access_requested_email(project_member.real_source_type, project_member.id).deliver_later
+  # Members
+  def new_access_request(member)
+    mailer.member_access_requested_email(member.real_source_type, member.id).deliver_later
   end
 
-  def decline_project_access_request(project_member)
-    mailer.member_access_denied_email(project_member.real_source_type, project_member.project.id, project_member.user.id).deliver_later
+  def decline_access_request(member)
+    mailer.member_access_denied_email(member.real_source_type, member.source_id, member.user_id).deliver_later
   end
 
+  # Project invite
   def invite_project_member(project_member, token)
     mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later
   end
@@ -216,15 +217,7 @@ class NotificationService
     mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
   end
 
-  # Group access request
-  def new_group_access_request(group_member)
-    mailer.member_access_requested_email(group_member.real_source_type, group_member.id).deliver_later
-  end
-
-  def decline_group_access_request(group_member)
-    mailer.member_access_denied_email(group_member.real_source_type, group_member.group.id, group_member.user.id).deliver_later
-  end
-
+  # Group invite
   def invite_group_member(group_member, token)
     mailer.member_invited_email(group_member.real_source_type, group_member.id, token).deliver_later
   end
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index d88f3ad314d01c51bca8e6ac56a20b4296e5cff9..dc083e50178352185f73ecae02ccaec6cf60f111 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -46,7 +46,7 @@
         Maximum file size is 1MB. Pages are optimized for a 72x72 px header logo
 
   .form-actions
-    = f.submit 'Save', class: 'btn btn-save'
+    = f.submit 'Save', class: 'btn btn-save append-right-10'
     - if @appearance.persisted?
       = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank'
 
diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml
index dd4a64e80bc38f6ab5598a2234b74819f6ba482e..6c51639b8405f757ec18105023bd40d0b3e341f9 100644
--- a/app/views/admin/appearances/preview.html.haml
+++ b/app/views/admin/appearances/preview.html.haml
@@ -1,29 +1,9 @@
 - page_title "Preview | Appearance"
-%h3.page-title
-  Appearance settings - Preview
-%hr
+.login-box
+  .login-heading
+    %h3 Existing user? Sign in
+  %form
+    = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email"
+    = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password"
+    = button_tag "Sign in", class: "btn-create btn"
 
-.ui-box
-  .title
-    Sign-in page
-  %div
-    .login-page
-      .container
-        .content
-          .login-title
-            %h1= brand_title
-      %hr
-      .container
-        .content
-          .row
-            .col-sm-7
-              .brand-image
-                = brand_image
-              .brand_text
-                = brand_text
-            .col-sm-4
-              .login-box
-                %h3.page-title Sign in
-                = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email"
-                = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password"
-                = button_tag "Sign in", class: "btn-create btn"
diff --git a/app/views/admin/appearances/show.html.haml b/app/views/admin/appearances/show.html.haml
index 089e8e4cb7acf1b4d3199926306678a058dfe521..454b779842c0324aa51fee8895dc6dd492b25610 100644
--- a/app/views/admin/appearances/show.html.haml
+++ b/app/views/admin/appearances/show.html.haml
@@ -1,7 +1,9 @@
 - page_title "Appearance"
+
 %h3.page-title
   Appearance settings
 %p.light
   You can modify the look and feel of GitLab here
+%hr
 
 = render 'form'
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index e9c7ca9d5aa6df04b566b233a747dd6d351366ee..ecc46d86afe798a6b103669d752bbce61db7efe7 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -1,4 +1,5 @@
 - page_title "Settings"
+
 %h3.page-title Settings
 %hr
 = render 'form'
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 5b8a0262ea076b8827a1dbc799ac7389cc1a0d29..50770465f0780144a42bfe175cc30d01c62b9125 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -88,28 +88,17 @@
               = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
             %hr
             = button_tag 'Add users to group', class: "btn btn-create"
+
+    = render 'shared/members/requests', membership_source: @group, members: @members.request
+
     .panel.panel-default
       .panel-heading
-        %h3.panel-title
-          Members
-          %span.badge
-            #{@group.group_members.count}
-      %ul.well-list.group-users-list
-        - @members.each do |member|
-          - user = member.user
-          %li{class: dom_class(member), id: (dom_id(user) if user)}
-            .list-item-name
-              - if user
-                %strong
-                  = link_to user.name, admin_user_path(user)
-              - else
-                %strong
-                  = member.invite_email
-                (invited)
-            %span.pull-right.light
-              = member.human_access
-              - if can?(current_user, :destroy_group_member, member)
-                = link_to group_group_member_path(@group, member), data: { confirm: remove_member_message(member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
-                  %i.fa.fa-minus.fa-inverse
+        %strong= @group.name
+        group members
+        %span.badge= @group.members.non_request.size
+        .pull-right
+          = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@group, :members]), class: "btn btn-xs"
+      %ul.well-list.group-users-list.content-list
+        = render partial: 'shared/members/member', collection: @members.non_request, as: :member, locals: { show_controls: false }
       .panel-footer
-        = paginate @members, param_name: 'members_page', theme: 'gitlab'
+        = paginate @members.non_request, param_name: 'members_page', theme: 'gitlab'
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 9e55a562e18032d4cda87d49032792ec72fe6790..461d588415db7b8cc52d8527d0ad23eaeba63f5d 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -135,44 +135,27 @@
     - if @group
       .panel.panel-default
         .panel-heading
-          %strong #{@group.name}
-          group members (#{@group.group_members.count})
+          %strong= @group.name
+          group members
+          %span.badge= @group_members.non_request.size
           .pull-right
             = link_to admin_group_path(@group), class: 'btn btn-xs' do
-              %i.fa.fa-pencil-square-o
-        %ul.well-list
-          - @group_members.each do |member|
-            = render 'shared/members/member', member: member, show_controls: false
+              = icon('pencil-square-o', text: 'Manage Access')
+        %ul.well-list.content-list
+          = render partial: 'shared/members/member', collection: @group_members.non_request, as: :member, locals: { show_controls: false }
         .panel-footer
-          = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
+          = paginate @group_members.non_request, param_name: 'group_members_page', theme: 'gitlab'
+
+    = render 'shared/members/requests', membership_source: @project, members: @project_members.request
 
     .panel.panel-default
       .panel-heading
-        Project members
-        %small
-          (#{@project.users.count})
+        %strong= @project.name
+        project members
+        %span.badge= @project.users.size
         .pull-right
-          = link_to namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-xs" do
-            %i.fa.fa-pencil-square-o
-            Manage Access
-      %ul.well-list.project_members
-        - @project_members.each do |project_member|
-          - user = project_member.user
-          %li.project_member
-            .list-item-name
-              - if user
-                %strong
-                  = link_to user.name, admin_user_path(user)
-              - else
-                %strong
-                  = project_member.invite_email
-                (invited)
-            .pull-right
-              - if project_member.owner?
-                %span.light Owner
-              - else
-                %span.light= project_member.human_access
-                = link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_member_message(project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do
-                  %i.fa.fa-times
+          = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@project, :members]), class: "btn btn-xs"
+      %ul.well-list.project_members.content-list
+        = render partial: 'shared/members/member', collection: @project_members.non_request, as: :member, locals: { show_controls: false }
       .panel-footer
-        = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
+        = paginate @project_members.non_request, param_name: 'project_members_page', theme: 'gitlab'
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index e049b40bfab73ecb01ee1aa3907beafa84c3a32e..61abfc6ecbe8aa08ff1ccdac37f2b26539094d55 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -28,7 +28,7 @@
   .col-md-6
     %h4 Restrict projects for this runner
     - if @runner.projects.any?
-      %table.table
+      %table.table.assigned-projects
         %thead
           %tr
             %th Assigned projects
@@ -44,7 +44,7 @@
                 .pull-right
                   = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
 
-    %table.table
+    %table.table.unassigned-projects
       %thead
         %tr
           %th Project
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index d35f332e1e01a3f8ba06e73910e90144b02bcecf..f7abad542862393c4a29e28f06e23d35a6f91cf9 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -13,7 +13,7 @@
         Explore Projects
 
   .nav-controls
-    = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+    = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
       = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
     = render 'shared/projects/dropdown'
     - if current_user.can_create_project?
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index a36531e095a477f070eba0f6b4769394c938e7a3..d6acade84f1ce6957b0717044c3641884f4ea582 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -17,8 +17,7 @@
     .panel-heading
       %strong #{@group.name}
       group members
-      %small
-        (#{@members.total_count})
+      %span.badge= @members.non_request.size
       .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/show.html.haml b/app/views/groups/show.html.haml
index 62ebd69485cd80d8da617ed2da982b29bc5a3aed..aecefbc6e8f1da18c826491cdb119909b27c18b1 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -33,7 +33,7 @@
           = link_to "#shared", 'data-toggle' => 'tab' do
             Shared Projects
     .nav-controls
-      = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+      = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
         = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
       = render 'shared/projects/dropdown'
       - if can? current_user, :create_projects, @group
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 01648047ce20ecfb9ffba7490196da0e1b35f3ac..8cc0b59edebd8c2545d4f3f2aaeefd4cb5a8c5c0 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -28,8 +28,12 @@
                     .key &#8984; 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
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 199ab3c38c3eecb81822554046ada54e667d40a8..2234bf79c87d6b12ef61b5c23c291a3bc7d08687 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -13,7 +13,7 @@
         = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
         .username
           = current_user.username
-    = link_to '#', class: "nav-header-btn text-center pin-nav-btn #{'is-active' if pinned_nav?} js-nav-pin", title: 'Pin/Unpin navigation' do
+    = link_to '#', class: "nav-header-btn text-center pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
       %span.sr-only Toggle navigation pinning
       = icon('thumb-tack')
   - if defined?(nav) && nav
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index f02ac9496993e5c9043ae2fc13528fa624b0a6f6..66e5ec1ad1ab86ada1cb279d09ba8db92c62be0d 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,60 +1,41 @@
-%ul.nav-links.scrolling-tabs
-  .fade-left
-  = nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do
-    = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
-      %span
-        Overview
-  = nav_link(controller: %w(background_jobs logs health_check)) do
-    = link_to admin_background_jobs_path, title: 'Monitoring' do
-      %span
-        Monitoring
-  = nav_link(controller: :deploy_keys) do
-    = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
-      %span
-        Deploy Keys
-  = nav_link(controller: :broadcast_messages) do
-    = link_to admin_broadcast_messages_path, title: 'Messages' do
-      %span
-        Messages
-  = nav_link(controller: :hooks) do
-    = link_to admin_hooks_path, title: 'Hooks' do
-      %span
-        Hooks
+%div{ class: nav_control_class }
+  = render 'layouts/nav/admin_settings'
 
-  = nav_link(controller: :appearances) do
-    = link_to admin_appearances_path, title: 'Appearances' do
-      %span
-        Appearance
-
-  = nav_link(controller: :applications) do
-    = link_to admin_applications_path, title: 'Applications' do
-      %span
-        Applications
-
-  = nav_link(controller: :services) do
-    = link_to admin_application_settings_services_path, title: 'Service Templates' do
-      %span
-        Service Templates
-
-  = nav_link(controller: :labels) do
-    = link_to admin_labels_path, title: 'Labels' do
-      %span
-        Labels
+  %ul.nav-links.scrolling-tabs
+    %li.fade-left
+      = icon('arrow-left')
+    = nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do
+      = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
+        %span
+          Overview
+    = nav_link(controller: %w(background_jobs logs health_check)) do
+      = link_to admin_background_jobs_path, title: 'Monitoring' do
+        %span
+          Monitoring
+    = nav_link(controller: :broadcast_messages) do
+      = link_to admin_broadcast_messages_path, title: 'Messages' do
+        %span
+          Messages
+    = nav_link(controller: :hooks) do
+      = link_to admin_hooks_path, title: 'Hooks' do
+        %span
+          System Hooks
 
-  = nav_link(controller: :abuse_reports) do
-    = link_to admin_abuse_reports_path, title: "Abuse Reports" do
-      %span
-        Abuse Reports
-        %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
+    = nav_link(controller: :applications) do
+      = link_to admin_applications_path, title: 'Applications' do
+        %span
+          Applications
 
-  - if askimet_enabled?
-    = nav_link(controller: :spam_logs) do
-      = link_to admin_spam_logs_path, title: "Spam Logs" do
+    = nav_link(controller: :abuse_reports) do
+      = link_to admin_abuse_reports_path, title: "Abuse Reports" do
         %span
-          Spam Logs
+          Abuse Reports
+          %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
 
-  = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
-    = link_to admin_application_settings_path, title: 'Settings' do
-      %span
-        Settings
-  .fade-right
+    - if askimet_enabled?
+      = nav_link(controller: :spam_logs) do
+        = link_to admin_spam_logs_path, title: "Spam Logs" do
+          %span
+            Spam Logs
+    %li.fade-right
+      = icon('arrow-right')
diff --git a/app/views/layouts/nav/_admin_settings.html.haml b/app/views/layouts/nav/_admin_settings.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..38e9b80d129c38b3979a13d33cdc3dffd3ebf2b4
--- /dev/null
+++ b/app/views/layouts/nav/_admin_settings.html.haml
@@ -0,0 +1,31 @@
+.controls
+  .dropdown.admin-settings-dropdown
+    %a.dropdown-new.btn.btn-default{href: '#', 'data-toggle' => 'dropdown'}
+      = icon('cog')
+      = icon('caret-down')
+    %ul.dropdown-menu.dropdown-menu-align-right
+      = nav_link(controller: :deploy_keys) do
+        = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
+          %span
+            Deploy Keys
+
+      = nav_link(controller: :services) do
+        = link_to admin_application_settings_services_path, title: 'Service Templates' do
+          %span
+            Service Templates
+
+      = nav_link(controller: :labels) do
+        = link_to admin_labels_path, title: 'Labels' do
+          %span
+            Labels
+
+      = nav_link(controller: :appearances) do
+        = link_to admin_appearances_path, title: 'Appearances' do
+          %span
+            Appearance
+
+      %li.divider
+      = nav_link(controller: :application_settings) do
+        = link_to admin_application_settings_path, title: 'Settings' do
+          %span
+            Settings
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 66361a644dd1534d3f68548d4bc7928c3b4dc26b..f7aa9fab7cfc012ca44edab25bff8b7a0ea1afe3 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -2,7 +2,8 @@
   = render 'layouts/nav/group_settings'
 
   %ul.nav-links.scrolling-tabs
-    .fade-left
+    %li.fade-left
+      = icon('arrow-left')
     = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
       = link_to group_path(@group), title: 'Home' do
         %span
@@ -31,4 +32,5 @@
       = link_to group_group_members_path(@group), title: 'Members' do
         %span
           Members
-    .fade-right
+    %li.fade-right
+      = icon('arrow-right')
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index dac46648b9f5652741e3748c324437cb432a750b..3a24b09ab7eda3ec0e3b99186d3cf15f4255e56a 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,16 +1,22 @@
 - if current_user
-  - if access = @group.users.find_by(id: current_user.id)
-    .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?(current_user, :admin_group, @group)
-            = nav_link(path: 'groups#projects') do
-              = link_to projects_group_path(@group), title: 'Projects' do
-                Projects
-            %li.divider
-            %li
-              = link_to edit_group_path(@group) do
-                Edit Group
+  - can_edit = can?(current_user, :admin_group, @group)
+  - member = @group.members.non_request.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
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index bb6f14a62259706636b5740d574e8e8a4d04a063..44ea939b7e4c305726900996f719c0698c222fca 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,5 +1,6 @@
 %ul.nav-links.scrolling-tabs
-  .fade-left
+  %li.fade-left
+    = icon('arrow-left')
   = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
     = link_to profile_path, title: 'Profile Settings' do
       %span
@@ -43,4 +44,5 @@
     = link_to audit_log_profile_path, title: 'Audit Log' do
       %span
         Audit Log
-  .fade-right
+  %li.fade-right
+    = icon('arrow-right')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 39ea4920ccc5a9f99c11f80176a5bf5406c7e677..27e840df5036417e7104b77ef8acc040cae750d6 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -5,19 +5,20 @@
         = icon('cog')
         = icon('caret-down')
       %ul.dropdown-menu.dropdown-menu-align-right
-        - is_project_member = @project.users.exists?(current_user.id)
-        - access = @project.team.max_member_access(current_user.id)
         - can_edit = can?(current_user, :admin_project, @project)
+        -# We don't use @project.team.find_member because it searches for group members too...
+        - member = @project.members.non_request.find_by(user_id: current_user.id)
+        - can_leave = member && can?(current_user, :destroy_project_member, member)
 
-        = render 'layouts/nav/project_settings', access: access, can_edit: can_edit
+        = render 'layouts/nav/project_settings', can_edit: can_edit
 
-        - if can_edit || is_project_member
+        - if can_edit || can_leave
           %li.divider
           - if can_edit
             %li
               = link_to edit_project_path(@project) do
                 Edit Project
-          - if is_project_member
+          - if can_leave
             %li
               = link_to polymorphic_path([:leave, @project, :members]),
                 data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do
@@ -25,7 +26,8 @@
 
 %div{ class: nav_control_class }
   %ul.nav-links.scrolling-tabs
-    .fade-left
+    %li.fade-left
+      = icon('arrow-left')
     = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
       = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
         %span
@@ -38,9 +40,9 @@
 
     - if project_nav_tab? :files
       = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
-        = link_to project_files_path(@project), title: 'Code',  class: 'shortcuts-tree' do
+        = link_to project_files_path(@project), title: 'Repository',  class: 'shortcuts-tree' do
           %span
-            Code
+            Repository
 
     - if project_nav_tab? :pipelines
       = nav_link(controller: [:pipelines, :builds, :environments]) do
@@ -109,4 +111,5 @@
       %li.hidden
         = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
           Commits
-    .fade-right
+    %li.fade-right
+      = icon('arrow-right')
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 13d32bd1354d9bc83144141fe7f4d6244f29fceb..51a54b4f262719f5c126f0c7c3d03c45271fcd52 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -3,7 +3,7 @@
     = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
       %span
         Members
-- if access && can_edit
+- if can_edit
   - if @project.allowed_to_share_with_group?
     = nav_link(controller: :group_links) do
       = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do
diff --git a/app/views/notify/project_was_not_exported_email.html.haml b/app/views/notify/project_was_not_exported_email.html.haml
index c9e9ade2cf1364c12462c1b1d93c21d88cd16a0f..c888da29c17de6b77745d2587e3b9bf98af446d8 100644
--- a/app/views/notify/project_was_not_exported_email.html.haml
+++ b/app/views/notify/project_was_not_exported_email.html.haml
@@ -6,4 +6,4 @@
   %ul
   - @errors.each do |error|
     %li
-      error
+      #{error}
diff --git a/app/views/notify/project_was_not_exported_email.text.erb b/app/views/notify/project_was_not_exported_email.text.erb
deleted file mode 100644
index a07f6edacf7aa8093f991375a295fbe7169dc1db..0000000000000000000000000000000000000000
--- a/app/views/notify/project_was_not_exported_email.text.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-Project <%= @project.name %> couldn't be exported.
-
-The errors we encountered were:
-
-- @errors.each do |error|
-<%= error %>
\ No newline at end of file
diff --git a/app/views/notify/project_was_not_exported_email.text.haml b/app/views/notify/project_was_not_exported_email.text.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b27cb620b9e7d0f88da069943d3fcea6c677a010
--- /dev/null
+++ b/app/views/notify/project_was_not_exported_email.text.haml
@@ -0,0 +1,6 @@
+= "Project #{@project.name} couldn't be exported."
+
+= "The errors we encountered were:"
+
+- @errors.each do |error|
+  #{error}
\ No newline at end of file
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 5afd83a522e56acf6302f1ad1ab9ecf9c9498545..f77738f97f54bcc279c76143fbca8a72f41469cd 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -28,7 +28,7 @@
       = label_tag :global_notification_level, "Global notification level", class: "label-light"
       %br
       .clearfix
-      .form-group.pull-left
+      .form-group.pull-left.global-notification-setting
         = render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true
 
       .clearfix
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 28a28282fd3b3c51abaeb9c776ee39be6e4f2ad3..ca6714ef42b18c69f26f2df3d82f2ed0478b8f01 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -14,8 +14,17 @@
           %span This is a confidential issue. Your comment will not be visible to the public.
           
       %li.pull-right
-        %button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
-          Go full screen
+        .toolbar-group
+          = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
+          = markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" })
+          = markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
+          = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`" }, title: "Insert code" })
+          = markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" })
+          = markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
+          = markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
+        .toolbar-group
+          %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
+            =icon("arrows-alt fw")
 
   .md-write-holder
     = yield
@@ -24,7 +33,7 @@
   - if defined?(referenced_users) && referenced_users
     %div.referenced-users.hide
       %span
-        = icon('exclamation-triangle')
+        = icon("exclamation-triangle")
         You are about to add
         %strong
           %span.js-referenced-users-count 0
diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml
index ee63bc55a303f622cfeaa633a9d1be94f005c56e..ac80951dd4fdb56c38393b8f8ca95a92f1c9dd7c 100644
--- a/app/views/projects/badges/index.html.haml
+++ b/app/views/projects/badges/index.html.haml
@@ -7,7 +7,7 @@
       %b Builds badge &middot;
       = @build_badge.to_html
       .pull-right
-        = render 'shared/ref_switcher', destination: 'badges'
+        = render 'shared/ref_switcher', destination: 'badges', align_right: true
     .panel-body
       .row
         .col-md-2.text-center
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index ae89637df60c0f187c0f2b4af1e04d75782cb97d..29c7d45074a2241d5f20bb0a78b4443a0cec8184 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -17,6 +17,8 @@
         = dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
       .gitignore-selector.js-gitignore-selector-wrap.hidden
         = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+      .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
+        = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
       .encoding-selector
         = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
 
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b8d8758fd2bb1eff19e349a301db2ff7b2b3b9e1..e38d1ff5ff05a7e76552ad95a3d4e338b6aaaa58 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -24,8 +24,8 @@
         %span.label.label-warning stuck
 
       %p.commit-title
-        - if commit_data = pipeline.commit_data
-          = link_to_gfm truncate(commit_data.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message"
+        - if commit = pipeline.commit
+          = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message"
         - else
           Cant find HEAD commit for this branch
 
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index a959b34a5390d93aab2d47c4ca416fb477bf54b1..929496f81d887751a7172ad3be0cf1ae342e5452 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -10,29 +10,30 @@
 = cache(cache_key) do
   %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
     = commit_author_avatar(commit, size: 36)
-    .commit-row-title
-      %span.item-title
-        = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
-        %span.commit-row-message.visible-xs-inline
-          &middot;
-          = commit.short_id
-        - if commit.status
-          = render_commit_status(commit, cssclass: 'visible-xs-inline')
-        - if commit.description?
-          %a.text-expander.hidden-xs.js-toggle-button ...
+    .commit-info-block
+      .commit-row-title
+        %span.item-title
+          = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+          %span.commit-row-message.visible-xs-inline
+            &middot;
+            = commit.short_id
+          - if commit.status
+            = render_commit_status(commit, cssclass: 'visible-xs-inline')
+          - if commit.description?
+            %a.text-expander.hidden-xs.js-toggle-button ...
 
-      .commit-actions.hidden-xs
-        - if commit.status
-          = render_commit_status(commit, cssclass: 'btn btn-transparent')
-        = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent')
-        = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
-        = link_to_browse_code(project, commit)
+        .commit-actions.hidden-xs
+          - if commit.status
+            = render_commit_status(commit, cssclass: 'btn btn-transparent')
+          = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent')
+          = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
+          = link_to_browse_code(project, commit)
 
-    - if commit.description?
-      %pre.commit-row-description.js-toggle-content
-        = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
+      - if commit.description?
+        %pre.commit-row-description.js-toggle-content
+          = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
 
-    .commit-row-info
-      = commit_author_link(commit, avatar: false, size: 24)
-      authored
-      #{time_ago_with_tooltip(commit.committed_date)}
+      .commit-row-info
+        = commit_author_link(commit, avatar: false, size: 24)
+        authored
+        #{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index c8aa849c2178efa5cd4d3fd7ce04bfb792290658..54dab4bff07c5bc0eb8df386c1041c642a42b128 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,7 +1,8 @@
 .scrolling-tabs-container
   .nav-links.sub-nav.scrolling-tabs
     %ul{ class: (container_class) }
-      .fade-left
+      %li.fade-left
+        = icon('arrow-left')
       = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
         = link_to project_files_path(@project) do
           Files
@@ -25,4 +26,5 @@
       = nav_link(controller: [:tags, :releases]) do
         = link_to namespace_project_tags_path(@project.namespace, @project) do
           Tags
-      .fade-right
+      %li.fade-right
+        = icon('arrow-right')
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index c322942aeba0fb4ca75b1276d16036c6c3742942..b22285c11e0df9b55c6681dde869c927460c4bff 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -3,7 +3,7 @@
 = render "projects/commits/head"
 
 %div{ class: (container_class) }
-  .row-content-block.second-block.content-component-block
+  .sub-header-block
     Compare branches, tags or commit ranges.
     %br
     Fill input field with commit id like
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index cdc34f51d6df27fe96e6317f2a91baa2340d80c8..f4ec7b767f63e1c67469f13d747454753d265502 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -1,24 +1,24 @@
+- @no_container = true
 - page_title "#{params[:from]}...#{params[:to]}"
 = render "projects/commits/head"
 
+%div{ class: (container_class) }
+  .sub-header-block.no-bottom-space
+    = render "form"
 
-.row-content-block
-  = render "form"
-
-- if @commits.present?
-  .prepend-top-default
+  - if @commits.present?
     = render "projects/commits/commit_list"
     = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs
-- else
-  .light-well.prepend-top-default
-    .center
-      %h4
-        There isn't anything to compare.
-      %p.slead
-        - if params[:to] == params[:from]
-          %span.label-branch #{params[:from]}
-          and
-          %span.label-branch #{params[:to]}
-          are the same.
-        - else
-          You'll need to use different branch names to get a valid comparison.
+  - else
+    .light-well
+      .center
+        %h4
+          There isn't anything to compare.
+        %p.slead
+          - if params[:to] == params[:from]
+            %span.label-branch #{params[:from]}
+            and
+            %span.label-branch #{params[:to]}
+            are the same.
+          - else
+            You'll need to use different branch names to get a valid comparison.
diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml
index f35faa6afb5529716c06ad6e33bab17095644c40..10822b6184cf761d73f9e52499db0e0216611721 100644
--- a/app/views/projects/container_registry/_tag.html.haml
+++ b/app/views/projects/container_registry/_tag.html.haml
@@ -3,9 +3,9 @@
     = escape_once(tag.name)
     = clipboard_button(clipboard_text: "docker pull #{tag.path}")
   %td
-    - if layer = tag.layers.first
-      %span.has-tooltip{ title: "#{layer.revision}" }
-        = layer.short_revision
+    - if tag.revision
+      %span.has-tooltip{ title: "#{tag.revision}" }
+        = tag.short_revision
     - else
       \-
   %td
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index ae9e77e7d89d211f5bef0a118ce506d23c48bc55..a03f117291f6d12eaed18e1867551ab6279ac1c8 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -3,16 +3,24 @@
 = render "projects/pipelines/head"
 
 %div{ class: (container_class) }
-  - if can?(current_user, :create_environment, @project)
+  - if can?(current_user, :create_environment, @project) && !@environments.blank?
     .top-area
       .nav-controls
         = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
           New environment
 
   - if @environments.blank?
-    %ul.content-list.environments
-      %li.nothing-here-block
-        No environments to show
+    .blank-state.blank-state-no-icon
+      %h2.blank-state-title
+        You don't have any environments right now.
+      %p.blank-state-text
+        Environments are places where code gets deployed, such as staging or production.
+        %br
+        = succeed "." do
+          = link_to "Read more about environments", help_page_path("ci", "environments")
+      - if can?(current_user, :create_environment, @project)
+        = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
+          New environment
   - else
     .table-holder
       %table.table.environments
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index 54465828ba9efebafe63b8409ee8453fb66b143e..da325efecd24a9f15a019549cbdf9185effd9761 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -4,6 +4,9 @@
   .col-lg-3
     %h4.prepend-top-0
       New Environment
-    %p Environments allow you to track deployments of your application
+    %p
+      Environments allow you to track deployments of your application
+      = succeed "." do
+        = link_to "Read more about environments", help_page_path("ci", "environments")
 
   = render 'form'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 069b77b5adf9d800aad2defd4729780e6378312a..4c15e2759d6033b60284d406e8b57f414e93a430 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -13,10 +13,14 @@
           = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete
 
   - if @deployments.blank?
-    %ul.content-list.environments
-      %li.nothing-here-block
-        No deployments for
-        %strong= @environment.name
+    .blank-state.blank-state-no-icon
+      %h2.blank-state-title
+        You don't have any deployments right now.
+      %p.blank-state-text
+        Define environments in the deploy stage(s) in
+        %code .gitlab-ci.yml
+        to track deployments here.
+      = link_to "Read more", help_page_path("ci", "environments"), class: "btn btn-success"
   - else
     .table-holder
       %table.table.environments
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index 4bcf2d9d5332ea3b6a59eb850c3a7bda9ca7c2f0..dbe9ddfde2f0877b097b715e1e43431b1dd9cf37 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -40,9 +40,3 @@
 
 
 = render 'projects', projects: @forks
-
-- if @private_forks_count > 0
-  .private-forks-notice
-    = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
-    %strong= pluralize(@private_forks_count, 'private fork')
-    %span you have no access to.
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 8becaea246f2912f008418bc3a60afd0a8a2c29a..a388d9a0a61fc9df3b0a3dec0a83c40f31dcc018 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -1,12 +1,14 @@
-- page_specific_javascripts asset_path("graphs/application.js")
-%ul.nav-links
-  = 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
+.nav-links.sub-nav
+  %ul{ class: (container_class) }
+
+    - page_specific_javascripts asset_path("graphs/application.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
diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml
index 19ccc125ea825f83b07583236c82880f9357b8d0..e695d3ae369ca92585e9288acbd3d3dfe376287e 100644
--- a/app/views/projects/graphs/ci.html.haml
+++ b/app/views/projects/graphs/ci.html.haml
@@ -1,15 +1,18 @@
+- @no_container = true
 - page_title "Continuous Integration", "Graphs"
 = render 'head'
-.row-content-block.append-bottom-default
-  .oneline
-    A collection of graphs for Continuous Integration
 
-#charts.ci-charts
-  .row
-    .col-md-6
-      = render 'projects/graphs/ci/overall'
-    .col-md-6
-      = render 'projects/graphs/ci/build_times'
+%div{ class: (container_class) }
+  .sub-header-block
+    .oneline
+      A collection of graphs for Continuous Integration
 
-  %hr
-  = render 'projects/graphs/ci/builds'
+  #charts.ci-charts
+    .row
+      .col-md-6
+        = render 'projects/graphs/ci/overall'
+      .col-md-6
+        = render 'projects/graphs/ci/build_times'
+
+    %hr
+    = render 'projects/graphs/ci/builds'
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index d9b2fb6c065d8f06bb64487b991205ec6557964c..0daffe68f6fd55fe7c001c983d9fbc708a809fe9 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -1,52 +1,54 @@
+- @no_container = true
 - page_title "Commits", "Graphs"
 = render 'head'
 
-.row-content-block.append-bottom-default
-  .tree-ref-holder
-    = render 'shared/ref_switcher', destination: 'graphs_commits'
-  %ul.breadcrumb.repo-breadcrumb
-    = commits_breadcrumbs
+%div{ class: (container_class) }
+  .sub-header-block
+    .tree-ref-holder
+      = render 'shared/ref_switcher', destination: 'graphs_commits'
+    %ul.breadcrumb.repo-breadcrumb
+      = commits_breadcrumbs
 
-%p.lead
-  Commit statistics for
-  %strong #{@ref}
-  #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
+  %p.lead
+    Commit statistics for
+    %strong #{@ref}
+    #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
 
-.row
-  .col-md-6
-    %ul
-      %li
-        %p.lead
-          %strong #{@commits_graph.commits.size}
-          commits during
-          %strong #{@commits_graph.duration}
-          days
-      %li
-        %p.lead
-          Average
-          %strong #{@commits_graph.commit_per_day}
-          commits per day
-      %li
-        %p.lead
-          Contributed by
-          %strong #{@commits_graph.authors}
-          authors
-  .col-md-6
-    %div
-      %p.slead
-        Commits per day of month
-      %canvas#month-chart
-.row
-  .col-md-6
-    %div
-      %p.slead
-        Commits per day hour (UTC)
-      %canvas#hour-chart
-  .col-md-6
-    %div
-      %p.slead
-        Commits per weekday
-      %canvas#weekday-chart
+  .row
+    .col-md-6
+      %ul
+        %li
+          %p.lead
+            %strong #{@commits_graph.commits.size}
+            commits during
+            %strong #{@commits_graph.duration}
+            days
+        %li
+          %p.lead
+            Average
+            %strong #{@commits_graph.commit_per_day}
+            commits per day
+        %li
+          %p.lead
+            Contributed by
+            %strong #{@commits_graph.authors}
+            authors
+    .col-md-6
+      %div
+        %p.slead
+          Commits per day of month
+        %canvas#month-chart
+  .row
+    .col-md-6
+      %div
+        %p.slead
+          Commits per day hour (UTC)
+        %canvas#hour-chart
+    .col-md-6
+      %div
+        %p.slead
+          Commits per weekday
+        %canvas#weekday-chart
 
 :javascript
   var responsiveChart = function (selector, data) {
diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml
index 249c16f4709619ebc3d1c2c52200457330f6b877..6d97f552a8e83f28b64b6fd1c806405d2ece3c48 100644
--- a/app/views/projects/graphs/languages.html.haml
+++ b/app/views/projects/graphs/languages.html.haml
@@ -1,24 +1,26 @@
+- @no_container = true
 - page_title "Languages", "Graphs"
 = render 'head'
 
-.row-content-block.append-bottom-default
-  .oneline
-    Programming languages used in this repository
+%div{ class: (container_class) }
+  .sub-header-block
+    .oneline
+      Programming languages used in this repository
 
-.row
-  .col-md-8
-    %canvas#languages-chart{ height: 400 }
-  .col-md-4
-    %ul.bordered-list
-      - @languages.each do |language|
-        %li
-          %span{ style: "color: #{language[:color]}" }
-            = icon('circle')
-          &nbsp;
-          = language[:label]
-          .pull-right
-            = language[:value]
-            \%
+  .row
+    .col-md-8
+      %canvas#languages-chart{ height: 400 }
+    .col-md-4
+      %ul.bordered-list
+        - @languages.each do |language|
+          %li
+            %span{ style: "color: #{language[:color]}" }
+              = icon('circle')
+            &nbsp;
+            = language[:label]
+            .pull-right
+              = language[:value]
+              \%
 
 :javascript
   var data = #{@languages.to_json};
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 33970e7b90912cee3848f9f6653de60441fe25ad..9f7e2a361ff7ddca93806b1f4b02d8659816a53c 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,29 +1,31 @@
+- @no_container = true
 - page_title "Contributors", "Graphs"
 = render 'head'
 
-.row-content-block.append-bottom-default
-  .tree-ref-holder
-    = render 'shared/ref_switcher', destination: 'graphs'
-  %ul.breadcrumb.repo-breadcrumb
-    = commits_breadcrumbs
-
-.loading-graph
-  .center
-    %h3.page-title
-      %i.fa.fa-spinner.fa-spin
-      Building repository graph.
-    %p.slead Please wait a moment, this page will automatically refresh when ready.
-
-.stat-graph.hide
-  .header.clearfix
-    %h3#date_header.page-title
-    %p.light
-      Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits.
-    %input#brush_change{:type => "hidden"}
-  .graphs
-    #contributors-master
-    #contributors.clearfix
-      %ol.contributors-list.clearfix
+%div{ class: (container_class) }
+  .sub-header-block
+    .tree-ref-holder
+      = render 'shared/ref_switcher', destination: 'graphs'
+    %ul.breadcrumb.repo-breadcrumb
+      = commits_breadcrumbs
+
+  .loading-graph
+    .center
+      %h3.page-title
+        %i.fa.fa-spinner.fa-spin
+        Building repository graph.
+      %p.slead Please wait a moment, this page will automatically refresh when ready.
+
+  .stat-graph.hide
+    .header.clearfix
+      %h3#date_header.page-title
+      %p.light
+        Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits.
+      %input#brush_change{:type => "hidden"}
+    .graphs.row
+      #contributors-master
+      #contributors.clearfix
+        %ol.contributors-list.clearfix
 
 
 
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 393998f15b9b5f94b98e81360346fec8b948084b..53dd300c35c081945b6b4e2097bd04ba1fdfae08 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -1,8 +1,8 @@
 - content_for :note_actions do
   - 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-grouped 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"}
+      = 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?
-      = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-grouped 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"}
+      = 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"}
 
 #notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index e4ab064eda8ad12242dcfca482f0b9296e39847c..593af319a4797f60209eafc429249e42a568b9fb 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -15,5 +15,5 @@
               = check_box_tag :filter_ref, 1, @options[:filter_ref]
               %span Begin with the selected commit
 
-    .network-graph{ data: { url: '#{escape_javascript(@url)}', commit_url: '#{escape_javascript(@commit_url)}', ref: '#{escape_javascript(@ref)}', commit_id: '#{escape_javascript(@commit.id)}' } }
+    .network-graph{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } }
       = spinner nil, true
diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml
index 0b00204340863dd151e46eb3b6ae986d9d313299..7d1cbc62e86dc83ecfee4471cd42529363fa4f4f 100644
--- a/app/views/projects/notes/_hints.html.haml
+++ b/app/views/projects/notes/_hints.html.haml
@@ -5,4 +5,4 @@
     is supported
   %button.toolbar-button.markdown-selector{ type: 'button', tabindex: '-1' }
     = icon('file-image-o', class: 'toolbar-button-icon')
-    Attach a file
+    Attach a file
\ No newline at end of file
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index bcdbff080116aea18b6dff84d739766a1f732eb2..c04d291412cf9bd4b9d42fc69aaf5bb2ed02129c 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -18,9 +18,9 @@
             = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
         .note-actions
           - access = note.project.team.human_max_access(note.author.id)
-          - if access
+          - if access and not note.system
             %span.note-role.hidden-xs= access
-          - if current_user
+          - if current_user and not note.system
             = 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')
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index cb6136c215a9158f1eb1355e998609bc039474fe..e783d8c72c52dd82190f1b21c48844555e7910fb 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -2,8 +2,7 @@
   .panel-heading
     %strong #{@group.name}
     group members
-    %small
-      (#{members.count})
+    %span.badge= members.size
     - if can?(current_user, :admin_group_member, @group)
       .controls
         = link_to 'Manage group members',
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
index 952844acefc3997e195d30ae05f869199a4f840a..840b57c2e6364b6af6db350808a17862f914b206 100644
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ b/app/views/projects/project_members/_shared_group_members.html.haml
@@ -1,6 +1,7 @@
 - @project_group_links.each do |group_links|
   - shared_group = group_links.group
-  - shared_group_users_count = group_links.group.group_members.count
+  - shared_group_members = shared_group.members.non_request
+  - shared_group_users_count = shared_group_members.size
   .panel.panel-default
     .panel-heading
       Shared with
@@ -15,7 +16,7 @@
             Edit group members
     %ul.content-list
       = render partial: 'shared/members/member',
-               collection: shared_group.group_members.order(access_level: :desc).limit(20),
+               collection: shared_group_members.order(access_level: :desc).limit(20),
                as: :member,
                locals: { show_controls: false, show_roles: false }
       - if shared_group_users_count > 20
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 03207614258b08c3f914c09cb7ebd5c9f382e168..b0bfdd235f7b24277909ee78f76705387fe8fe96 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -2,8 +2,7 @@
   .panel-heading
     %strong #{@project.name}
     project members
-    %small
-      (#{members.count})
+    %span.badge= members.size
     .controls
       = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form'  do
         .form-group
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 357ccccaf1d73b99c9f1d5b8629b3a2eccaaf434..a2026c41d014df9c90bf5f75a90819b9a35df365 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -18,7 +18,7 @@
   = render 'team', members: @project_members.non_request
 
   - if @group
-    = render "group_members", members: @group_members
+    = render "group_members", members: @group_members.non_request
 
   - if @project_group_links.any? && @project.allowed_to_share_with_group?
     = render "shared_group_members"
diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
index d62f5c8f131e0c40d06c009adae2d47b345da7ac..c45a9d4f81feb53f83acde06f43f7b8db224fc0a 100644
--- a/app/views/projects/runners/_form.html.haml
+++ b/app/views/projects/runners/_form.html.haml
@@ -12,6 +12,12 @@
       .checkbox
         = f.check_box :run_untagged
         %span.light Indicates whether this runner can pick jobs without tags
+  .form-group
+    = label :locked, 'Lock to current projects', class: 'control-label'
+    .col-sm-10
+      .checkbox
+        = f.check_box :locked
+        %span.light When a runner is locked, it cannot be assigned to other projects
   .form-group
     = label_tag :token, class: 'control-label' do
       Token
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 96e2aac451f027ba4b780d9eb775b1f9f2993184..852258577584a2aa773b79f732976a1f77a19f4d 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -2,8 +2,10 @@
   %h4
     = runner_status_icon(runner)
     %span.monospace
-      - if @runners.include?(runner)
+      - if @project_runners.include?(runner)
         = link_to runner.short_sha, runner_path(runner)
+        - if runner.locked?
+          = icon('lock', class: 'has-tooltip', title: 'Locked to current projects')
         %small
           = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
             %i.fa.fa-edit.btn
@@ -11,7 +13,7 @@
         = runner.short_sha
 
     .pull-right
-      - if @runners.include?(runner)
+      - if @project_runners.include?(runner)
         - if runner.belongs_to_one_project?
           = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
         - else
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 8ae9f0d95f7f7c6bccf59b19a4d83b20570c5d69..d469dda5b81edbda197196245e6739e2d6583847 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -17,13 +17,13 @@
       Start runner!
 
 
-- if @runners.any?
+- if @project_runners.any?
   %h4.underlined-title Runners activated for this project
   %ul.bordered-list.activated-specific-runners
-    = render partial: 'runner', collection: @runners, as: :runner
+    = render partial: 'runner', collection: @project_runners, as: :runner
 
-- if @specific_runners.any?
+- if @assignable_runners.any?
   %h4.underlined-title Available specific runners
   %ul.bordered-list.available-specific-runners
-    = render partial: 'runner', collection: @specific_runners, as: :runner
-  = paginate @specific_runners
+    = render partial: 'runner', collection: @assignable_runners, as: :runner
+  = paginate @assignable_runners
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
index f24e1b9144e8e7393398cadf8aad605433908938..61b99f35d7465e5c5dd39f772335d2a6f24ad9e4 100644
--- a/app/views/projects/runners/show.html.haml
+++ b/app/views/projects/runners/show.html.haml
@@ -22,6 +22,9 @@
     %tr
       %td Can run untagged jobs
       %td= @runner.run_untagged? ? 'Yes' : 'No'
+    %tr
+      %td Locked to this project
+      %td= @runner.locked? ? 'Yes' : 'No'
     %tr
       %td Tags
       %td
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index e9ca46a74bfeba1dea804d96b0011a787bdc5452..15f0d85194b899ee1da71875e0a30b31603891bd 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -57,6 +57,10 @@
           %li.missing
             = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
               Add Contribution guide
+        - unless @repository.gitlab_ci_yml
+          %li.missing
+            = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
+              Set Up CI
 
 - if @repository.commit
   .content-block.second-block.white
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 4faa547769b54191430adb92f3d1f45b24066057..4ea75dbbf0cdf4d0b738b7cdf62ac8d6b64214f4 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,4 +1,7 @@
 - if (@page && @page.persisted?)
+  - if can?(current_user, :create_wiki, @project)
+    = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+      New Page
   = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
     Page History
   - if can?(current_user, :create_wiki, @project)
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index 988fe024e2877a2d8593d15bb8d324f5e66b3c31..f8ea479e0b113e90f0285b678037b891e7f97602 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,5 +1,5 @@
-.top-area
-  %ul.nav-links
+.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)
 
@@ -10,9 +10,4 @@
       = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
         Git Access
 
-  .nav-controls
-    - if can?(current_user, :create_wiki, @project)
-      = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
-        New Page
-
-= render 'projects/wikis/new'
+  = render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 919daf0a7b2be658a4bde51c7d18a9132f91092c..4f8abcdc8e1a167b7b9908d3e011941ee4317785 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -1,14 +1,17 @@
-%div#modal-new-wiki.modal
-  .modal-dialog
-    .modal-content
-      .modal-header
-        %a.close{href: "#", "data-dismiss" => "modal"} ×
-        %h3.page-title New Wiki Page
-      .modal-body
-        %form.new-wiki-page
-          .form-group
-            = label_tag :new_wiki_path do
-              %span Page slug
-            = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
-          .form-actions
-            = button_tag 'Create Page', class: 'build-new-wiki btn btn-create'
+- @no_container = true
+
+%div{ class: (container_class) }
+  %div#modal-new-wiki.modal
+    .modal-dialog
+      .modal-content
+        .modal-header
+          %a.close{href: "#", "data-dismiss" => "modal"} ×
+          %h3.page-title New Wiki Page
+        .modal-body
+          %form.new-wiki-page
+            .form-group
+              = label_tag :new_wiki_path do
+                %span Page slug
+              = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
+            .form-actions
+              = button_tag 'Create Page', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index cbd69ee1a73c31d7a46b09f071cac365d75a7008..bf5d09d50c20ab993b43624fe0e039fcf78c7a01 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,19 +1,24 @@
+- @no_container = true
 - page_title "Edit", @page.title.capitalize, "Wiki"
 = render 'nav'
 
-.top-area
-  .nav-text.wiki-page
-    %strong
-      - if @page.persisted?
-        = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
-      - else
-        = @page.title.capitalize
-    %span.light
-      &middot;
-      Edit Page
+%div{ class: (container_class) }
+  .top-area
+    .nav-text
+      %strong
+        - if @page.persisted?
+          = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+        - else
+          = @page.title.capitalize
+      %span.light
+        &middot;
+        Edit Page
 
-  .nav-controls
-    = render 'main_links'
+    .nav-controls
+      - if can?(current_user, :create_wiki, @project)
+        = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+          New Page
+      = render 'main_links'
 
 
-= render 'form'
+  = render 'form'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index ccceab6155e8aaa309ee6e16eac00f0dacf2e110..6caf7230f350d6ee4c3f5f471947bf40e1ab1105 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,32 +1,34 @@
+- @no_container = true
 - page_title "Git Access", "Wiki"
 
 = render 'nav'
-.row-content-block
-  %span.oneline
-    Git access for
-    %strong= @project_wiki.path_with_namespace
+%div{ class: (container_class) }
+  .sub-header-block
+    %span.oneline
+      Git access for
+      %strong= @project_wiki.path_with_namespace
 
-  .pull-right
-    = render "shared/clone_panel", project: @project_wiki
+    .pull-right
+      = render "shared/clone_panel", project: @project_wiki
 
-.git-empty.prepend-top-default
-  %fieldset
-    %legend Install Gollum:
-    %pre.dark
-      :preserve
-        gem install gollum
+  .prepend-top-default
+    %fieldset
+      %legend Install Gollum:
+      %pre.dark
+        :preserve
+          gem install gollum
 
-    %legend Clone Your Wiki:
-    %pre.dark
-      :preserve
-        git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
-        cd #{h @project_wiki.path}
+      %legend Clone Your Wiki:
+      %pre.dark
+        :preserve
+          git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
+          cd #{h @project_wiki.path}
 
-    %legend Start Gollum And Edit Locally:
-    %pre.dark
-      :preserve
-        gollum
-        == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
-        >> Thin web server (v1.5.0 codename Knife)
-        >> Maximum connections set to 1024
-        >> Listening on 0.0.0.0:4567, CTRL+C to stop
+      %legend Start Gollum And Edit Locally:
+      %pre.dark
+        :preserve
+          gollum
+          == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
+          >> Thin web server (v1.5.0 codename Knife)
+          >> Maximum connections set to 1024
+          >> Listening on 0.0.0.0:4567, CTRL+C to stop
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 45460ed9f41ad2e1769f778dedea40baaed583b9..630ee35b70b9473104e764209db9a0f699ebee35 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,37 +1,37 @@
 - page_title "History", @page.title.capitalize, "Wiki"
 = render 'nav'
+%div{ class: (container_class) }
+  .top-area
+    .nav-text
+      %strong
+        = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+      %span.light
+        &middot;
+        History
 
-.top-area
-  .nav-text
-    %strong
-      = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
-    %span.light
-      &middot;
-      History
-
-.table-holder
-  %table.table
-    %thead
-      %tr
-        %th Page version
-        %th Author
-        %th Commit Message
-        %th Last updated
-        %th Format
-    %tbody
-      - @page.versions.each_with_index do |version, index|
-        - commit = version
+  .table-holder
+    %table.table
+      %thead
         %tr
-          %td
-            = link_to project_wiki_path_with_version(@project, @page,
-                                                     commit.id, index == 0) do
-              = truncate_sha(commit.id)
-          %td
-            = commit.author.name
-          %td
-            = commit.message
-          %td
-            #{time_ago_with_tooltip(version.authored_date)}
-          %td
-            %strong
-              = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
+          %th Page version
+          %th Author
+          %th Commit Message
+          %th Last updated
+          %th Format
+      %tbody
+        - @page.versions.each_with_index do |version, index|
+          - commit = version
+          %tr
+            %td
+              = link_to project_wiki_path_with_version(@project, @page,
+                                                       commit.id, index == 0) do
+                = truncate_sha(commit.id)
+            %td
+              = commit.author.name
+            %td
+              = commit.message
+            %td
+              #{time_ago_with_tooltip(version.authored_date)}
+            %td
+              %strong
+                = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 2f6162fa3c5f01d7ccb384e882d1e8c5ef072638..81d9f391c1c50f435282afe30a2a3dec4f474416 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,12 +1,14 @@
+- @no_container = true
 - page_title "Pages", "Wiki"
 
 = render 'nav'
 
-%ul.content-list
-  - @wiki_pages.each do |wiki_page|
-    %li
-      = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
-      %small (#{wiki_page.format})
-      .pull-right
-        %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
-= paginate @wiki_pages, theme: 'gitlab'
+%div{ class: (container_class) }
+  %ul.content-list
+    - @wiki_pages.each do |wiki_page|
+      %li
+        = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
+        %small (#{wiki_page.format})
+        .pull-right
+          %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
+  = paginate @wiki_pages, theme: 'gitlab'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 9166c0edb3b6e5af7e399803e975116f237a414d..76f9b1ecd762b7ddda0e9a6e4735331491754af9 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,24 +1,26 @@
+- @no_container = true
 - page_title @page.title.capitalize, "Wiki"
 = render 'nav'
 
-.top-area
-  .nav-text
-    %strong= @page.title.capitalize
+%div{ class: (container_class) }
+  .top-area
+    .nav-text
+      %strong= @page.title.capitalize
 
-    %span.wiki-last-edit-by
-      &middot;
-      last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+      %span.wiki-last-edit-by
+        &middot;
+        last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
 
-  .nav-controls
-    = render 'main_links'
+    .nav-controls
+      = render 'main_links'
 
-- if @page.historical?
-  .warning_message
-    This is an old version of this page.
-    You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
+  - if @page.historical?
+    .warning_message
+      This is an old version of this page.
+      You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
 
 
-.wiki-holder.prepend-top-default.append-bottom-default
-  .wiki
-    = preserve do
-      = render_wiki_content(@page)
+  .wiki-holder.prepend-top-default.append-bottom-default
+    .wiki
+      = preserve do
+        = render_wiki_content(@page)
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index 300550022130653b035a090fd4faffd7df44340f..aa18e6f236f0e9580564f6121c43e61eaa0d1cdb 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,7 +1,9 @@
 %ul.nav-links.event-filter.scrolling-tabs
-  .fade-left
+  %li.fade-left
+    = icon('arrow-left')
   = event_filter_link EventFilter.push, 'Push events'
   = event_filter_link EventFilter.merged, 'Merge events'
   = event_filter_link EventFilter.comments, 'Comments'
   = event_filter_link EventFilter.team, 'Team'
-  .fade-right
+  %li.fade-right
+    = icon('arrow-right')
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index eb2e1919e190541aae8b412ccab832b25dbfe79e..ea7162d4d63a54f1818ade14e298ff3fc9e85857 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,7 +1,14 @@
+- dropdown_toggle_text = @ref || @project.default_branch
 = form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do
-  = select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm"
   = hidden_field_tag :destination, destination
   - if defined?(path)
     = hidden_field_tag :path, path
   - @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-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"
+      = dropdown_content
+      = dropdown_loading
diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml
index b5963876034533e5ca288be635653559805e830b..e4bd2bdc265d4f40365293ac6a03bce8223feaf6 100644
--- a/app/views/shared/members/_requests.html.haml
+++ b/app/views/shared/members/_requests.html.haml
@@ -3,6 +3,6 @@
     .panel-heading
       %strong= membership_source.name
       access requests
-      %small= "(#{members.size})"
+      %span.badge= members.size
     %ul.content-list
       = render partial: 'shared/members/member', collection: members, as: :member
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 2e08bb2ac08444fa370176b199c2a76941c0afda..3a9dd37dc7d8a53c4b875d7db7658c71f7a48523 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -16,6 +16,12 @@
         = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
           avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
           forks: forks, show_last_commit_as_description: show_last_commit_as_description
+
+      - if @private_forks_count && @private_forks_count > 0
+        %li.project-row.private-forks-notice
+          = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
+          %strong= pluralize(@private_forks_count, 'private fork')
+          %span you have no access to.
     = paginate(projects, remote: remote, theme: "gitlab") if projects.respond_to? :total_pages
   - else
     .nothing-here-block No projects found
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 09ffc319065df5dad9c26eb764ac82f3b83ab9c4..c6dc1e4ab38da2b2a162c878665d82ef7bbbc1e9 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -215,7 +215,7 @@ Settings.gitlab.default_projects_features['container_registry'] = true if Settin
 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['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil?
 Settings.gitlab['restricted_signup_domains'] ||= []
-Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
+Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project]
 Settings.gitlab['trusted_proxies'] ||= []
 
 
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index f1eec674888e2f3f70ea84d9251403e4f9de4f96..7a2b9a7f6c1cfffb3fb51f6edd5b858bbd3931e6 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -23,6 +23,10 @@ Sidekiq.configure_server do |config|
   config['pool'] = Sidekiq.options[:concurrency] + 2
   ActiveRecord::Base.establish_connection(config)
   Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
+
+  # Avoid autoload issue such as 'Mail::Parsers::AddressStruct'
+  # https://github.com/mikel/mail/issues/912#issuecomment-214850355
+  Mail.eager_autoload!
 end
 
 Sidekiq.configure_client do |config|
diff --git a/config/routes.rb b/config/routes.rb
index de6094fa0edfdada440fd54816fe0fa2c33b68c2..e45293cdf7fd8db9dc15790c9c909579300c47ec 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -295,7 +295,7 @@ Rails.application.routes.draw do
           post :repository_check
         end
 
-        resources :runner_projects
+        resources :runner_projects, only: [:create, :destroy]
       end
     end
 
@@ -479,6 +479,7 @@ Rails.application.routes.draw do
         get :download_export
         get :autocomplete_sources
         get :activity
+        get :refs
       end
 
       scope module: :projects do
diff --git a/db/migrate/20160509091049_add_locked_to_ci_runner.rb b/db/migrate/20160509091049_add_locked_to_ci_runner.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3fbaef3b7f0ae87d39d78a1e02516da7c77a97ab
--- /dev/null
+++ b/db/migrate/20160509091049_add_locked_to_ci_runner.rb
@@ -0,0 +1,13 @@
+class AddLockedToCiRunner < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+  disable_ddl_transaction!
+
+  def up
+    add_column_with_default(:ci_runners, :locked, :boolean,
+                            default: false, allow_null: false)
+  end
+
+  def down
+    remove_column(:ci_runners, :locked)
+  end
+end
diff --git a/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bd0463886bc0a46bae6d07837cbe048f3f9d40e8
--- /dev/null
+++ b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb
@@ -0,0 +1,9 @@
+class SetMissingStageOnCiBuilds < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  def up
+    update_column_in_batches(:ci_builds, :stage, :test) do |table, query|
+      query.where(table[:stage].eq(nil))
+    end
+  end
+end
diff --git a/db/migrate/20160616102642_remove_duplicated_keys.rb b/db/migrate/20160616102642_remove_duplicated_keys.rb
new file mode 100644
index 0000000000000000000000000000000000000000..00a45d7fe7313b9b41caf4373bf2526e8e38739a
--- /dev/null
+++ b/db/migrate/20160616102642_remove_duplicated_keys.rb
@@ -0,0 +1,19 @@
+# rubocop:disable all
+class RemoveDuplicatedKeys < ActiveRecord::Migration
+  def up
+    select_all("SELECT fingerprint FROM #{quote_table_name(:keys)} GROUP BY fingerprint HAVING COUNT(*) > 1").each do |row|
+      fingerprint = connection.quote(row['fingerprint'])
+      execute(%Q{
+        DELETE FROM keys
+        WHERE fingerprint = #{fingerprint}
+        AND id != (
+          SELECT id FROM (
+            SELECT max(id) AS id
+            FROM keys
+            WHERE fingerprint = #{fingerprint}
+          ) max_ids
+        )
+      })
+    end
+  end
+end
diff --git a/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb b/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4bb4204cebda05cc4db931c711f8e2fd6f8d4563
--- /dev/null
+++ b/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb
@@ -0,0 +1,21 @@
+class RemoveKeysFingerprintIndexIfExists < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  disable_ddl_transaction!
+
+  # https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/250
+  # That MR was added on gitlab-ee so we need to check if the index
+  # already exists because we want to do is create an unique index instead.
+
+  def up
+    if index_exists?(:keys, :fingerprint)
+      remove_index :keys, :fingerprint
+    end
+  end
+
+  def down
+    unless index_exists?(:keys, :fingerprint)
+      add_concurrent_index :keys, :fingerprint
+    end
+  end
+end
diff --git a/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb b/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e35af38aac3a6cf111dbb826243864730d237f6a
--- /dev/null
+++ b/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb
@@ -0,0 +1,13 @@
+class AddUniqueIndexToKeysFingerprint < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :keys, :fingerprint, unique: true
+  end
+
+  def down
+    remove_index :keys, :fingerprint
+  end
+end
diff --git a/db/migrate/20160620115026_add_index_on_runners_locked.rb b/db/migrate/20160620115026_add_index_on_runners_locked.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dfa5110dea4b5ce80075c8c59573311a47f07144
--- /dev/null
+++ b/db/migrate/20160620115026_add_index_on_runners_locked.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 AddIndexOnRunnersLocked < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  disable_ddl_transaction!
+
+  def change
+    add_concurrent_index :ci_runners, :locked
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5a27e9d5cdc4e34f270c670cf6d15ab529a49a8e..7a8377f687c00aede850a1901fcd3414abdb3646 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: 20160616084004) do
+ActiveRecord::Schema.define(version: 20160620115026) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -287,9 +287,11 @@ ActiveRecord::Schema.define(version: 20160616084004) do
     t.string   "platform"
     t.string   "architecture"
     t.boolean  "run_untagged", default: true,  null: false
+    t.boolean  "locked",       default: false, null: false
   end
 
   add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
+  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
   add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"}
 
@@ -507,6 +509,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do
   end
 
   add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree
+  add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree
   add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
 
   create_table "label_links", force: :cascade do |t|
@@ -707,6 +710,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do
     t.integer  "level",       default: 0, null: false
     t.datetime "created_at",              null: false
     t.datetime "updated_at",              null: false
+    t.text     "events"
   end
 
   add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 5d89d0c9821ae3fb2fe33953a78e3d78ccac6a2a..f1283cea0ad16c114dd5b6065be6a9b850dd9221 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -3,17 +3,18 @@
 ## User documentation
 
 - [API](api/README.md) Automate GitLab via a simple and powerful API.
-- [CI](ci/README.md) GitLab Continuous Integration (CI) getting started, `.gitlab-ci.yml` options, and examples.
+- [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.
+- [Container Registry](container_registry/README.md) Learn how to use GitLab Container Registry.
 - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
 - [Importing to GitLab](workflow/importing/README.md).
+- [Importing and exporting projects between instances](user/project/settings/import_export.md).
 - [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
-- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab
+- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab.
 - [Permissions](permissions/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
 - [Profile Settings](profile/README.md)
 - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
 - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
-- [Container Registry](container_registry/README.md) Learn how to use GitLab Container Registry.
 - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
 - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
 - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
@@ -24,15 +25,15 @@
   external authentication with LDAP, SAML, CAS and additional Omniauth providers.
 - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough.
 - [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
+- [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.
 - [Libravatar](customization/libravatar.md) Use Libravatar 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
+- [Operations](operations/README.md) Keeping GitLab up and running.
 - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects.
-- [Repository checks](administration/repository_checks.md) Periodic Git repository checks
+- [Repository checks](administration/repository_checks.md) Periodic Git repository checks.
 - [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
 - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
 - [Update](update/README.md) Update guides to upgrade your installation.
@@ -41,11 +42,11 @@
 - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
 - [Git LFS configuration](workflow/lfs/lfs_administration.md)
 - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
-- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics
-- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint
-- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs
-- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability
-- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab
+- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics.
+- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint.
+- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs.
+- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability.
+- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab.
 
 ## Contributor documentation
 
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 7870669fa7767973958132db165a6b2d3c781101..d5d433034546fe77676447cc0ceee6af1ef10605 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -22,6 +22,7 @@ You can read more about Docker Registry at https://docs.docker.com/registry/intr
 - [Disable Container Registry per project](#disable-container-registry-per-project)
 - [Disable Container Registry for new projects site-wide](#disable-container-registry-for-new-projects-site-wide)
 - [Container Registry storage path](#container-registry-storage-path)
+- [Container Registry storage driver](#container-registry-storage-driver)
 - [Storage limitations](#storage-limitations)
 - [Changelog](#changelog)
 
@@ -84,6 +85,17 @@ GitLab does not ship with a Registry init file. Hence, [restarting GitLab][resta
 will not restart the Registry should you modify its settings. Read the upstream
 documentation on how to achieve that.
 
+The Docker Registry configuration will need `container_registry` as the service and `https://gitlab.example.com/jwt/auth` as the realm:
+
+```
+auth:
+  token:
+    realm: https://gitlab.example.com/jwt/auth
+    service: container_registry
+    issuer: gitlab-issuer
+    rootcertbundle: /root/certs/certbundle
+```
+
 ## Container Registry domain configuration
 
 There are two ways you can configure the Registry's external domain.
@@ -306,8 +318,12 @@ the Container Registry by themselves, follow the steps below.
 
 ## Container Registry storage path
 
-To change the storage path where Docker images will be stored, follow the
-steps below.
+>**Note:**
+For configuring storage in the cloud instead of the filesystem, see the
+[storage driver configuration](#container-registry-storage-driver).
+
+If you want to store your images on the filesystem, you can change the storage
+path for the Container Registry, follow the steps below.
 
 This path is accessible to:
 
@@ -349,6 +365,72 @@ The default location where images are stored in source installations, is
 
 1. Save the file and [restart GitLab][] for the changes to take effect.
 
+## Container Registry storage driver
+
+You can configure the Container Registry to use a different storage backend by
+configuring a different storage driver. By default the GitLab Container Registry
+is configured to use the filesystem driver, which makes use of [storage path](#container-registry-storage-path)
+configuration.
+
+The different supported drivers are:
+
+| Driver     | Description                         |
+|------------|-------------------------------------|
+| filesystem | Uses a path on the local filesystem |
+| azure      | Microsoft Azure Blob Storage        |
+| gcs        | Google Cloud Storage                |
+| s3         | Amazon Simple Storage Service       |
+| swift      | OpenStack Swift Object Storage      |
+| oss        | Aliyun OSS                          |
+
+Read more about the individual driver's config options in the
+[Docker Registry docs][storage-config].
+
+> **Warning** GitLab will not backup Docker images that are not stored on the
+filesystem. Remember to enable backups with your object storage provider if
+desired.
+
+---
+
+**Omnibus GitLab installations**
+
+To configure the storage driver in Omnibus:
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+    ```ruby
+    registry['storage'] = {
+      's3' => {
+        'accesskey' => 's3-access-key',
+        'secretkey' => 's3-secret-key-for-access-key',
+        'bucket' => 'your-s3-bucket'
+      }
+    }
+    ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**Installations from source**
+
+Configuring the storage driver is done in your registry config YML file created
+when you [deployed your docker registry][registry-deploy].
+
+Example:
+
+```
+storage:
+  s3:
+    accesskey: 'AKIAKIAKI'
+    secretkey: 'secret123'
+    bucket: 'gitlab-registry-bucket-AKIAKIAKI'
+  cache:
+    blobdescriptor: inmemory
+  delete:
+    enabled: true
+```
+
 ## Storage limitations
 
 Currently, there is no storage limitation, which means a user can upload an
diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md
new file mode 100644
index 0000000000000000000000000000000000000000..c212059b9d5a511dd2db3157b7a25835d1e82089
--- /dev/null
+++ b/doc/administration/raketasks/project_import_export.md
@@ -0,0 +1,33 @@
+# Project import/export
+
+>**Note:**
+  - This feature was [introduced][ce-3050] in GitLab 8.9
+  - Importing will not be possible if the import instance version is lower
+    than that of the exporter.
+  - For existing installations, the project import option has to be enabled in
+    application settings (`/admin/application_settings`) under 'Import sources'.
+  - The exports are stored in a temporary [shared directory][tmp] and are deleted
+    every 24 hours by a specific worker.
+
+The GitLab Import/Export version can be checked by using:
+
+```bash
+# Omnibus installations
+sudo gitlab-rake gitlab:import_export:version
+
+# Installations from source
+bundle exec rake gitlab:import_export:version RAILS_ENV=production
+```
+
+The current list of DB tables that will get exported can be listed by using:
+
+```bash
+# Omnibus installations
+sudo gitlab-rake gitlab:import_export:data
+
+# Installations from source
+bundle exec rake gitlab:import_export:data RAILS_ENV=production
+```
+
+[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
+[tmp]: ../../development/shared_files.md
diff --git a/doc/api/README.md b/doc/api/README.md
index 73f4460368854abdd835c99058eae86a243ec9c2..288f7f9ee6998ac75a0a4adc5cee086068a80f31 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -32,6 +32,7 @@ following locations:
 - [Services](services.md)
 - [Session](session.md)
 - [Settings](settings.md)
+- [Sidekiq metrics](sidekiq_metrics.md)
 - [System Hooks](system_hooks.md)
 - [Tags](tags.md)
 - [Users](users.md)
diff --git a/doc/ci/README.md b/doc/ci/README.md
index ef72df97ce6b714ea14e164912c85fdc0c85ef79..3dd4e2bc2309e3028953c576805a433725343233 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -5,6 +5,8 @@
 - [Get started with GitLab CI](quick_start/README.md)
 - [CI examples for various languages](examples/README.md)
 - [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
+- [Pipelines and builds](pipelines.md)
+- [Environments and deployments](environments.md)
 - [Learn how `.gitlab-ci.yml` works](yaml/README.md)
 - [Configure a Runner, the application that runs your builds](runners/README.md)
 - [Use Docker images with GitLab Runner](docker/using_docker_images.md)
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
new file mode 100644
index 0000000000000000000000000000000000000000..d85b8a34cedb21f90f027ce25c1d2626ba67a51f
--- /dev/null
+++ b/doc/ci/environments.md
@@ -0,0 +1,58 @@
+# Introduction to environments and deployments
+
+>**Note:**
+Introduced in GitLab 8.9.
+
+## Environments
+
+Environments are places where code gets deployed, such as staging or production.
+CI/CD [Pipelines] usually have one or more [jobs] that deploy to an environment.
+Defining environments in a project's `.gitlab-ci.yml` lets developers track
+[deployments] to these environments.
+
+## Deployments
+
+Deployments are created when [jobs] deploy versions of code to [environments].
+
+## Defining environments
+
+You can create and delete environments manually in the web interface, but we
+recommend that you define your environments in `.gitlab-ci.yml` first, which
+will automatically create environments for you after the first deploy.
+
+The `environment` is just a hint for GitLab that this job actually deploys to
+this environment. Each time the job succeeds, a deployment is recorded,
+remembering the git SHA and environment.
+
+Add something like this to your `.gitlab-ci.yml`:
+```
+production:
+  stage: deploy
+  script: dpl...
+  environment: production
+```
+
+See full [documentation](yaml/README.md#environment).
+
+## Seeing environment status
+
+You can find the environment list under **Pipelines > Environments** for your
+project. You'll see the git SHA and date of the last deployment to each
+environment defined.
+
+>**Note:**
+Only deploys that happen after your `.gitlab-ci.yml` is properly configured will
+show up in the environments and deployments lists.
+
+## Seeing deployment history
+
+Clicking on an environment will show the history of deployments.
+
+>**Note:**
+Only deploys that happen after your `.gitlab-ci.yml` is properly configured will
+show up in the environments and deployments lists.
+
+[Pipelines]: pipelines.md
+[jobs]: yaml/README.md#jobs
+[environments]: #environments
+[deployments]: #deployments
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
new file mode 100644
index 0000000000000000000000000000000000000000..48a9f99475954b5774d135ace728adbe26489d14
--- /dev/null
+++ b/doc/ci/pipelines.md
@@ -0,0 +1,38 @@
+# Introduction to pipelines and builds
+
+>**Note:**
+Introduced in GitLab 8.8.
+
+## Pipelines
+
+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)
+executed.
+
+## Builds
+
+Builds are individual runs of [jobs]. Not to be confused with a `build` job or
+`build` stage.
+
+## Defining pipelines
+
+Pipelines are defined in `.gitlab-ci.yml` by specifying [jobs] that run in
+[stages].
+
+See full [documentation](yaml/README.md#jobs).
+
+## Seeing pipeline status
+
+You can find the current and historical pipeline runs under **Pipelines** for your
+project.
+
+## Seeing build status
+
+Clicking on a pipeline will show the builds that were run for that pipeline.
+
+[builds]: #builds
+[jobs]: yaml/README.md#jobs
+[stages]: yaml/README.md#stages
+[runners]: runners/README.md
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 386b8e29fcfb7a85653b029a7a9cf9ee69f06f14..7fa1a478f344d8d12a1108f03639e9f3a0ac9b7e 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -4,41 +4,41 @@
 is fully integrated into GitLab itself and is [enabled] by default on all
 projects.
 
-The TL;DR version of how GitLab CI works is the following.
-
----
-
 GitLab offers a [continuous integration][ci] service. If you
 [add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository,
 and configure your GitLab project to use a [Runner], then each merge request or
-push triggers a build.
+push triggers your CI [pipeline].
 
-The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it
-runs three [stages]: `build`, `test`, and `deploy`.
+The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it runs
+a pipeline with three [stages]: `build`, `test`, and `deploy`. You don't need to
+use all three stages; stages with no jobs are simply ignored.
 
 If everything runs OK (no non-zero return values), you'll get a nice green
 checkmark associated with the pushed commit or merge request. This makes it
-easy to see whether a merge request will cause any of the tests to fail before
+easy to see whether a merge request caused any of the tests to fail before
 you even look at the code.
 
-Most projects only use GitLab's CI service to run the test suite so that
+Most projects use GitLab's CI service to run the test suite so that
 developers get immediate feedback if they broke something.
 
+There's a growing trend to use continuous delivery and continuous deployment to
+automatically deploy tested code to staging and production environments.
+
 So in brief, the steps needed to have a working CI can be summed up to:
 
 1. Add `.gitlab-ci.yml` to the root directory of your repository
 1. Configure a Runner
 
-From there on, on every push to your Git repository, the build will be
-automagically started by the Runner and will appear under the project's
-`/builds` page.
+From there on, on every push to your Git repository, the Runner will
+automagically start the pipeline and the pipeline will appear under the
+project's `/pipelines` page.
 
 ---
 
 This guide assumes that you:
 
 - have a working GitLab instance of version 8.0 or higher or are using
-  [GitLab.com](https://gitlab.com/users/sign_in)
+  [GitLab.com](https://gitlab.com)
 - have a project in GitLab that you would like to use CI for
 
 Let's break it down to pieces and work on solving the GitLab CI puzzle.
@@ -57,15 +57,14 @@ On any push to your repository, GitLab will look for the `.gitlab-ci.yml`
 file and start builds on _Runners_ according to the contents of the file,
 for that commit.
 
-Because `.gitlab-ci.yml` is in the repository, it is version controlled,
-old versions still build successfully, forks can easily make use of CI,
-branches can have separate builds and you have a single source of truth for CI.
-You can read more about the reasons why we are using `.gitlab-ci.yml`
-[in our blog about it][blog-ci].
+Because `.gitlab-ci.yml` is in the repository and is version controlled, old
+versions still build successfully, forks can easily make use of CI, branches can
+have different pipelines and jobs, and you have a single source of truth for CI.
+You can read more about the reasons why we are using `.gitlab-ci.yml` [in our
+blog about it][blog-ci].
 
 **Note:** `.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file
-so you have to pay extra attention to the indentation. Always use spaces, not
-tabs.
+so you have to pay extra attention to indentation. Always use spaces, not tabs.
 
 ### Creating a simple `.gitlab-ci.yml` file
 
@@ -108,7 +107,7 @@ 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.
 
-For more information and a complete `.gitlab-ci.yml` syntax, please check
+For more information and a complete `.gitlab-ci.yml` syntax, please read
 [the documentation on .gitlab-ci.yml](../yaml/README.md).
 
 ### Push `.gitlab-ci.yml` to GitLab
@@ -122,7 +121,8 @@ git commit -m "Add .gitlab-ci.yml"
 git push origin master
 ```
 
-Now if you go to the **Builds** page you will see that the builds are pending.
+Now if you go to the **Pipelines** page you will see that the pipeline is
+pending.
 
 You can also go to the **Commits** page and notice the little clock icon next
 to the commit SHA.
@@ -138,15 +138,14 @@ Notice that there are two jobs pending which are named after what we wrote in
 `.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured
 yet for these builds.
 
-The next step is to configure a Runner so that it picks the pending jobs.
+The next step is to configure a Runner so that it picks the pending builds.
 
 ## Configuring a Runner
 
-In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`.
-A Runner can be a virtual machine, a VPS, a bare-metal machine, a docker
-container or even a cluster of containers. GitLab and the Runners communicate
-through an API, so the only needed requirement is that the machine on which the
-Runner is configured to have Internet access.
+In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`. A Runner
+can be a virtual machine, a VPS, a bare-metal machine, a docker container or
+even a cluster of containers. GitLab and the Runners communicate through an API,
+so the only requirement is that the Runner's machine has Internet access.
 
 A Runner can be specific to a certain project or serve multiple projects in
 GitLab. If it serves all projects it's called a _Shared Runner_.
@@ -188,12 +187,16 @@ To enable **Shared Runners** you have to go to your project's
 
 [Read more on Shared Runners](../runners/README.md).
 
-## Seeing the status of your build
+## Seeing the status of your pipeline and builds
 
 After configuring the Runner successfully, you should see the status of your
 last commit change from _pending_ to either _running_, _success_ or _failed_.
 
-You can view all builds, by going to the **Builds** page in your project.
+You can view all pipelines by going to the **Pipelines** page in your project.
+
+![Commit status](img/pipelines_status.png)
+
+Or you can view all builds, by going to the **Pipelines > Builds** page.
 
 ![Commit status](img/builds_status.png)
 
@@ -238,3 +241,4 @@ CI with various languages.
 [runner]: ../runners/README.md
 [enabled]: ../enable_or_disable_ci.md
 [stages]: ../yaml/README.md#stages
+[pipeline]: ../pipelines.md
diff --git a/doc/ci/quick_start/img/pipelines_status.png b/doc/ci/quick_start/img/pipelines_status.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bc97bb739cedf27bdebe43a14a1606ff380a2f6
Binary files /dev/null and b/doc/ci/quick_start/img/pipelines_status.png differ
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 400784da61784aae547d92bf6322e9b96245bd1b..ddebd987650ea920b2de23ab49a5097752babbd7 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -96,6 +96,12 @@ To register the runner, run the command below and follow instructions:
 sudo gitlab-ci-multi-runner register
 ```
 
+###  Lock a specific runner from being enabled for other projects
+
+You can configure a runner to assign it exclusively to a project. When a
+runner is locked this way, it can no longer be enabled for other projects.
+This setting is available on each runner in *Project Settings* > *Runners*.
+
 ###  Making an existing Shared Runner Specific
 
 If you are an admin on your GitLab instance,
@@ -128,7 +134,7 @@ the appropriate dependencies to run Rails test suites.
 ### Prevent runner with tags from picking jobs without tags
 
 You can configure a runner to prevent it from picking jobs with tags when
-the runnner does not have tags assigned. This setting is available on each
+the runner does not have tags assigned. This setting is available on each
 runner in *Project Settings* > *Runners*.
 
 ### Be careful with sensitive information
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 9c98f9c98c6461883753caf6239e2a30549173c3..1892acda29b0f8afa8fdac984db144fdb6db2679 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -13,32 +13,34 @@ If you want a quick introduction to GitLab CI, follow our
 **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)
+  - [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)
-    - [job variables](#job-variables)
-    - [only and except](#only-and-except)
-    - [tags](#tags)
-    - [when](#when)
-    - [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)
+  - [script](#script)
+  - [stage](#stage)
+  - [only and except](#only-and-except)
+  - [job variables](#job-variables)
+  - [tags](#tags)
+  - [when](#when)
+  - [environment](#environment)
+  - [artifacts](#artifacts)
+    - [artifacts:name](#artifactsname)
+    - [artifacts:when](#artifactswhen)
+    - [artifacts:expire_in](#artifactsexpire_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)
+  - [Anchors](#anchors)
+- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ciyml)
 - [Skipping builds](#skipping-builds)
 - [Examples](#examples)
 
@@ -54,7 +56,7 @@ of your repository and contains definitions of how your project should be built.
 
 The YAML file defines a set of jobs with constraints stating when they should
 be run. The jobs are defined as top-level elements with a name and always have
-to contain the `script` clause:
+to contain at least the `script` clause:
 
 ```yaml
 job1:
@@ -165,9 +167,9 @@ stages:
 
 There are also two edge cases worth mentioning:
 
-1. If no `stages` is defined in `.gitlab-ci.yml`, then by default the `build`,
+1. If no `stages` are defined in `.gitlab-ci.yml`, then by default the `build`,
    `test` and `deploy` are allowed to be used as job's stage by default.
-2. If a job doesn't specify `stage`, the job is assigned the `test` stage.
+2. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
 
 ### types
 
@@ -178,9 +180,9 @@ Alias for [stages](#stages).
 >**Note:**
 Introduced in GitLab Runner v0.5.0.
 
-GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build
-environment. The variables are stored in the git repository and are meant to
-store non-sensitive project configuration, for example:
+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
+to store non-sensitive project configuration, for example:
 
 ```yaml
 variables:
@@ -253,8 +255,8 @@ rspec:
     - binaries/
 ```
 
-The cache is provided on best effort basis, so don't expect that cache will be
-always present. For implementation details please check GitLab Runner.
+The cache is provided on a best-effort basis, so don't expect that the cache
+will be always present. For implementation details, please check GitLab Runner.
 
 #### cache:key
 
@@ -479,10 +481,10 @@ failure.
 `when` can be set to one of the following values:
 
 1. `on_success` - execute build only when all builds from prior stages
-    succeeded. This is the default.
+    succeed. This is the default.
 1. `on_failure` - execute build only when at least one build from prior stages
-    failed.
-1. `always` - execute build despite the status of builds from prior stages.
+    fails.
+1. `always` - execute build regardless of the status of builds from prior stages.
 
 For example:
 
@@ -530,14 +532,18 @@ The above script will:
 ### environment
 
 >**Note:**
-Introduced in GitLab v8.9.0.
+Introduced in GitLab 8.9.
 
-`environment` is used to define that job does deployment to specific environment.
-This allows to easily track all deployments to your environments straight from GitLab.
+`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 does exist a new one will be created automatically.
+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 '_'.
+The `environment` name must contain only letters, digits, '-' and '_'. Common
+names are `qa`, `staging`, and `production`, but you can use whatever name works
+with your workflow.
 
 ---
 
@@ -550,7 +556,8 @@ deploy to production:
   environment: production
 ```
 
-The `deploy to production` job will be marked as doing deployment to `production` environment.
+The `deploy to production` job will be marked as doing deployment to
+`production` environment.
 
 ### artifacts
 
@@ -559,10 +566,10 @@ The `deploy to production` job will be marked as doing deployment to `production
 > - Introduced in GitLab Runner v0.7.0 for non-Windows platforms.
 > - Windows support was added in GitLab Runner v.1.0.0.
 > - Currently not all executors are supported.
-> - Build artifacts are only collected for successful builds.
+> - Build artifacts are only collected for successful builds by default.
 
-`artifacts` is used to specify list of files and directories which should be
-attached to build after success. To pass artifacts between different builds,
+`artifacts` is used to specify a list of files and directories which should be
+attached to the build after success. To pass artifacts between different builds,
 see [dependencies](#dependencies).
 
 Below are some examples.
@@ -690,9 +697,9 @@ failure.
 
 `artifacts:when` can be set to one of the following values:
 
-1. `on_success` - upload artifacts only when build succeeds. This is the default
-1. `on_failure` - upload artifacts only when build fails
-1. `always` - upload artifacts despite the build status
+1. `on_success` - upload artifacts only when the build succeeds. This is the default.
+1. `on_failure` - upload artifacts only when the build fails.
+1. `always` - upload artifacts regardless of the build status.
 
 ---
 
@@ -711,16 +718,18 @@ job:
 >**Note:**
 Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
 
-`artifacts:expire_in` is used to remove uploaded artifacts after specified time.
-By default artifacts are stored on GitLab forver.
-`expire_in` allows to specify after what time the artifacts should be removed.
-The artifacts will expire counting from the moment when they are uploaded and stored on GitLab.
+`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
+to specify how long artifacts should live before they expire, counting from the
+time they are uploaded and stored on GitLab.
 
-After artifacts uploading you can use the **Keep** button on build page to keep the artifacts forever.
+You can use the **Keep** button on the build page to override expiration and
+keep artifacts forever.
 
-Artifacts are removed every hour, but they are not accessible after expire date.
+After expiry, artifacts are actually deleted hourly by default (via a cron job),
+but they are not accessible after expiry.
 
-The value of `expire_in` is a elapsed time. The example of parsable values:
+The value of `expire_in` is an elapsed time. Examples of parseable values:
 - '3 mins 4 sec'
 - '2 hrs 20 min'
 - '2h20min'
@@ -732,7 +741,7 @@ The value of `expire_in` is a elapsed time. The example of parsable values:
 
 **Example configurations**
 
-To expire artifacts after 1 week from the moment that they are uploaded:
+To expire artifacts 1 week after being uploaded:
 
 ```yaml
 job:
@@ -814,6 +823,61 @@ job:
   - execute this after my script
 ```
 
+## Git Strategy
+
+>**Note:**
+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`
+is faster. `GIT_STRATEGY` can be specified in the global `variables` section or
+in the `variables` section for individual jobs. If it's not specified, then the
+default from project settings will be used.
+
+```
+variables:
+  GIT_STRATEGY: clone
+```
+
+or
+
+```
+variables:
+  GIT_STRATEGY: fetch
+```
+
+## Shallow cloning
+
+>**Note:**
+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
+shallow cloning of the repository which can significantly speed up cloning for
+repositories with a large number of commits or old, large binaries. The value is
+passed to `git fetch` and `git clone`.
+
+>**Note:**
+If you use a depth of 1 and have a queue of builds or retry
+builds, jobs may fail.
+
+Since Git fetching and cloning is based on a ref, such as a branch name, runners
+can't clone a specific commit SHA. If there are multiple builds in the queue, or
+you are retrying an old build, the commit to be tested needs to be within the
+git history that is cloned. Setting too small a value for `GIT_DEPTH` can make
+it impossible to run these old commits. You will see `unresolved reference` in
+build logs. You should then reconsider changing `GIT_DEPTH` to a higher value.
+
+Builds that rely on `git describe` may not work correctly when `GIT_DEPTH` is
+set since only part of the git history is present.
+
+To fetch or clone only the last 3 commits:
+```
+variables:
+  GIT_DEPTH: "3"
+```
+
 ## Hidden jobs
 
 >**Note:**
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index f5d97179f8a3e61d7ac11412df93d1fd7b4e6212..975bb82c37d858979664e2f2faf10a2b9eec80ba 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -183,6 +183,62 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
     (`workflow/lfs/lfs_administration.md`).
 
 
+## Configuration documentation for source and Omnibus installations
+
+GitLab currently officially supports two installation methods: installations
+from source and Omnibus packages installations.
+
+Whenever there is a setting that is configurable for both installation methods,
+prefer to document it in the CE docs to avoid duplication.
+
+Configuration settings include:
+
+- settings that touch configuration files in `config/`
+- NGINX settings and settings in `lib/support/` in general
+
+When there is a list of steps to perform, usually that entails editing the
+configuration file and reconfiguring/restarting GitLab. In such case, follow
+the style below as a guide:
+
+````
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+    ```ruby
+    external_url "https://gitlab.example.com"
+    ```
+
+1. Save the file and [reconfigure] GitLab for the changes to take effect.
+
+---
+
+**For installations from source**
+
+1. Edit `config/gitlab.yml`:
+
+    ```yaml
+    gitlab:
+      host: "gitlab.example.com"
+    ```
+
+1. Save the file and [restart] GitLab for the changes to take effect.
+
+
+[reconfigure]: path/to/administration/gitlab_restart.md#omnibus-gitlab-reconfigure
+[restart]: path/to/administration/gitlab_restart.md#installations-from-source
+````
+
+In this case:
+
+- before each step list the installation method is declared in bold
+- three dashes (`---`) are used to create an horizontal line and separate the
+  two methods
+- the code blocks are indented one or more spaces under the list item to render
+  correctly
+- different highlighting languages are used for each config in the code block
+- the [references](#references) guide is used for reconfigure/restart
+
 ## API
 
 Here is a list of must-have items. Use them in the exact order that appears
diff --git a/doc/intro/README.md b/doc/intro/README.md
index 382d10aaf40f5beb518a37b3b7cc3352c4c74b7c..1850031eb26452bb9566489de51032a56ff14955 100644
--- a/doc/intro/README.md
+++ b/doc/intro/README.md
@@ -12,7 +12,7 @@ Create projects and groups.
 Create issues, labels, milestones, cast your vote, and review issues.
 
 - [Create a new issue](../gitlab-basics/create-issue.md)
-- [Assign labels to issues](../workflow/labels.md)
+- [Assign labels to issues](../user/project/labels.md)
 - [Use milestones as an overview of your project's tracker](../workflow/milestones.md)
 - [Use voting to express your like/dislike to issues and merge requests](../workflow/award_emoji.md)
 
diff --git a/doc/project_services/emails_on_push.md b/doc/project_services/emails_on_push.md
new file mode 100644
index 0000000000000000000000000000000000000000..2f9f36f962ef9feceb4638b9d3956d6a00bba5dd
--- /dev/null
+++ b/doc/project_services/emails_on_push.md
@@ -0,0 +1,17 @@
+## Enabling emails on push
+
+To receive email notifications for every change that is pushed to the project, visit
+your project's **Settings > Services > Emails on push** and activate the service.
+
+In the _Recipients_ area, provide a list of emails separated by commas.
+
+You can configure any of the following settings depending on your preference.
+
++ **Push events** - Email will be triggered when a push event is recieved
++ **Tag push events** - Email will be triggered when a tag is created and pushed
++ **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`).
++ **Disable code diffs** - Don't include possibly sensitive code diffs in notification body.
+
+---
+
+![Email on push service settings](img/emails_on_push_service.png)
diff --git a/doc/project_services/img/emails_on_push_service.png b/doc/project_services/img/emails_on_push_service.png
new file mode 100644
index 0000000000000000000000000000000000000000..cd6f79ad1eb9c3635e00e62b0e16c62482e4a790
Binary files /dev/null and b/doc/project_services/img/emails_on_push_service.png differ
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index a5af620d9be6f16ea4eeec20ffbcd3340397e111..f81a035f70b2cad13ed8b0d3936e6fb1a47d47cf 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -33,7 +33,7 @@ further configuration instructions and details. Contributions are welcome.
 | Campfire | Simple web-based real-time group chat |
 | Custom Issue Tracker | Custom issue tracker |
 | Drone CI | Continuous Integration platform built on Docker, written in Go |
-| Emails on push | Email the commits and diff of each push to a list of recipients |
+| [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients |
 | External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
 | Flowdock | Flowdock is a collaboration web app for technical teams |
 | Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities |
diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md
index bb463d43a7c22ba88ed95bfa8cd0219e3477d916..cb66ef920bb95ee67b928106e8606233bf12b4a5 100644
--- a/doc/update/8.6-to-8.7.md
+++ b/doc/update/8.6-to-8.7.md
@@ -45,7 +45,7 @@ sudo -u git -H git checkout 8-7-stable-ee
 
 ```bash
 cd /home/git/gitlab-shell
-sudo -u git -H git fetch --all --tags
+sudo -u git -H git fetch --tags
 sudo -u git -H git checkout v2.7.2
 ```
 
diff --git a/doc/user/project/img/labels_assign_label_in_new_issue.png b/doc/user/project/img/labels_assign_label_in_new_issue.png
new file mode 100644
index 0000000000000000000000000000000000000000..e32a35f7cda12e6ea4af3f5b2f7698f7903ab76b
Binary files /dev/null and b/doc/user/project/img/labels_assign_label_in_new_issue.png differ
diff --git a/doc/user/project/img/labels_assign_label_sidebar.png b/doc/user/project/img/labels_assign_label_sidebar.png
new file mode 100644
index 0000000000000000000000000000000000000000..799443af889b899c84748a355a61303306cef78c
Binary files /dev/null and b/doc/user/project/img/labels_assign_label_sidebar.png differ
diff --git a/doc/user/project/img/labels_assign_label_sidebar_saved.png b/doc/user/project/img/labels_assign_label_sidebar_saved.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7d8d69e60ef3a4df05fbf5359b1ed580dec1a73
Binary files /dev/null and b/doc/user/project/img/labels_assign_label_sidebar_saved.png differ
diff --git a/doc/user/project/img/labels_default.png b/doc/user/project/img/labels_default.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee0c9f889ad77d4efcec77f0a74bedf82ca907ce
Binary files /dev/null and b/doc/user/project/img/labels_default.png differ
diff --git a/doc/user/project/img/labels_description_tooltip.png b/doc/user/project/img/labels_description_tooltip.png
new file mode 100644
index 0000000000000000000000000000000000000000..0d1e3e091fbc42e791ae1194461c2b0cef7df788
Binary files /dev/null and b/doc/user/project/img/labels_description_tooltip.png differ
diff --git a/doc/user/project/img/labels_filter.png b/doc/user/project/img/labels_filter.png
new file mode 100644
index 0000000000000000000000000000000000000000..ed622be2d93d288bd46ff45b2ccf8929b0a6ebdb
Binary files /dev/null and b/doc/user/project/img/labels_filter.png differ
diff --git a/doc/user/project/img/labels_filter_by_priority.png b/doc/user/project/img/labels_filter_by_priority.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5a9e20919b38cc26f901fa5a4eb88c0f9629a84
Binary files /dev/null and b/doc/user/project/img/labels_filter_by_priority.png differ
diff --git a/doc/user/project/img/labels_generate.png b/doc/user/project/img/labels_generate.png
new file mode 100644
index 0000000000000000000000000000000000000000..9579be4e231496825a1900c20de6b9f09bb26806
Binary files /dev/null and b/doc/user/project/img/labels_generate.png differ
diff --git a/doc/user/project/img/labels_new_label.png b/doc/user/project/img/labels_new_label.png
new file mode 100644
index 0000000000000000000000000000000000000000..a916d3dceb594671ef019aa77ff5c7fc424532dd
Binary files /dev/null and b/doc/user/project/img/labels_new_label.png differ
diff --git a/doc/user/project/img/labels_new_label_on_the_fly.png b/doc/user/project/img/labels_new_label_on_the_fly.png
new file mode 100644
index 0000000000000000000000000000000000000000..80cc434239e9de7c753b79cebf486ad41e01f88b
Binary files /dev/null and b/doc/user/project/img/labels_new_label_on_the_fly.png differ
diff --git a/doc/user/project/img/labels_new_label_on_the_fly_create.png b/doc/user/project/img/labels_new_label_on_the_fly_create.png
new file mode 100644
index 0000000000000000000000000000000000000000..c41090945ebfe71627f1e5eab5fbc3d2c70e1095
Binary files /dev/null and b/doc/user/project/img/labels_new_label_on_the_fly_create.png differ
diff --git a/doc/user/project/img/labels_prioritize.png b/doc/user/project/img/labels_prioritize.png
new file mode 100644
index 0000000000000000000000000000000000000000..8dfe72cf8262a3d7ac982a206d0f6493684b4781
Binary files /dev/null and b/doc/user/project/img/labels_prioritize.png differ
diff --git a/doc/user/project/img/labels_subscribe.png b/doc/user/project/img/labels_subscribe.png
new file mode 100644
index 0000000000000000000000000000000000000000..ea3db2bc0cf5903ae7073a03204a137f8b7862b9
Binary files /dev/null and b/doc/user/project/img/labels_subscribe.png differ
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
new file mode 100644
index 0000000000000000000000000000000000000000..4258185b7d08cd7cec24bed4dc2abba80106542a
--- /dev/null
+++ b/doc/user/project/labels.md
@@ -0,0 +1,147 @@
+# Labels
+
+Labels provide an easy way to categorize the issues or merge requests based on
+descriptive titles like `bug`, `documentation` or any other text you feel like
+it. They can have different colors, a description, and are visible throughout
+the issue tracker or inside each issue individually.
+
+With labels, you can navigate the issue tracker and filter any bloated
+information to visualize only the issues you are interested in. Let's see how
+that works.
+
+## Create new labels
+
+>**Note:**
+A permission level of `Developer` or higher is required in order to manage
+labels.
+
+Head over a single project and navigate to **Issues > Labels**.
+
+The first time you visit this page, you'll notice that there are no labels
+created yet.
+
+![Generate new labels](img/labels_generate.png)
+
+---
+
+You can skip that and create a new label or click that link and GitLab will
+generate a set of predefined labels for you. There 8 default generated labels
+in total and you can see them in the screenshot below.
+
+![Default generated labels](img/labels_default.png)
+
+---
+
+You can see that from the labels page you can have an overview of the number of
+issues and merge requests assigned to each label.
+
+Creating a new label from scratch is as easy as pressing the **New label**
+button. From there on you can choose the name, give it an optional description,
+a color and you are set.
+
+When you are ready press the **Create label** button to create the new label.
+
+![New label](img/labels_new_label.png)
+
+## Prioritize labels
+
+>**Notes:**
+ - This feature was introduced in GitLab 8.9.
+ - Priority sorting is based on the highest priority label only. This might
+   change in the future, follow the discussion in
+   https://gitlab.com/gitlab-org/gitlab-ce/issues/18554.
+
+Prioritized labels are like any other label, but sorted by priority. This allows
+you to sort issues and merge requests by priority.
+
+To prioritize labels, navigate to your project's **Issues > Labels** and click
+on the star icon next to them to put them in the priority list. Click on the
+star icon again to remove them from the list.
+
+From there, you can drag them around to set the desired priority. Priority is
+set from high to low with an ascending order. Labels with no priority, count as
+having their priority set to null.
+
+![Prioritize labels](img/labels_prioritize.png)
+
+Now that you have labels prioritized, you can use the 'Priority' filter in the
+issues or merge requests tracker. Those with the highest priority label, will
+appear on top.
+
+![Filter labels by priority](img/labels_filter_by_priority.png)
+
+## Subscribe to labels
+
+If you don’t want to miss issues or merge requests that are important to you,
+simply subscribe to a label. You’ll get notified whenever the label gets added
+to an issue or merge request, making sure you don’t miss a thing.
+
+Go to your project's **Issues > Labels** area, find the label(s) you want to
+subscribe to and click on the eye icon. Click again to unsubscribe.
+
+![Subscribe to labels](img/labels_subscribe.png)
+
+If you work on a large or popular project, try subscribing only to the labels
+that are relevant to you. You’ll notice it’ll be much easier to focus on what’s
+important.
+
+## Create a new label right from the issue tracker
+
+>**Note:**
+This feature was introduced in GitLab 8.6.
+
+There are times when you are already in the issue tracker searching for a
+label, only to realize it doesn't exist. Instead of going to the **Labels**
+page and being distracted from your original purpose, you can create new
+labels on the fly.
+
+Select **Create new** from the labels dropdown list, provide a name, pick a
+color and hit **Create**.
+
+![Create new label on the fly](img/labels_new_label_on_the_fly_create.png)
+![New label on the fly](img/labels_new_label_on_the_fly.png)
+
+## Assigning labels to issues and merge requests
+
+There are generally two ways to assign a label to an issue or merge request.
+
+You can assign a label when you first create or edit an issue or merge request.
+
+![Assign label in new issue](img/labels_assign_label_in_new_issue.png)
+
+---
+
+The second way is by using the right sidebar when inside an issue or merge
+request. Expand it and hit **Edit** in the labels area. Start typing the name
+of the label you are looking for to narrow down the list, and select it. You
+can add more than one labels at once. When done, click outside the sidebar area
+for the changes to take effect.
+
+![Assign label in sidebar](img/labels_assign_label_sidebar.png)
+![Save labels in sidebar](img/labels_assign_label_sidebar_saved.png)
+
+---
+
+To remove labels, expand the left sidebar and unmark them from the labels list.
+Simple as that.
+
+##  Use labels to filter issues
+
+Once you start adding labels to your issues, you'll see the benefit of it.
+Labels can have several uses, one of them being the quick filtering of issues
+or merge requests.
+
+Pick an existing label from the dropdown _Label_ menu or click on an existing
+label from the issue tracker. In the latter case, you also get to see the
+label description like shown below.
+
+![Filter labels](img/labels_filter.png)
+
+---
+
+And if you added a description to your label, you can see it by hovering your
+mouse over the label in the issue tracker or wherever else the label is
+rendered.
+
+![Label tooltips](img/labels_description_tooltip.png)
+
diff --git a/doc/user/project/settings/img/import_export_download_export.png b/doc/user/project/settings/img/import_export_download_export.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2f7f0085c19ec066822023a62053104473c25bd
Binary files /dev/null and b/doc/user/project/settings/img/import_export_download_export.png differ
diff --git a/doc/user/project/settings/img/import_export_export_button.png b/doc/user/project/settings/img/import_export_export_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f7bdd21b0d0072d348782aeb89cb0b2cd148920
Binary files /dev/null and b/doc/user/project/settings/img/import_export_export_button.png differ
diff --git a/doc/user/project/settings/img/import_export_mail_link.png b/doc/user/project/settings/img/import_export_mail_link.png
new file mode 100644
index 0000000000000000000000000000000000000000..c123f83eb8e4be6e3890505ca355c04dee14fddd
Binary files /dev/null and b/doc/user/project/settings/img/import_export_mail_link.png differ
diff --git a/doc/user/project/settings/img/import_export_new_project.png b/doc/user/project/settings/img/import_export_new_project.png
new file mode 100644
index 0000000000000000000000000000000000000000..b3a7f2010188b2e02ebadd665be79387de36b495
Binary files /dev/null and b/doc/user/project/settings/img/import_export_new_project.png differ
diff --git a/doc/user/project/settings/img/import_export_select_file.png b/doc/user/project/settings/img/import_export_select_file.png
new file mode 100644
index 0000000000000000000000000000000000000000..f31832af3e1d0a2accd3ada2f100cff6a0ded093
Binary files /dev/null and b/doc/user/project/settings/img/import_export_select_file.png differ
diff --git a/doc/user/project/settings/img/settings_edit_button.png b/doc/user/project/settings/img/settings_edit_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..3c0cee536de250ac9db41b895c39b6a94ca20c68
Binary files /dev/null and b/doc/user/project/settings/img/settings_edit_button.png differ
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
new file mode 100644
index 0000000000000000000000000000000000000000..38e9786123dc580c24e050929efecc6642eec4fa
--- /dev/null
+++ b/doc/user/project/settings/import_export.md
@@ -0,0 +1,73 @@
+# Project import/export
+
+>**Notes:**
+  - This feature was [introduced][ce-3050] in GitLab 8.9
+  - Importing will not be possible if the import instance version is lower
+    than that of the exporter.
+  - For existing installations, the project import option has to be enabled in
+    application settings (`/admin/application_settings`) under 'Import sources'.
+    Ask your administrator if you don't see the **GitLab export** button when
+    creating a new project.
+  - You can find some useful raketasks if you are an administrator in the
+    [import_export](../../../administration/raketasks/project_import_export.md)
+    raketask.
+  - The exports are stored in a temporary [shared directory][tmp] and are deleted
+    every 24 hours by a specific worker.
+
+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.
+
+## Exported contents
+
+The following items will be exported:
+
+- Project and wiki repositories
+- Project uploads
+- Project configuration including web hooks and services
+- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets,
+  and other project entities
+
+The following items will NOT be exported:
+
+- Build traces and artifacts
+- LFS objects
+
+## Exporting a project and its data
+
+1. Go to the project settings page by clicking on **Edit Project**:
+
+    ![Project settings button](img/settings_edit_button.png)
+
+1. Scroll down to find the **Export project** button:
+
+    ![Export button](img/import_export_export_button.png)
+
+1. Once the export is generated, you should receive an e-mail with a link to
+   download the file:
+
+    ![Email download link](img/import_export_mail_link.png)
+
+1. Alternatively, you can come back to the project settings and download the
+   file from there, or generate a new export. Once the file available, the page
+   should show the **Download export** button:
+
+    ![Download export](img/import_export_download_export.png)
+
+## Importing the project
+
+1. The new GitLab project import feature is at the far right of the import
+   options when creating a New Project. Make sure you are in the right namespace
+   and you have entered a project name. Click on **GitLab export**:
+
+    ![New project](img/import_export_new_project.png)
+
+1. You can see where the project will be imported to. You can now select file
+   exported previously:
+
+    ![Select file](img/import_export_select_file.png)
+
+1. Click on **Import project** to begin importing. Your newly imported project
+   page will appear soon.
+
+[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
+[tmp]: ../../../development/shared_files.md
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 9efe41308dcfb303699f4f0c5fc8d0aa24f70878..ddb2f7281b1034e376ad8bb35a48a8484e36c3a4 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -7,7 +7,7 @@
 - [Groups](groups.md)
 - [Keyboard shortcuts](shortcuts.md)
 - [File finder](file_finder.md)
-- [Labels](labels.md)
+- [Labels](../user/project/labels.md)
 - [Notification emails](notifications.md)
 - [Project Features](project_features.md)
 - [Project forking workflow](forking_workflow.md)
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
index fffa0aba57f3fa898f003633d4f23dfad1b295b6..4b55113025512b724a98d4e2a5bb143ed42a0c68 100644
--- a/doc/workflow/add-user/add-user.md
+++ b/doc/workflow/add-user/add-user.md
@@ -8,7 +8,7 @@ You should have `master` or `owner` permissions to add or import a new user
 to your project.
 
 The first step to add or import a user, go to your project and click on
-**Members** on the left side of your screen.
+**Members** in the drop-down menu on the right side of your screen.
 
 ![Members](img/add_user_members_menu.png)
 
@@ -87,3 +87,25 @@ invitation, change their access level or even delete them.
 
 Once the user accepts the invitation, they will be prompted to create a new
 GitLab account using the same e-mail address the invitation was sent to.
+
+## Request access to a project
+
+As a user, you can request to be a member of a project. Go to the project you'd
+like to be a member of, and click the **Request Access** button on the right
+side of your screen.
+
+![Request access button](img/request_access_button.png)
+
+---
+
+Project owners & masters will be notified of your request and will be able to approve or
+decline it on the members page.
+
+![Manage access requests](img/access_requests_management.png)
+
+---
+
+If you change your mind before your request is approved, just click the
+**Withdraw Access Request** button.
+
+![Withdraw access request button](img/withdraw_access_request_button.png)
diff --git a/doc/workflow/add-user/img/access_requests_management.png b/doc/workflow/add-user/img/access_requests_management.png
new file mode 100644
index 0000000000000000000000000000000000000000..e9641cb4f85b9112a2ab2bfc4868081fe28caaae
Binary files /dev/null and b/doc/workflow/add-user/img/access_requests_management.png differ
diff --git a/doc/workflow/add-user/img/add_user_email_accept.png b/doc/workflow/add-user/img/add_user_email_accept.png
index 910affc9659eb1a298be9d55af5492950ba295b7..18aabf93d50bd9d5b24a9f5d9ce4b4491d19b01f 100644
Binary files a/doc/workflow/add-user/img/add_user_email_accept.png and b/doc/workflow/add-user/img/add_user_email_accept.png differ
diff --git a/doc/workflow/add-user/img/add_user_email_ready.png b/doc/workflow/add-user/img/add_user_email_ready.png
index 5f02ce89b3e1190d8579eb33f14afa80ed19d7a0..385d64330c0a14fb2046683db3ba9ccd050ba095 100644
Binary files a/doc/workflow/add-user/img/add_user_email_ready.png and b/doc/workflow/add-user/img/add_user_email_ready.png differ
diff --git a/doc/workflow/add-user/img/add_user_email_search.png b/doc/workflow/add-user/img/add_user_email_search.png
index 140979fbe13103533deba4ad8feb14f3df0c3585..84741edbca405049c15e9b3e07054279bfb53918 100644
Binary files a/doc/workflow/add-user/img/add_user_email_search.png and b/doc/workflow/add-user/img/add_user_email_search.png differ
diff --git a/doc/workflow/add-user/img/add_user_give_permissions.png b/doc/workflow/add-user/img/add_user_give_permissions.png
index 8ef9156c8d5c5ca2935420cbdd4f7b4083dca340..7e580384e54566c504b70137059de04a1edb5607 100644
Binary files a/doc/workflow/add-user/img/add_user_give_permissions.png and b/doc/workflow/add-user/img/add_user_give_permissions.png differ
diff --git a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png
index 5770d5cf0c4e2b3b24691a7e0bcd29d6f8b45583..8dbd73a5bc82a294b44a592461fdc9f4279a2f40 100644
Binary files a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png and b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png differ
diff --git a/doc/workflow/add-user/img/add_user_imported_members.png b/doc/workflow/add-user/img/add_user_imported_members.png
index dea4b3f40adff254643ae04223be61b7e1523862..abac1f59c026baaf201fd0977f21528b6768441c 100644
Binary files a/doc/workflow/add-user/img/add_user_imported_members.png and b/doc/workflow/add-user/img/add_user_imported_members.png differ
diff --git a/doc/workflow/add-user/img/add_user_list_members.png b/doc/workflow/add-user/img/add_user_list_members.png
index 7daa6ca7d9e96b3692231615acf05ec11af3e05d..e17d88c6f5f964ec47c1afbdd1ac2b1076ac542b 100644
Binary files a/doc/workflow/add-user/img/add_user_list_members.png and b/doc/workflow/add-user/img/add_user_list_members.png differ
diff --git a/doc/workflow/add-user/img/add_user_members_menu.png b/doc/workflow/add-user/img/add_user_members_menu.png
index f1797b95f6785b339d830a1bcefe1487676076ac..ec5d39f402d92dfdfd98215707afd3113a45e63a 100644
Binary files a/doc/workflow/add-user/img/add_user_members_menu.png and b/doc/workflow/add-user/img/add_user_members_menu.png differ
diff --git a/doc/workflow/add-user/img/add_user_search_people.png b/doc/workflow/add-user/img/add_user_search_people.png
index 5ac10ce80d4892a3cfdd125f824b5d9f0d3ace6e..eaa062376f45d7ae71cd04d942a0526a0fe60cc4 100644
Binary files a/doc/workflow/add-user/img/add_user_search_people.png and b/doc/workflow/add-user/img/add_user_search_people.png differ
diff --git a/doc/workflow/add-user/img/request_access_button.png b/doc/workflow/add-user/img/request_access_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..984d640b0f0ef6325d8b67268bc7c87ce4d4ef1b
Binary files /dev/null and b/doc/workflow/add-user/img/request_access_button.png differ
diff --git a/doc/workflow/add-user/img/withdraw_access_request_button.png b/doc/workflow/add-user/img/withdraw_access_request_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..ff54a0e438483e6ee8aa674ec78915024009cb52
Binary files /dev/null and b/doc/workflow/add-user/img/withdraw_access_request_button.png differ
diff --git a/doc/workflow/award_emoji.md b/doc/workflow/award_emoji.md
index 70b35c58be69756c3f3aba873547112fc1a85277..e6f8b792707450f786498b1ab24dddc900d92d95 100644
--- a/doc/workflow/award_emoji.md
+++ b/doc/workflow/award_emoji.md
@@ -1,28 +1,26 @@
-# Award emojis
+# Award emoji
 
 >**Note:**
 This feature was [introduced][1825] in GitLab 8.2.
 
 When you're collaborating online, you get fewer opportunities for high-fives
-and thumbs-ups. In order to make virtual celebrations easier, you can now vote
-on issues and merge requests using emoji!
+and thumbs-ups. Emoji can be awarded to issues and merge requests, making
+virtual celebrations easier.
 
 ![Award emoji](img/award_emoji_select.png)
 
-This makes it much easier to give and receive feedback, without a long comment
-thread. Any comment that contains only the thumbs up or down emojis is
-converted to a vote and depicted in the emoji area.
-
-You can then use that functionality to sort issues and merge requests based on
-popularity.
+Award emoji make it much easier to give and receive feedback without a long
+comment thread. Comments that are only emoji will automatically become
+award emoji.
 
 ## Sort issues and merge requests on vote count
 
 >**Note:**
 This feature was [introduced][2871] in GitLab 8.5.
 
-You can quickly sort the issues or merge requests by the number of votes they
-have received. The sort option can be found in the right dropdown menu.
+You can quickly sort issues and merge requests by the number of votes they
+have received. The sort options can be found in the dropdown menu as "Most
+popular" and "Least popular".
 
 ![Votes sort options](img/award_emoji_votes_sort_options.png)
 
@@ -40,9 +38,28 @@ Sort by least popular issues/merge requests.
 
 ---
 
-The number of upvotes and downvotes is not summed up. That means that an issue
-with 18 upvotes and 5 downvotes is considered more popular than an issue with
-17 upvotes and no downvotes.
+The total number of votes is not summed up. An issue with 18 upvotes and 5
+downvotes is considered more popular than an issue with 17 upvotes and no
+downvotes.
+
+## Award emoji for comments
+
+>**Note:**
+This feature was [introduced][4291] in GitLab 8.9.
+
+Award emoji can also be applied to individual comments when you want to
+celebrate an accomplishment or agree with an opinion.
+
+To add an award emoji, click the smile in the top right of the comment and pick
+an emoji from the dropdown.
+
+![Picking an emoji for a comment](img/award_emoji_comment_picker.png)
+
+![An award emoji has been applied to a comment](img/award_emoji_comment_awarded.png)
+
+If you want to remove an award emoji, just click the emoji again and the vote
+will be removed.
 
 [2871]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781
 [1825]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825
+[4291]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4291
diff --git a/doc/workflow/award_emoji.png b/doc/workflow/award_emoji.png
index fb26ee043930b2a06f4b93dd3918c041e2f32033..3408ed958410029f682b4c5932e5977b6fefdba1 100644
Binary files a/doc/workflow/award_emoji.png and b/doc/workflow/award_emoji.png differ
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 34ada1774d8721cbe383789f32e0523b93eb3dbc..1a316e80976d8c3e80932831680809139f71b103 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -51,6 +51,28 @@ If necessary, you can increase the access level of an individual user for a spec
 
 ![Barry effectively has 'Master' access to GitLab CI now](groups/override_access_level.png)
 
+## Request access to a group
+
+As a user, you can request to be a member of a group. Go to the group you'd
+like to be a member of, and click the **Request Access** button on the right
+side of your screen.
+
+![Request access button](groups/request_access_button.png)
+
+---
+
+Group owners & masters will be notified of your request and will be able to approve or
+decline it on the members page.
+
+![Manage access requests](groups/access_requests_management.png)
+
+---
+
+If you change your mind before your request is approved, just click the
+**Withdraw Access Request** button.
+
+![Withdraw access request button](groups/withdraw_access_request_button.png)
+
 ## Managing group memberships via LDAP
 
 In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
diff --git a/doc/workflow/groups/access_requests_management.png b/doc/workflow/groups/access_requests_management.png
new file mode 100644
index 0000000000000000000000000000000000000000..ffede8e9bd68495ad70e5ac2b12fc47d7da03260
Binary files /dev/null and b/doc/workflow/groups/access_requests_management.png differ
diff --git a/doc/workflow/groups/request_access_button.png b/doc/workflow/groups/request_access_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..ff0ac8747a7cde212a2cc01581ea656bd7294b82
Binary files /dev/null and b/doc/workflow/groups/request_access_button.png differ
diff --git a/doc/workflow/groups/withdraw_access_request_button.png b/doc/workflow/groups/withdraw_access_request_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..99d7a326ed897d328154d3b8f959a7b2da9ba228
Binary files /dev/null and b/doc/workflow/groups/withdraw_access_request_button.png differ
diff --git a/doc/workflow/img/award_emoji_comment_awarded.png b/doc/workflow/img/award_emoji_comment_awarded.png
new file mode 100644
index 0000000000000000000000000000000000000000..6769783186963d63060261fb514b033f2cce1377
Binary files /dev/null and b/doc/workflow/img/award_emoji_comment_awarded.png differ
diff --git a/doc/workflow/img/award_emoji_comment_picker.png b/doc/workflow/img/award_emoji_comment_picker.png
new file mode 100644
index 0000000000000000000000000000000000000000..d9c3faecdca3db86040f3a1bb0141848d4aac710
Binary files /dev/null and b/doc/workflow/img/award_emoji_comment_picker.png differ
diff --git a/doc/workflow/labels.md b/doc/workflow/labels.md
index 6e4840ca5ae06c1e5888ca8683eb40bf42ad252f..5c09891dfdd88902993c1801f0c162042015476e 100644
--- a/doc/workflow/labels.md
+++ b/doc/workflow/labels.md
@@ -1,18 +1,3 @@
 # Labels
 
-In GitLab, you can easily tag issues and Merge Requests. If you have permission level `Developer` or higher, you can manage labels. To create, edit or delete a label, go to a project and then to `Issues` and then `Labels`.
-
-Here you can create a new label.
-
-![new label](labels/label1.png)
-
-You can choose to set a color.
-
-![label color](labels/label2.png)
-
-If you want to change an existing label, press edit next to the listed label.
-You will be presented with the same form as when creating a new label.
-
-![edit label](labels/label3.png)
-
-You can add labels to Merge Requests when you create or edit them.
+This document was moved to [user/project/labels.md](../user/project/labels.md).
diff --git a/doc/workflow/labels/label1.png b/doc/workflow/labels/label1.png
deleted file mode 100644
index cac661a34c8c393438130096b7fe78c4d996c9c9..0000000000000000000000000000000000000000
Binary files a/doc/workflow/labels/label1.png and /dev/null differ
diff --git a/doc/workflow/labels/label2.png b/doc/workflow/labels/label2.png
deleted file mode 100644
index 44d9fef86d4d32568d301843f674d4bda73bf99e..0000000000000000000000000000000000000000
Binary files a/doc/workflow/labels/label2.png and /dev/null differ
diff --git a/doc/workflow/labels/label3.png b/doc/workflow/labels/label3.png
deleted file mode 100644
index e2fce11b7a42be3d0b6c99fdf45f741b5ac3088c..0000000000000000000000000000000000000000
Binary files a/doc/workflow/labels/label3.png and /dev/null differ
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
index beb6c53ec771f8aa10660e20ce4f5b7a1e16c04c..16be0413b64ac6be977d54ad78e9f23b0c5e6a13 100644
Binary files a/doc/workflow/shortcuts.png and b/doc/workflow/shortcuts.png differ
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index ab7de7ac31547fb02d60df5a267da742f3122a1d..657e847cf4ae3c4f3a9285614ca222cf5fe50f64 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -26,13 +26,6 @@ Feature: Admin Groups
     When I visit group page
     Then I should see project shared with group
 
-  @javascript
-  Scenario: Remove user from group
-    Given we have user "John Doe" in group
-    When I visit admin group page
-    And I remove user "John Doe" from group
-    Then I should not see "John Doe" in team list
-
   @javascript
   Scenario: Invite user to a group by e-mail
     When I visit admin group page
diff --git a/features/dashboard/group.feature b/features/dashboard/group.feature
index e3c01db2ebb113dc73e509f501f54896ec99639e..3ae2c679dc1a8aa7dd7842854c89c239ccce797d 100644
--- a/features/dashboard/group.feature
+++ b/features/dashboard/group.feature
@@ -5,53 +5,9 @@ Feature: Dashboard Group
     And "John Doe" is owner of group "Owned"
     And "John Doe" is guest of group "Guest"
 
-  # Leave groups
-
-  @javascript
-  Scenario: Owner should be able to leave from group if he is not the last owner
-    Given "Mary Jane" is owner of group "Owned"
-    When I visit dashboard groups page
-    Then I should see group "Owned" in group list
-    Then I should see group "Guest" in group list
-    When I click on the "Leave" button for group "Owned"
-    And I visit dashboard groups page
-    Then I should not see group "Owned" in group list
-    Then I should see group "Guest" in group list
-
-  @javascript
-  Scenario: Owner should not be able to leave from group if he is the last owner
-    Given "Mary Jane" is guest of group "Owned"
-    When I visit dashboard groups page
-    Then I should see group "Owned" in group list
-    Then I should see group "Guest" in group list
-    When I click on the "Leave" button for group "Owned"
-    Then I should see the "Can not leave message"
-
-  @javascript
-  Scenario: Guest should be able to leave from group
-    Given "Mary Jane" is guest of group "Guest"
-    When I visit dashboard groups page
-    Then I should see group "Owned" in group list
-    Then I should see group "Guest" in group list
-    When I click on the "Leave" button for group "Guest"
-    When I visit dashboard groups page
-    Then I should see group "Owned" in group list
-    Then I should not see group "Guest" in group list
-
-  @javascript
-  Scenario: Guest should be able to leave from group even if he is the only user in the group
-    When I visit dashboard groups page
-    Then I should see group "Owned" in group list
-    Then I should see group "Guest" in group list
-    When I click on the "Leave" button for group "Guest"
-    When I visit dashboard groups page
-    Then I should see group "Owned" in group list
-    Then I should not see group "Guest" in group list
-
   Scenario: Create a group from dasboard
     And I visit dashboard groups page
     And I click new group link
     And submit form with new group "Samurai" info
     Then I should be redirected to group "Samurai" page
     And I should see newly created group "Samurai"
-
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index c4f987a7923fd218b3f52f6001fc9e487ee67dce..57dda9c2234fbe0471c5a4ba3b1bbf7af12354eb 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -10,9 +10,9 @@ Feature: Project Active Tab
     Then the active main tab should be Home
     And no other main tabs should be active
 
-  Scenario: On Project Code
+  Scenario: On Project Repository
     Given I visit my project's files page
-    Then the active main tab should be Code
+    Then the active main tab should be Repository
     And no other main tabs should be active
 
   Scenario: On Project Issues
@@ -59,46 +59,46 @@ Feature: Project Active Tab
     And no other sub navs should be active
     And the active main tab should be Settings
 
-  # Sub Tabs: Code
+  # Sub Tabs: Repository
 
-  Scenario: On Project Code/Files
+  Scenario: On Project Repository/Files
     Given I visit my project's files page
     Then the active sub tab should be Files
     And no other sub tabs should be active
-    And the active main tab should be Code
+    And the active main tab should be Repository
 
-  Scenario: On Project Code/Commits
+  Scenario: On Project Repository/Commits
     Given I visit my project's commits page
     Then the active sub tab should be Commits
     And no other sub tabs should be active
-    And the active main tab should be Code
+    And the active main tab should be Repository
 
-  Scenario: On Project Code/Network
+  Scenario: On Project Repository/Network
     Given I visit my project's network page
     Then the active sub tab should be Network
     And no other sub tabs should be active
-    And the active main tab should be Code
+    And the active main tab should be Repository
 
-  Scenario: On Project Code/Compare
+  Scenario: On Project Repository/Compare
     Given I visit my project's commits page
     And I click the "Compare" tab
     Then the active sub tab should be Compare
     And no other sub tabs should be active
-    And the active main tab should be Code
+    And the active main tab should be Repository
 
-  Scenario: On Project Code/Branches
+  Scenario: On Project Repository/Branches
     Given I visit my project's commits page
     And I click the "Branches" tab
     Then the active sub tab should be Branches
     And no other sub tabs should be active
-    And the active main tab should be Code
+    And the active main tab should be Repository
 
-  Scenario: On Project Code/Tags
+  Scenario: On Project Repository/Tags
     Given I visit my project's commits page
     And I click the "Tags" tab
     Then the active sub tab should be Tags
     And no other sub tabs should be active
-    And the active main tab should be Code
+    And the active main tab should be Repository
 
   Scenario: On Project Issues/Browse
     Given I visit my project's issues page
diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature
index c73d0b323376899f2b26255e23a51439c90f965f..f71f69ef060d468d0c8d0fb4afa7a2aab1526e79 100644
--- a/features/project/shortcuts.feature
+++ b/features/project/shortcuts.feature
@@ -8,21 +8,21 @@ Feature: Project Shortcuts
   @javascript
   Scenario: Navigate to files tab
     Given I press "g" and "f"
-    Then the active main tab should be Code
+    Then the active main tab should be Repository
     Then the active sub tab should be Files
 
   @javascript
   Scenario: Navigate to commits tab
     Given I visit my project's files page
     Given I press "g" and "c"
-    Then the active main tab should be Code
+    Then the active main tab should be Repository
     Then the active sub tab should be Commits
 
   @javascript
   Scenario: Navigate to network tab
     Given I press "g" and "n"
     Then the active sub tab should be Network
-    And the active main tab should be Code
+    And the active main tab should be Repository
 
   @javascript
   Scenario: Navigate to graphs tab
diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb
index e1f1db2872fdad292b8e8ca628a2b094318be028..8613dc537cc71a2a5925f1cc2763533451db728e 100644
--- a/features/steps/admin/groups.rb
+++ b/features/steps/admin/groups.rb
@@ -62,7 +62,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
 
   step 'I should see "johndoe@gitlab.com" in team list in every project as "Reporter"' do
     page.within ".group-users-list" do
-      expect(page).to have_content "johndoe@gitlab.com (invited)"
+      expect(page).to have_content "johndoe@gitlab.com – Invited by"
       expect(page).to have_content "Reporter"
     end
   end
@@ -92,12 +92,6 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
     current_group.add_reporter(user_john)
   end
 
-  step 'I remove user "John Doe" from group' do
-    page.within "#user_#{user_john.id}" do
-      click_link 'Remove user from group'
-    end
-  end
-
   step 'I should not see "John Doe" in team list' do
     page.within ".group-users-list" do
       expect(page).not_to have_content "John Doe"
diff --git a/features/steps/dashboard/group.rb b/features/steps/dashboard/group.rb
index 9b79a3be49b3a2f373265c128c8a351e938f5e22..cf679fea5308f7c9104f82e10850e59895870cf8 100644
--- a/features/steps/dashboard/group.rb
+++ b/features/steps/dashboard/group.rb
@@ -4,44 +4,6 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
   include SharedPaths
   include SharedUser
 
-  # Leave
-
-  step 'I click on the "Leave" button for group "Owned"' do
-    find(:css, 'li', text: "Owner").find(:css, 'i.fa.fa-sign-out').click
-    # poltergeist always confirms popups.
-  end
-
-  step 'I click on the "Leave" button for group "Guest"' do
-    find(:css, 'li', text: "Guest").find(:css, 'i.fa.fa-sign-out').click
-    # poltergeist always confirms popups.
-  end
-
-  step 'I should not see the "Leave" button for group "Owned"' do
-    expect(find(:css, 'li', text: "Owner")).not_to have_selector(:css, 'i.fa.fa-sign-out')
-    # poltergeist always confirms popups.
-  end
-
-  step 'I should not see the "Leave" button for groupr "Guest"' do
-    expect(find(:css, 'li', text: "Guest")).not_to have_selector(:css,  'i.fa.fa-sign-out')
-    # poltergeist always confirms popups.
-  end
-
-  step 'I should see group "Owned" in group list' do
-    expect(page).to have_content("Owned")
-  end
-
-  step 'I should not see group "Owned" in group list' do
-    expect(page).not_to have_content("Owned")
-  end
-
-  step 'I should see group "Guest" in group list' do
-    expect(page).to have_content("Guest")
-  end
-
-  step 'I should not see group "Guest" in group list' do
-    expect(page).not_to have_content("Guest")
-  end
-
   step 'I click new group link' do
     click_link "New Group"
   end
@@ -60,8 +22,4 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
     expect(page).to have_content "Samurai"
     expect(page).to have_content "Tokugawa Shogunate"
   end
-
-  step 'I should see the "Can not leave message"' do
-    expect(page).to have_content "You can not leave the \"Owned\" group."
-  end
 end
diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb
index 979f4692d5a62d0d16248a7799c029c21285c612..7e339443b75f497b832acf3ff1f23e07efcb0656 100644
--- a/features/steps/profile/notifications.rb
+++ b/features/steps/profile/notifications.rb
@@ -15,8 +15,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps
   end
 
   step 'I should see Notification saved message' do
-    page.within '.flash-container' do
-      expect(page).to have_content 'Notification settings saved'
-    end
+    expect(page).to have_content 'On mention'
   end
 end
diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb
index 9b59b682676ad6da8114da2c01a60be4aff2fce9..019b3124a863d5b5d6de957750e01dd69c9dc5b3 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -20,11 +20,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
   end
 
   step 'page should select "master" in select box' do
-    expect(page).to have_selector '.select2-chosen', text: "master"
+    expect(page).to have_selector '.dropdown-menu-toggle', text: "master"
   end
 
   step 'page should select "v1.0.0" in select box' do
-    expect(page).to have_selector '.select2-chosen', text: "v1.0.0"
+    expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0"
   end
 
   step 'page should have "master" on graph' do
@@ -40,11 +40,19 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
   end
 
   When 'I switch ref to "feature"' do
-    select 'feature', from: 'ref'
+    first('.js-project-refs-dropdown').click
+
+    page.within '.project-refs-form' do
+      click_link 'feature'
+    end
   end
 
   When 'I switch ref to "v1.0.0"' do
-    select 'v1.0.0', from: 'ref'
+    first('.js-project-refs-dropdown').click
+
+    page.within '.project-refs-form' do
+      click_link 'v1.0.0'
+    end
   end
 
   When 'click "Show only selected branch" checkbox' do
@@ -68,11 +76,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
   end
 
   step 'page should select "feature" in select box' do
-    expect(page).to have_selector '.select2-chosen', text: "feature"
+    expect(page).to have_selector '.dropdown-menu-toggle', text: "feature"
   end
 
   step 'page should select "v1.0.0" in select box' do
-    expect(page).to have_selector '.select2-chosen', text: "v1.0.0"
+    expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0"
   end
 
   step 'page should have "feature" on graph' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 98b57e5cbfbe21766426c3854520d6544a487bf0..76fefee9254f12cae6041e1aba43654b12f143d4 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -134,8 +134,8 @@ class Spinach::Features::Project < Spinach::FeatureSteps
   end
 
   step 'I should see Notification saved message' do
-    page.within '.flash-container' do
-      expect(page).to have_content 'Notification settings saved'
+    page.within '#notifications-button' do
+      expect(page).to have_content 'On mention'
     end
   end
 
diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb
index 47de4b91df1ebaa0b53f5f4af7e22e8c8bd6b8a6..90771847909e1c3bb3677a1f3c4cc0285fb72f34 100644
--- a/features/steps/project/project_find_file.rb
+++ b/features/steps/project/project_find_file.rb
@@ -13,12 +13,12 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps
   end
 
   step 'I should see "find file" page' do
-    ensure_active_main_tab('Code')
+    ensure_active_main_tab('Repository')
     expect(page).to have_selector('.file-finder-holder', count: 1)
   end
 
   step 'I fill in Find by path with "git"' do
-    ensure_active_main_tab('Code')
+    ensure_active_main_tab('Repository')
     expect(page).to have_selector('.file-finder-holder', count: 1)
   end
 
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 79a3ed8197e46ec9cb44a17d4344038247354d6e..0fe046dcbf6616393e0e3793baa0d477266ac9da 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -290,15 +290,23 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step "I switch ref to 'test'" do
-    select "'test'", from: 'ref'
+    first('.js-project-refs-dropdown').click
+
+    page.within '.project-refs-form' do
+      click_link 'test'
+    end
   end
 
   step "I switch ref to fix" do
-    select "fix", from: 'ref'
+    first('.js-project-refs-dropdown').click
+
+    page.within '.project-refs-form' do
+      click_link 'fix'
+    end
   end
 
   step "I see the ref 'test' has been selected" do
-    expect(page).to have_selector '.select2-chosen', text: "'test'"
+    expect(page).to have_selector '.dropdown-toggle-text', text: "'test'"
   end
 
   step "I visit the 'test' tree" do
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index bfee87933010d210f40d2b8f186f18da2c78ab3c..d6024212601f060340dd267f697e28adec52d275 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -8,8 +8,8 @@ module SharedProjectTab
     ensure_active_main_tab('Project')
   end
 
-  step 'the active main tab should be Code' do
-    ensure_active_main_tab('Code')
+  step 'the active main tab should be Repository' do
+    ensure_active_main_tab('Repository')
   end
 
   step 'the active main tab should be Graphs' do
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 0e7a1cc26234af672d3b740293d8ef2fa67fb322..f8f680a6311f087584f7cd649c43b687d50a96b8 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -33,7 +33,6 @@ module API
     mount ::API::Commits
     mount ::API::DeployKeys
     mount ::API::Files
-    mount ::API::Gitignores
     mount ::API::GroupMembers
     mount ::API::Groups
     mount ::API::Internal
@@ -58,6 +57,7 @@ module API
     mount ::API::Subscriptions
     mount ::API::SystemHooks
     mount ::API::Tags
+    mount ::API::Templates
     mount ::API::Triggers
     mount ::API::Users
     mount ::API::Variables
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 2e397643ed1611f62ae2cf494d8a04c7cf5bfdfb..5a23a18fe9c7e22dda765b9b86130f26276a609e 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -423,6 +423,7 @@ module API
     class RunnerDetails < Runner
       expose :tag_list
       expose :run_untagged
+      expose :locked
       expose :version, :revision, :platform, :architecture
       expose :contacted_at
       expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? }
@@ -444,11 +445,7 @@ module API
       expose :created_at, :started_at, :finished_at
       expose :user, with: User
       expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? }
-      expose :commit, with: RepoCommit do |repo_obj, _options|
-        if repo_obj.respond_to?(:commit)
-          repo_obj.commit.commit_data
-        end
-      end
+      expose :commit, with: RepoCommit
       expose :runner, with: Runner
     end
 
@@ -472,11 +469,11 @@ module API
       expose :content
     end
 
-    class GitignoresList < Grape::Entity
+    class TemplatesList < Grape::Entity
       expose :name
     end
 
-    class Gitignore < Grape::Entity
+    class Template < Grape::Entity
       expose :name, :content
     end
   end
diff --git a/lib/api/gitignores.rb b/lib/api/gitignores.rb
deleted file mode 100644
index 270c9501dd2271b3f58e834b56145feeea9dab12..0000000000000000000000000000000000000000
--- a/lib/api/gitignores.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module API
-  class Gitignores < Grape::API
-
-    # Get the list of the available gitignore templates
-    #
-    # Example Request:
-    #   GET /gitignores
-    get 'gitignores' do
-      present Gitlab::Gitignore.all, with: Entities::GitignoresList
-    end
-
-    # Get the text for a specific gitignore
-    #
-    # Parameters:
-    #   name (required) - The name of a license
-    #
-    # Example Request:
-    #   GET /gitignores/Elixir
-    #
-    get 'gitignores/:name' do
-      required_attributes! [:name]
-
-      gitignore = Gitlab::Gitignore.find(params[:name])
-      not_found!('.gitignore') unless gitignore
-
-      present gitignore, with: Entities::Gitignore
-    end
-  end
-end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 3ac7b50c4ce1f4dcb1cab3b4b89e6254d31f0818..1d361569d595c623b4ea178df6c0905e2f9b0216 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -23,8 +23,6 @@ module API
       end
 
       post "/allowed" do
-        Gitlab::Metrics.action = 'Grape#/internal/allowed'
-
         status 200
 
         actor =
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 4faba9dc87ba635a93c30707dd523bb7abc63eb2..ecc8f2fc5a2307e9e84b103600e246ee56106bd8 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -49,7 +49,7 @@ module API
         runner = get_runner(params[:id])
         authenticate_update_runner!(runner)
 
-        attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged]
+        attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged, :locked]
         if runner.update(attrs)
           present runner, with: Entities::RunnerDetails, current_user: current_user
         else
@@ -96,9 +96,14 @@ module API
 
         runner = get_runner(params[:runner_id])
         authenticate_enable_runner!(runner)
-        Ci::RunnerProject.create(runner: runner, project: user_project)
 
-        present runner, with: Entities::Runner
+        runner_project = runner.assign_to(user_project)
+
+        if runner_project.persisted?
+          present runner, with: Entities::Runner
+        else
+          conflict!("Runner was already enabled for this project")
+        end
       end
 
       # Disable project's runner
@@ -163,6 +168,7 @@ module API
 
       def authenticate_enable_runner!(runner)
         forbidden!("Runner is shared") if runner.is_shared?
+        forbidden!("Runner is locked") if runner.locked?
         return if current_user.is_admin?
         forbidden!("No access granted") unless user_can_access_runner?(runner)
       end
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1840879775666751c702f2bb3b2e0a076ed70e86
--- /dev/null
+++ b/lib/api/templates.rb
@@ -0,0 +1,36 @@
+module API
+  class Templates < Grape::API
+    TEMPLATE_TYPES = {
+      gitignores:     Gitlab::Template::Gitignore,
+      gitlab_ci_ymls: Gitlab::Template::GitlabCiYml
+    }.freeze
+
+    TEMPLATE_TYPES.each do |template, klass|
+      # Get the list of the available template
+      #
+      # Example Request:
+      #   GET /gitignores
+      #   GET /gitlab_ci_ymls
+      get template.to_s do
+        present klass.all, with: Entities::TemplatesList
+      end
+
+      # Get the text for a specific template
+      #
+      # Parameters:
+      #   name (required) - The name of a template
+      #
+      # Example Request:
+      #   GET /gitignores/Elixir
+      #   GET /gitlab_ci_ymls/Ruby
+      get "#{template}/:name" do
+        required_attributes! [:name]
+
+        new_template = klass.find(params[:name])
+        not_found!(template.to_s.singularize) unless new_template
+
+        present new_template, with: Entities::Template
+      end
+    end
+  end
+end
diff --git a/lib/banzai.rb b/lib/banzai.rb
index b467413a7dd8127c30c452af7a7af1d8917b5441..093382261ae8552645b1c3cd2cb44ce8c0bb3538 100644
--- a/lib/banzai.rb
+++ b/lib/banzai.rb
@@ -7,10 +7,6 @@ module Banzai
     Renderer.render_result(text, context)
   end
 
-  def self.pre_process(text, context)
-    Renderer.pre_process(text, context)
-  end
-
   def self.post_process(html, context)
     Renderer.post_process(html, context)
   end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index ea21c7b041cc7a02ae79c479e0391a71a16c91a3..c78da40460764bb4c3ccea18c8189bea840813d3 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -14,6 +14,8 @@ module Banzai
       def call
         return doc unless linkable_files?
 
+        @uri_types = {}
+
         doc.search('a:not(.gfm)').each do |el|
           process_link_attr el.attribute('href')
         end
@@ -48,7 +50,7 @@ module Banzai
         uri.path = [
           relative_url_root,
           context[:project].path_with_namespace,
-          path_type(file_path),
+          uri_type(file_path),
           ref || context[:project].default_branch,  # if no ref exists, point to the default branch
           file_path
         ].compact.join('/').squeeze('/').chomp('/')
@@ -87,7 +89,7 @@ module Banzai
         return path unless request_path
 
         parts = request_path.split('/')
-        parts.pop if path_type(request_path) != 'tree'
+        parts.pop if uri_type(request_path) != :tree
 
         while path.start_with?('../')
           parts.pop
@@ -98,45 +100,20 @@ module Banzai
       end
 
       def file_exists?(path)
-        return false if path.nil?
-        repository.blob_at(current_sha, path).present? ||
-          repository.tree(current_sha, path).entries.any?
-      end
-
-      # Get the type of the given path
-      #
-      # path - String path to check
-      #
-      # Examples:
-      #
-      #   path_type('doc/README.md') # => 'blob'
-      #   path_type('doc/logo.png')  # => 'raw'
-      #   path_type('doc/api')       # => 'tree'
-      #
-      # Returns a String
-      def path_type(path)
-        unescaped_path = Addressable::URI.unescape(path)
-
-        if tree?(unescaped_path)
-          'tree'
-        elsif image?(unescaped_path)
-          'raw'
-        else
-          'blob'
-        end
+        path.present? && !!uri_type(path)
       end
 
-      def tree?(path)
-        repository.tree(current_sha, path).entries.any?
-      end
+      def uri_type(path)
+        @uri_types[path] ||= begin
+          unescaped_path = Addressable::URI.unescape(path)
 
-      def image?(path)
-        repository.blob_at(current_sha, path).try(:image?)
+          current_commit.uri_type(unescaped_path)
+        end
       end
 
-      def current_sha
-        context[:commit].try(:id) ||
-          ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha
+      def current_commit
+        @current_commit ||= context[:commit] ||
+          ref ? repository.commit(ref) : repository.head_commit
       end
 
       def relative_url_root
@@ -148,7 +125,7 @@ module Banzai
       end
 
       def repository
-        context[:project].try(:repository)
+        @repository ||= context[:project].try(:repository)
       end
     end
   end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index c14a9c4c72218fea9baef0482a0dbc88f20c784c..6718acdef7e017fc304748fb771b0bf05424e7d4 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -30,13 +30,9 @@ module Banzai
     end
 
     def self.render_result(text, context = {})
-      Pipeline[context[:pipeline]].call(text, context)
-    end
+      text = Pipeline[:pre_process].to_html(text, context) if text
 
-    def self.pre_process(text, context)
-      pipeline = Pipeline[:pre_process]
-
-      pipeline.to_html(text, context)
+      Pipeline[context[:pipeline]].call(text, context)
     end
 
     # Perform post-processing on an HTML String
diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb
index 0c41f22c7c56bb0615c16d660e3b2991a47b769d..bcc82969eb3f0e0f97eca45335d9f17a4d741dcd 100644
--- a/lib/ci/api/runners.rb
+++ b/lib/ci/api/runners.rb
@@ -28,12 +28,9 @@ module Ci
         post "register" do
           required_attributes! [:token]
 
-          attributes = { description: params[:description],
-                         tag_list: params[:tag_list] }
-
-          unless params[:run_untagged].nil?
-            attributes[:run_untagged] = params[:run_untagged]
-          end
+          attributes = attributes_for_keys(
+            [:description, :tag_list, :run_untagged, :locked]
+          )
 
           runner =
             if runner_registration_token_valid?
diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb
index 7a0929d774ed19cc867dc236e9c4f5cdf36b46e9..708d01b95a1b14595cebb63580e3481e261e5d69 100644
--- a/lib/container_registry/tag.rb
+++ b/lib/container_registry/tag.rb
@@ -3,6 +3,7 @@ module ContainerRegistry
     attr_reader :repository, :name
 
     delegate :registry, :client, to: :repository
+    delegate :revision, :short_revision, to: :config_blob, allow_nil: true
 
     def initialize(repository, name)
       @repository, @name = repository, name
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 6d0e30e916f9fe2993d56f4bd45e18a443147828..831f1e635baca56b041c8c4e1f0c0de61eb3fb47 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -5,6 +5,8 @@
 #
 module Gitlab
   module Access
+    class AccessDeniedError < StandardError; end
+
     GUEST     = 10
     REPORTER  = 20
     DEVELOPER = 30
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 7e3f5abba621cddf21ce81837a51654b8085e97d..ab7b811c5d81c59e3d45194c3f91f35c3c1c9abd 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -31,7 +31,7 @@ module Grack
 
       auth!
 
-      lfs_response = Gitlab::Lfs::Router.new(project, @user, @request).try_call
+      lfs_response = Gitlab::Lfs::Router.new(project, @user, @ci, @request).try_call
       return lfs_response unless lfs_response.nil?
 
       if @user.nil? && !@ci
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index e2fee6b9f3ecb98847f35b6ec9bf73f65b664f6d..047c77c6fc2f713a72394fb2f4a8a1ecbc6cc75c 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -37,7 +37,7 @@ module Gitlab
         end
 
         def diffs
-          @diffs ||= (safe_diff_files(compare.diffs, diff_refs) if compare)
+          @diffs ||= (safe_diff_files(compare.diffs(max_files: 30), diff_refs) if compare)
         end
 
         def diffs_count
diff --git a/lib/gitlab/gitignore.rb b/lib/gitlab/gitignore.rb
deleted file mode 100644
index f46b43b61a4e8efcb5ea777a23f3210b7eb6d614..0000000000000000000000000000000000000000
--- a/lib/gitlab/gitignore.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-module Gitlab
-  class Gitignore
-    FILTER_REGEX = /\.gitignore\z/.freeze
-
-    def initialize(path)
-      @path = path
-    end
-
-    def name
-      File.basename(@path, '.gitignore')
-    end
-
-    def content
-      File.read(@path)
-    end
-
-    class << self
-      def all
-        languages_frameworks + global
-      end
-
-      def find(key)
-        file_name = "#{key}.gitignore"
-
-        directory = select_directory(file_name)
-        directory ? new(File.join(directory, file_name)) : nil
-      end
-
-      def global
-        files_for_folder(global_dir).map { |file| new(File.join(global_dir, file)) }
-      end
-
-      def languages_frameworks
-        files_for_folder(gitignore_dir).map { |file| new(File.join(gitignore_dir, file)) }
-      end
-
-      private
-
-      def select_directory(file_name)
-        [gitignore_dir, global_dir].find { |dir| File.exist?(File.join(dir, file_name)) }
-      end
-
-      def global_dir
-        File.join(gitignore_dir, 'Global')
-      end
-
-      def gitignore_dir
-        Rails.root.join('vendor/gitignore')
-      end
-
-      def files_for_folder(dir)
-        Dir.glob("#{dir.to_s}/*.gitignore").map { |file| file.gsub(FILTER_REGEX, '') }
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb
index 9d9617761b3b2be1284e30554c4a9904296c87fd..e3ed2f6791d5dc852538c964f9e0e87055a29cfa 100644
--- a/lib/gitlab/lfs/response.rb
+++ b/lib/gitlab/lfs/response.rb
@@ -2,10 +2,11 @@ module Gitlab
   module Lfs
     class Response
 
-      def initialize(project, user, request)
+      def initialize(project, user, ci, request)
         @origin_project = project
         @project = storage_project(project)
         @user = user
+        @ci = ci
         @env = request.env
         @request = request
       end
@@ -189,7 +190,7 @@ module Gitlab
         return render_not_enabled unless Gitlab.config.lfs.enabled
 
         unless @project.public?
-          return render_unauthorized unless @user
+          return render_unauthorized unless @user || @ci
           return render_forbidden unless user_can_fetch?
         end
 
@@ -210,7 +211,7 @@ module Gitlab
 
       def user_can_fetch?
         # Check user access against the project they used to initiate the pull
-        @user.can?(:download_code, @origin_project)
+        @ci || @user.can?(:download_code, @origin_project)
       end
 
       def user_can_push?
diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb
index 78d028911021612dc90709302f46e02ca535a9d7..69bd5e6230587830b381fcf1a4adb378b09b2e61 100644
--- a/lib/gitlab/lfs/router.rb
+++ b/lib/gitlab/lfs/router.rb
@@ -1,9 +1,12 @@
 module Gitlab
   module Lfs
     class Router
-      def initialize(project, user, request)
+      attr_reader :project, :user, :ci, :request
+
+      def initialize(project, user, ci, request)
         @project = project
         @user = user
+        @ci = ci
         @env = request.env
         @request = request
       end
@@ -80,7 +83,7 @@ module Gitlab
       def lfs
         return unless @project
 
-        Gitlab::Lfs::Response.new(@project, @user, @request)
+        Gitlab::Lfs::Response.new(@project, @user, @ci, @request)
       end
 
       def sanitize_tmp_filename(name)
diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb
new file mode 100644
index 0000000000000000000000000000000000000000..760ff3e614a699b6055fb7573add67df79fca1e5
--- /dev/null
+++ b/lib/gitlab/template/base_template.rb
@@ -0,0 +1,67 @@
+module Gitlab
+  module Template
+    class BaseTemplate
+      def initialize(path)
+        @path = path
+      end
+
+      def name
+        File.basename(@path, self.class.extension)
+      end
+
+      def content
+        File.read(@path)
+      end
+
+      class << self
+        def all
+          self.categories.keys.flat_map { |cat| by_category(cat) }
+        end
+
+        def find(key)
+          file_name = "#{key}#{self.extension}"
+
+          directory = select_directory(file_name)
+          directory ? new(File.join(category_directory(directory), file_name)) : nil
+        end
+
+        def categories
+          raise NotImplementedError
+        end
+
+        def extension
+          raise NotImplementedError
+        end
+
+        def base_dir
+          raise NotImplementedError
+        end
+
+        def by_category(category)
+          templates_for_directory(category_directory(category))
+        end
+
+        def category_directory(category)
+          File.join(base_dir, categories[category])
+        end
+
+        private
+
+        def select_directory(file_name)
+          categories.keys.find do |category|
+            File.exist?(File.join(category_directory(category), file_name))
+          end
+        end
+
+        def templates_for_directory(dir)
+          dir << '/' unless dir.end_with?('/')
+          Dir.glob(File.join(dir, "*#{self.extension}")).select { |f| f =~ filter_regex }.map { |f| new(f) }
+        end
+
+        def filter_regex
+          @filter_reges ||= /#{Regexp.escape(extension)}\z/
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/template/gitignore.rb b/lib/gitlab/template/gitignore.rb
new file mode 100644
index 0000000000000000000000000000000000000000..964fbfd4de330ef82019a615d67be438dcb2eedf
--- /dev/null
+++ b/lib/gitlab/template/gitignore.rb
@@ -0,0 +1,22 @@
+module Gitlab
+  module Template
+    class Gitignore < BaseTemplate
+      class << self
+        def extension
+          '.gitignore'
+        end
+
+        def categories
+          {
+            "Languages" => '',
+            "Global"    => 'Global'
+          }
+        end
+
+        def base_dir
+          Rails.root.join('vendor/gitignore')
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/template/gitlab_ci_yml.rb b/lib/gitlab/template/gitlab_ci_yml.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7f480fe33c0f51aeb653313517c2829035f542bd
--- /dev/null
+++ b/lib/gitlab/template/gitlab_ci_yml.rb
@@ -0,0 +1,27 @@
+module Gitlab
+  module Template
+    class GitlabCiYml < BaseTemplate
+      def content
+        explanation = "# This file is a template, and might need editing before it works on your project."
+        [explanation, super].join("\n")
+      end
+
+      class << self
+        def extension
+          '.gitlab-ci.yml'
+        end
+
+        def categories
+          {
+            "General" => '',
+            "Pages" => 'Pages'
+          }
+        end
+
+        def base_dir
+          Rails.root.join('vendor/gitlab-ci-yml')
+        end
+      end
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/import_export.rake b/lib/tasks/gitlab/import_export.rake
new file mode 100644
index 0000000000000000000000000000000000000000..c2c6031db670d269f9726ef1f1ca693d84a3b986
--- /dev/null
+++ b/lib/tasks/gitlab/import_export.rake
@@ -0,0 +1,13 @@
+namespace :gitlab do
+  namespace :import_export do
+    desc "GitLab | Show Import/Export version"
+    task version: :environment do
+      puts "Import/Export v#{Gitlab::ImportExport.version}"
+    end
+
+    desc "GitLab | Display exported DB structure"
+    task data: :environment do
+      puts YAML.load_file(Gitlab::ImportExport.config_file)['project_tree'].to_yaml(:SortKeys => true)
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/update_gitignore.rake b/lib/tasks/gitlab/update_gitignore.rake
deleted file mode 100644
index 4fd48cccb1d4606ef8c2124fb866d11dd4206bdb..0000000000000000000000000000000000000000
--- a/lib/tasks/gitlab/update_gitignore.rake
+++ /dev/null
@@ -1,46 +0,0 @@
-namespace :gitlab do
-  desc "GitLab | Update gitignore"
-  task :update_gitignore do
-    unless clone_gitignores
-      puts "Cloning the gitignores failed".color(:red)
-      return
-    end
-
-    remove_unneeded_files(gitignore_directory)
-    remove_unneeded_files(global_directory)
-
-    puts "Done".color(:green)
-  end
-
-  def clone_gitignores
-    FileUtils.rm_rf(gitignore_directory) if Dir.exist?(gitignore_directory)
-    FileUtils.cd vendor_directory
-
-    system('git clone --depth=1 --branch=master https://github.com/github/gitignore.git')
-  end
-
-  # Retain only certain files:
-  # - The LICENSE, because we have to
-  # - The sub dir global
-  # - The gitignores themself
-  # - Dir.entires returns also the entries '.' and '..'
-  def remove_unneeded_files(path)
-    Dir.foreach(path) do |file|
-      FileUtils.rm_rf(File.join(path, file)) unless file =~ /(\.{1,2}|LICENSE|Global|\.gitignore)\z/
-    end
-  end
-
-  private
-
-  def vendor_directory
-    Rails.root.join('vendor')
-  end
-
-  def gitignore_directory
-    File.join(vendor_directory, 'gitignore')
-  end
-
-  def global_directory
-    File.join(gitignore_directory, 'Global')
-  end
-end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
new file mode 100644
index 0000000000000000000000000000000000000000..4f76dad728633ea8a91d4a4f8abd9b71fac4b431
--- /dev/null
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -0,0 +1,54 @@
+namespace :gitlab do
+  desc "GitLab | Update templates"
+  task :update_templates do
+    TEMPLATE_DATA.each { |template| update(template) }
+  end
+
+  def update(template)
+    sub_dir = template.repo_url.match(/([a-z-]+)\.git\z/)[1]
+    dir = File.join(vendor_directory, sub_dir)
+
+    unless clone_repository(template.repo_url, dir)
+      puts "Cloning the #{sub_dir} templates failed".red
+      return
+    end
+
+    remove_unneeded_files(dir, template.cleanup_regex)
+    puts "Done".green
+  end
+
+  def clone_repository(url, directory)
+    FileUtils.rm_rf(directory) if Dir.exist?(directory)
+
+    system("git clone #{url} --depth=1 --branch=master #{directory}")
+  end
+
+  # Retain only certain files:
+  # - The LICENSE, because we have to
+  # - The sub dirs so we can organise the file by category
+  # - The templates themself
+  # - Dir.entries returns also the entries '.' and '..'
+  def remove_unneeded_files(directory, regex)
+    Dir.foreach(directory) do |file|
+      FileUtils.rm_rf(File.join(directory, file)) unless file =~ regex
+    end
+  end
+
+  private
+
+  Template = Struct.new(:repo_url, :cleanup_regex)
+  TEMPLATE_DATA = [
+    Template.new(
+      "https://github.com/github/gitignore.git",
+      /(\.{1,2}|LICENSE|Global|\.gitignore)\z/
+    ),
+    Template.new(
+      "https://gitlab.com/gitlab-org/gitlab-ci-yml.git",
+      /(\.{1,2}|LICENSE|Pages|\.gitlab-ci.yml)\z/
+    )
+  ]
+
+  def vendor_directory
+    Rails.root.join('vendor')
+  end
+end
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 89c2c26a367630f87304f59d4d42a9290be5ec89..c8601341d54457927894554320a8b3eb672f6a3e 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -118,9 +118,7 @@ describe Groups::GroupMembersController do
         it 'cannot removes himself from the group' do
           delete :leave, group_id: group
 
-          expect(response).to redirect_to(group_path(group))
-          expect(response).to set_flash[:alert].to "You can not leave the \"#{group.name}\" group. Transfer or delete the group."
-          expect(group.users).to include user
+          expect(response.status).to eq(403)
         end
       end
 
@@ -134,7 +132,7 @@ describe Groups::GroupMembersController do
           delete :leave, group_id: group
 
           expect(response).to set_flash.to 'Your access request to the group has been withdrawn.'
-          expect(response).to redirect_to(dashboard_groups_path)
+          expect(response).to redirect_to(group_path(group))
           expect(group.members.request).to be_empty
           expect(group.users).not_to include user
         end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index fc5f458e79543b3496af06c64b6e6fe380716492..e5e750c855f258c924fd35d4e9bae7fe6fb0e30d 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -171,11 +171,7 @@ describe Projects::ProjectMembersController do
           delete :leave, namespace_id: project.namespace,
                          project_id: project
 
-          expect(response).to redirect_to(
-            namespace_project_path(project.namespace, project)
-          )
-          expect(response).to set_flash[:alert].to "You can not leave the \"#{project.human_name}\" project. Transfer or delete the project."
-          expect(project.users).to include user
+          expect(response.status).to eq(403)
         end
       end
 
@@ -190,7 +186,7 @@ describe Projects::ProjectMembersController do
                          project_id: project
 
           expect(response).to set_flash.to 'Your access request to the project has been withdrawn.'
-          expect(response).to redirect_to(dashboard_projects_path)
+          expect(response).to redirect_to(namespace_project_path(project.namespace, project))
           expect(project.members.request).to be_empty
           expect(project.users).not_to include user
         end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index fba545560c7a53f9b3b7eb037ce5d3b1121b8d9a..146b2c2e131e822c43be6bb582a5da1a67f13381 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -237,4 +237,24 @@ describe ProjectsController do
       expect(response.status).to eq(401)
     end
   end
+
+  describe "GET refs" do
+    it "should get a list of branches and tags" do
+      get :refs, namespace_id: public_project.namespace.path, id: public_project.path
+
+      parsed_body = JSON.parse(response.body)
+      expect(parsed_body["Branches"]).to include("master")
+      expect(parsed_body["Tags"]).to include("v1.0.0")
+      expect(parsed_body["Commits"]).to be_nil
+    end
+
+    it "should get a list of branches, tags and commits" do
+      get :refs, namespace_id: public_project.namespace.path, id: public_project.path, ref: "123456"
+
+      parsed_body = JSON.parse(response.body)
+      expect(parsed_body["Branches"]).to include("master")
+      expect(parsed_body["Tags"]).to include("v1.0.0")
+      expect(parsed_body["Commits"]).to include("123456")
+    end
+  end
 end
diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/pipelines.rb
similarity index 100%
rename from spec/factories/ci/commits.rb
rename to spec/factories/ci/pipelines.rb
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 9499cd4e02529dba98feffbaf46322b524723c44..2d297776cb021a87aeea909c7a889d39159717f2 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -60,6 +60,40 @@ describe "Admin Runners" do
       it { expect(page).to have_content(@project1.name_with_namespace) }
       it { expect(page).not_to have_content(@project2.name_with_namespace) }
     end
+
+    describe 'enable/create' do
+      before do
+        @project1.runners << runner
+        visit admin_runner_path(runner)
+      end
+
+      it 'enables specific runner for project' do
+        within '.unassigned-projects' do
+          click_on 'Enable'
+        end
+
+        assigned_project = page.find('.assigned-projects')
+
+        expect(assigned_project).to have_content(@project2.path)
+      end
+    end
+
+    describe 'disable/destroy' do
+      before do
+        @project1.runners << runner
+        visit admin_runner_path(runner)
+      end
+
+      it 'enables specific runner for project' do
+        within '.assigned-projects' do
+          click_on 'Disable'
+        end
+
+        new_runner_project = page.find('.unassigned-projects')
+
+        expect(new_runner_project).to have_content(@project1.path)
+      end
+    end
   end
 
   describe 'runners registration token' do
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 53b4f027117d3f7c55ce1a8ad4604b8d804a022b..203e55a36f299a8260e1f4be5e1f5ee6509d98da 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -26,7 +26,8 @@ describe "Container Registry" do
     end
 
     context 'when there are tags' do
-      it { expect(page).to have_content(tag_name)}
+      it { expect(page).to have_content(tag_name) }
+      it { expect(page).to have_content('d7a513a66') }
     end
   end
 
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index 40fea5211e966afac45df721ae01f56e2d51d4fd..7fb28f4174b01be2c13a32657d016700ecb6ecf0 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -20,7 +20,7 @@ feature 'Environments', feature: true do
 
     context 'without environments' do
       scenario 'does show no environments' do
-        expect(page).to have_content('No environments to show')
+        expect(page).to have_content('You don\'t have any environments right now.')
       end
     end
 
@@ -61,7 +61,7 @@ feature 'Environments', feature: true do
 
     context 'without deployments' do
       scenario 'does show no deployments' do
-        expect(page).to have_content('No deployments for')
+        expect(page).to have_content('You don\'t have any deployments right now.')
       end
     end
 
@@ -108,7 +108,7 @@ feature 'Environments', feature: true do
         end
 
         scenario 'does create a new pipeline' do
-          expect(page).to have_content('production')
+          expect(page).to have_content('Production')
         end
       end
 
diff --git a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..33bf6d3752f465c3ea020a0ac99820c7334a5734
--- /dev/null
+++ b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Groups > Members > Last owner cannot leave group', feature: true do
+  let(:owner) { create(:user) }
+  let(:group) { create(:group) }
+
+  background do
+    group.add_owner(owner)
+    login_as(owner)
+    visit group_path(group)
+  end
+
+  scenario 'user does not see a "Leave Group" link' do
+    expect(page).not_to have_content 'Leave Group'
+  end
+end
diff --git a/spec/features/groups/members/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3185ff924b943af13893c7173881e49400a8f5f5
--- /dev/null
+++ b/spec/features/groups/members/member_leaves_group_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'Groups > Members > Member leaves group', feature: true do
+  let(:user) { create(:user) }
+  let(:owner) { create(:user) }
+  let(:group) { create(:group, :public) }
+
+  background do
+    group.add_owner(owner)
+    group.add_developer(user)
+    login_as(user)
+    visit group_path(group)
+  end
+
+  scenario 'user leaves group' do
+    click_link 'Leave Group'
+
+    expect(current_path).to eq(dashboard_groups_path)
+    expect(group.users.exists?(user.id)).to be_falsey
+  end
+end
diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb
index 22525ce530b1522dd70ab8b94a857c2b2e593124..321c9bad7d06db4790c3656c1bf138c51a1034fa 100644
--- a/spec/features/groups/members/owner_manages_access_requests_spec.rb
+++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb
@@ -42,7 +42,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do
 
   def expect_visible_access_request(group, user)
     expect(group.members.request.exists?(user_id: user)).to be_truthy
-    expect(page).to have_content "#{group.name} access requests (1)"
+    expect(page).to have_content "#{group.name} access requests 1"
     expect(page).to have_content user.name
   end
 end
diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb
index a878a96b6ee398e62710d8eddfd1ebd3f1c9c44f..1ea607cbca065b1178ddfc704a20a4243a1925be 100644
--- a/spec/features/groups/members/user_requests_access_spec.rb
+++ b/spec/features/groups/members/user_requests_access_spec.rb
@@ -21,6 +21,7 @@ feature 'Groups > Members > User requests access', feature: true do
     expect(page).to have_content 'Your request for access has been queued for review.'
 
     expect(page).to have_content 'Withdraw Access Request'
+    expect(page).not_to have_content 'Leave Group'
   end
 
   scenario 'user is not listed in the group members page' do
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index c3cb3379440e6e2e38787dde0e9802005d127d0d..5065dfb849cf07d3471d5fe1adaaa81231e0adb7 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -22,7 +22,7 @@ describe 'Issues', feature: true do
 
     before do
       visit edit_namespace_project_issue_path(project.namespace, project, issue)
-      click_button "Go full screen"
+      find('.js-zen-enter').click
     end
 
     it 'should open new issue popup' do
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 51be81d634c17e30119c1ae7b1328366ecc0d09e..01e90618a98cd68e233e6a4d655cb1bd1586fc53 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 feature 'list of badges' do
-  include Select2Helper
-
   background do
     user = create(:user)
     project = create(:project)
@@ -24,7 +22,11 @@ feature 'list of badges' do
   end
 
   scenario 'user changes current ref on badges list page', js: true do
-    select2('improve/awesome', from: '#ref')
+    first('.js-project-refs-dropdown').click
+
+    page.within '.project-refs-form' do
+      click_link 'improve/awesome'
+    end
 
     expect(page).to have_content 'badges/improve/awesome/build.svg'
   end
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d516e8ce55a5ce648877b8bd52e627b472281c63
--- /dev/null
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+feature 'User wants to add a .gitlab-ci.yml file', feature: true do
+  include WaitForAjax
+
+  before do
+    user = create(:user)
+    project = create(:project)
+    project.team << [user, :master]
+    login_as user
+    visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitlab-ci.yml')
+  end
+
+  scenario 'user can see .gitlab-ci.yml dropdown' do
+    expect(page).to have_css('.gitlab-ci-yml-selector')
+  end
+
+  scenario 'user can pick a template from the dropdown', js: true do
+    find('.js-gitlab-ci-yml-selector').click
+    wait_for_ajax
+    within '.gitlab-ci-yml-selector' do
+      find('.dropdown-input-field').set('jekyll')
+      find('.dropdown-content li', text: 'jekyll').click
+    end
+    wait_for_ajax
+
+    expect(page).to have_content('This file is a template, and might need editing before it works on your project')
+    expect(page).to have_content('jekyll build -d test')
+  end
+end
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index 5fe4caa12f07492d1991d041190024debe9578b7..aa2d906fa2e9c09d025aed0629ec5731c1716dc5 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -41,7 +41,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do
 
   def expect_visible_access_request(project, user)
     expect(project.members.request.exists?(user_id: user)).to be_truthy
-    expect(page).to have_content "#{project.name} access requests (1)"
+    expect(page).to have_content "#{project.name} access requests 1"
     expect(page).to have_content user.name
   end
 end
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..79dec442818be6f4be60d91f36ee52aa7f03de27
--- /dev/null
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Member leaves project', feature: true do
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+
+  background do
+    project.team << [user, :developer]
+    login_as(user)
+    visit namespace_project_path(project.namespace, project)
+  end
+
+  scenario 'user leaves project' do
+    click_link 'Leave Project'
+
+    expect(current_path).to eq(dashboard_projects_path)
+    expect(project.users.exists?(user.id)).to be_falsey
+  end
+end
diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..67811b1048e6c5df768e7e358a23153158447dde
--- /dev/null
+++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Owner cannot leave project', feature: true do
+  let(:owner) { create(:user) }
+  let(:project) { create(:project) }
+
+  background do
+    project.team << [owner, :owner]
+    login_as(owner)
+    visit namespace_project_path(project.namespace, project)
+  end
+
+  scenario 'user does not see a "Leave Project" link' do
+    expect(page).not_to have_content 'Leave Project'
+  end
+end
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index fd92a3a2f0cf20eb61d0dcb6bfd8f5acd305e80a..af420c170ef55aa96d9dcf0da0f690fceab66fd7 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -21,6 +21,7 @@ feature 'Projects > Members > User requests access', feature: true do
     expect(page).to have_content 'Your request for access has been queued for review.'
 
     expect(page).to have_content 'Withdraw Access Request'
+    expect(page).not_to have_content 'Leave Project'
   end
 
   scenario 'user is not listed in the project members page' do
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 9dd0378d165111577c2a650022687150ca008a81..6fa8298d4895b7bd17d34792f4590d50e50ecda9 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -70,22 +70,6 @@ feature 'Project', feature: true do
     end
   end
 
-  describe 'leave project link' do
-    let(:user)    { create(:user) }
-    let(:project) { create(:project, namespace: user.namespace) }
-
-    before do
-      login_with(user)
-      project.team.add_user(user, Gitlab::Access::MASTER)
-      visit namespace_project_path(project.namespace, project)
-    end
-
-    it 'click project-settings and find leave project' do
-      find('#project-settings-button').click
-      expect(page).to have_link('Leave Project')
-    end
-  end
-
   describe 'project title' do
     include WaitForAjax
 
diff --git a/spec/fixtures/container_registry/tag_manifest.json b/spec/fixtures/container_registry/tag_manifest.json
index 1b6008e2872606d99320c32e8cf6566c9d46bf0f..8d1b874c29bb5431e6de9bebcbd1d6e7abf9a952 100644
--- a/spec/fixtures/container_registry/tag_manifest.json
+++ b/spec/fixtures/container_registry/tag_manifest.json
@@ -1 +1,16 @@
-{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/octet-stream","size":1145,"digest":"sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","size":2319870,"digest":"sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"}]}
+{
+  "schemaVersion": 2,
+  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
+  "config": {
+    "mediaType": "application/octet-stream",
+    "size": 1145,
+    "digest": "sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"
+  },
+  "layers": [
+    {
+      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
+      "size": 2319870,
+      "digest": "sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"
+    }
+  ]
+}
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index f6c1005d2655971e5585000f02f797e60a9e17cc..bb28866f01009fca5dc0e0e6a081f6261750c215 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -174,51 +174,6 @@ describe ApplicationHelper do
     end
   end
 
-  describe 'grouped_options_refs' do
-    let(:options) { helper.grouped_options_refs }
-    let(:project) { create(:project) }
-
-    before do
-      assign(:project, project)
-
-      # Override Rails' grouped_options_for_select helper to just return the
-      # first argument (`options`), since it's easier to work with than the
-      # generated HTML.
-      allow(helper).to receive(:grouped_options_for_select).
-        and_wrap_original { |_, *args| args.first }
-    end
-
-    it 'includes a list of branch names' do
-      expect(options[0][0]).to eq('Branches')
-      expect(options[0][1]).to include('master', 'feature')
-    end
-
-    it 'includes a list of tag names' do
-      expect(options[1][0]).to eq('Tags')
-      expect(options[1][1]).to include('v1.0.0', 'v1.1.0')
-    end
-
-    it 'includes a specific commit ref if defined' do
-      # Must be an instance variable
-      ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8'
-      assign(:ref, ref)
-
-      expect(options[2][0]).to eq('Commit')
-      expect(options[2][1]).to eq([ref])
-    end
-
-    it 'sorts tags in a natural order' do
-      # Stub repository.tag_names to make sure we get some valid testing data
-      expect(project.repository).to receive(:tag_names).
-        and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿',
-                    'v1.0.9a', 'v2.0-rc1', 'v2.0rc2'])
-
-      expect(options[1][1]).
-        to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0rc1¿', 'v2.0-rc1', 'v1.0.10',
-               'v1.0.9', 'v1.0.9a'])
-    end
-  end
-
   describe 'simple_sanitize' do
     let(:a_tag) { '<a href="#">Foo</a>' }
 
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index ea27f36e9b5131756cc858c0b9df2179792662d3..71f0c1076c5fceddd7c9e9afa3ad0e0196624c39 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -1,3 +1,4 @@
+#= require lib/text_utility
 #= require issue
 
 describe 'Issue', ->
@@ -38,7 +39,7 @@ describe 'reopen/close issue', ->
     expect(typeof $btnClose.prop('disabled')).toBe('undefined')
 
     $btnClose.trigger('click')
-    
+
     expect($btnReopen).toBeVisible()
     expect($btnClose).toBeHidden()
     expect($('div.status-box-closed')).toBeVisible()
@@ -50,7 +51,7 @@ describe 'reopen/close issue', ->
       expect(req.type).toBe('PUT')
       expect(req.url).toBe('http://goesnowhere.nothing/whereami')
       req.success saved: false
-    
+
     $btnClose = $('a.btn-close')
     $btnReopen = $('a.btn-reopen')
     $btnClose.attr('href','http://goesnowhere.nothing/whereami')
@@ -59,7 +60,7 @@ describe 'reopen/close issue', ->
     expect(typeof $btnClose.prop('disabled')).toBe('undefined')
 
     $btnClose.trigger('click')
-    
+
     expect($btnReopen).toBeHidden()
     expect($btnClose).toBeVisible()
     expect($('div.status-box-closed')).toBeHidden()
@@ -73,7 +74,7 @@ describe 'reopen/close issue', ->
       expect(req.type).toBe('PUT')
       expect(req.url).toBe('http://goesnowhere.nothing/whereami')
       req.error()
-    
+
     $btnClose = $('a.btn-close')
     $btnReopen = $('a.btn-reopen')
     $btnClose.attr('href','http://goesnowhere.nothing/whereami')
@@ -82,7 +83,7 @@ describe 'reopen/close issue', ->
     expect(typeof $btnClose.prop('disabled')).toBe('undefined')
 
     $btnClose.trigger('click')
-    
+
     expect($btnReopen).toBeHidden()
     expect($btnClose).toBeVisible()
     expect($('div.status-box-closed')).toBeHidden()
@@ -105,4 +106,4 @@ describe 'reopen/close issue', ->
     expect($btnReopen).toBeHidden()
     expect($btnClose).toBeVisible()
     expect($('div.status-box-open')).toBeVisible()
-    expect($('div.status-box-closed')).toBeHidden()
\ No newline at end of file
+    expect($('div.status-box-closed')).toBeHidden()
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 0e6685f0ffb5751379b17622a7cfac80dc4381e3..b9e4a4eaf0ecd54ce4ed5306f2f6956e8be37160 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -132,11 +132,8 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
       path = 'files/images/한글.png'
       escaped = Addressable::URI.escape(path)
 
-      # Stub these methods so the file doesn't actually need to be in the repo
-      allow_any_instance_of(described_class).
-        to receive(:file_exists?).and_return(true)
-      allow_any_instance_of(described_class).
-        to receive(:image?).with(path).and_return(true)
+      # Stub this method so the file doesn't actually need to be in the repo
+      allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
 
       doc = filter(image(escaped))
       expect(doc.at_css('img')['src']).to match '/raw/'
diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb
index 88814bc474d15cc4174f90e3ac2ddd73c3026dc0..659facd6c19e663dfa06f4b061c5c2b80cd227e2 100644
--- a/spec/lib/gitlab/lfs/lfs_router_spec.rb
+++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb
@@ -17,12 +17,15 @@ describe Gitlab::Lfs::Router, lib: true do
     }
   end
 
-  let(:lfs_router_auth) { new_lfs_router(project, user) }
-  let(:lfs_router_noauth) { new_lfs_router(project, nil) }
-  let(:lfs_router_public_auth) { new_lfs_router(public_project, user) }
-  let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) }
-  let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) }
-  let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) }
+  let(:lfs_router_auth) { new_lfs_router(project, user: user) }
+  let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) }
+  let(:lfs_router_noauth) { new_lfs_router(project) }
+  let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) }
+  let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) }
+  let(:lfs_router_public_noauth) { new_lfs_router(public_project) }
+  let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) }
+  let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) }
+  let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) }
 
   let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" }
   let(:sample_size) { 499013 }
@@ -80,6 +83,7 @@ describe Gitlab::Lfs::Router, lib: true do
 
       context 'with required headers' do
         before do
+          project.lfs_objects << lfs_object
           env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile"
         end
 
@@ -91,7 +95,6 @@ describe Gitlab::Lfs::Router, lib: true do
 
         context 'when user has project access' do
           before do
-            project.lfs_objects << lfs_object
             project.team << [user, :master]
           end
 
@@ -104,6 +107,17 @@ describe Gitlab::Lfs::Router, lib: true do
             expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
           end
         end
+
+        context 'when CI is authorized' do
+          it "responds with status 200" do
+            expect(lfs_router_ci_auth.try_call.first).to eq(200)
+          end
+
+          it "responds with the file location" do
+            expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream")
+            expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
+          end
+        end
       end
 
       context 'without required headers' do
@@ -134,143 +148,145 @@ describe Gitlab::Lfs::Router, lib: true do
     end
 
     describe 'download' do
-      describe 'when user is authenticated' do
-        before do
-          body = { 'operation' => 'download',
-                   'objects' => [
-                     { 'oid' => sample_oid,
-                       'size' => sample_size
-                     }]
-          }.to_json
-          env['rack.input'] = StringIO.new(body)
-        end
+      before do
+        body = { 'operation' => 'download',
+                 'objects' => [
+                   { 'oid' => sample_oid,
+                     'size' => sample_size
+                   }]
+        }.to_json
+        env['rack.input'] = StringIO.new(body)
+      end
 
-        describe 'when user has download access' do
+      shared_examples 'an authorized requests' do
+        context 'when downloading an lfs object that is assigned to our project' do
           before do
-            @auth = authorize(user)
-            env["HTTP_AUTHORIZATION"] = @auth
-            project.team << [user, :reporter]
+            project.lfs_objects << lfs_object
           end
 
-          context 'when downloading an lfs object that is assigned to our project' do
-            before do
-              project.lfs_objects << lfs_object
-            end
-
-            it 'responds with status 200 and href to download' do
-              response = lfs_router_auth.try_call
-              expect(response.first).to eq(200)
-              response_body = ActiveSupport::JSON.decode(response.last.first)
+          it 'responds with status 200 and href to download' do
+            response = router.try_call
+            expect(response.first).to eq(200)
+            response_body = ActiveSupport::JSON.decode(response.last.first)
 
-              expect(response_body).to eq('objects' => [
-                { 'oid' => sample_oid,
-                  'size' => sample_size,
-                  'actions' => {
-                    'download' => {
-                      'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
-                      'header' => { 'Authorization' => @auth }
-                    }
+            expect(response_body).to eq('objects' => [
+              { 'oid' => sample_oid,
+                'size' => sample_size,
+                'actions' => {
+                  'download' => {
+                    'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+                    'header' => { 'Authorization' => auth }
                   }
-                }])
-            end
+                }
+              }])
           end
+        end
 
-          context 'when downloading an lfs object that is assigned to other project' do
-            before do
-              public_project.lfs_objects << lfs_object
-            end
+        context 'when downloading an lfs object that is assigned to other project' do
+          before do
+            public_project.lfs_objects << lfs_object
+          end
 
-            it 'responds with status 200 and error message' do
-              response = lfs_router_auth.try_call
-              expect(response.first).to eq(200)
-              response_body = ActiveSupport::JSON.decode(response.last.first)
+          it 'responds with status 200 and error message' do
+            response = router.try_call
+            expect(response.first).to eq(200)
+            response_body = ActiveSupport::JSON.decode(response.last.first)
 
-              expect(response_body).to eq('objects' => [
-                { 'oid' => sample_oid,
-                  'size' => sample_size,
-                  'error' => {
-                    'code' => 404,
-                    'message' => "Object does not exist on the server or you don't have permissions to access it",
-                  }
-                }])
-            end
+            expect(response_body).to eq('objects' => [
+              { 'oid' => sample_oid,
+                'size' => sample_size,
+                'error' => {
+                  'code' => 404,
+                  'message' => "Object does not exist on the server or you don't have permissions to access it",
+                }
+              }])
           end
+        end
 
-          context 'when downloading a lfs object that does not exist' do
-            before do
-              body = { 'operation' => 'download',
-                       'objects' => [
-                         { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
-                           'size' => 1575078
-                         }]
-              }.to_json
-              env['rack.input'] = StringIO.new(body)
-            end
+        context 'when downloading a lfs object that does not exist' do
+          before do
+            body = { 'operation' => 'download',
+                     'objects' => [
+                       { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+                         'size' => 1575078
+                       }]
+            }.to_json
+            env['rack.input'] = StringIO.new(body)
+          end
 
-            it "responds with status 200 and error message" do
-              response = lfs_router_auth.try_call
-              expect(response.first).to eq(200)
-              response_body = ActiveSupport::JSON.decode(response.last.first)
+          it "responds with status 200 and error message" do
+            response = router.try_call
+            expect(response.first).to eq(200)
+            response_body = ActiveSupport::JSON.decode(response.last.first)
 
-              expect(response_body).to eq('objects' => [
-                { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
-                  'size' => 1575078,
-                  'error' => {
-                    'code' => 404,
-                    'message' => "Object does not exist on the server or you don't have permissions to access it",
-                  }
-                }])
-            end
+            expect(response_body).to eq('objects' => [
+              { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+                'size' => 1575078,
+                'error' => {
+                  'code' => 404,
+                  'message' => "Object does not exist on the server or you don't have permissions to access it",
+                }
+              }])
           end
+        end
 
-          context 'when downloading one new and one existing lfs object' do
-            before do
-              body = { 'operation' => 'download',
-                       'objects' => [
-                         { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
-                           'size' => 1575078
-                         },
-                         { 'oid' => sample_oid,
-                           'size' => sample_size
-                         }
-                       ]
-              }.to_json
-              env['rack.input'] = StringIO.new(body)
-              project.lfs_objects << lfs_object
-            end
+        context 'when downloading one new and one existing lfs object' do
+          before do
+            body = { 'operation' => 'download',
+                     'objects' => [
+                       { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+                         'size' => 1575078
+                       },
+                       { 'oid' => sample_oid,
+                         'size' => sample_size
+                       }
+                     ]
+            }.to_json
+            env['rack.input'] = StringIO.new(body)
+            project.lfs_objects << lfs_object
+          end
 
-            it "responds with status 200 with upload hypermedia link for the new object" do
-              response = lfs_router_auth.try_call
-              expect(response.first).to eq(200)
-              response_body = ActiveSupport::JSON.decode(response.last.first)
+          it "responds with status 200 with upload hypermedia link for the new object" do
+            response = router.try_call
+            expect(response.first).to eq(200)
+            response_body = ActiveSupport::JSON.decode(response.last.first)
 
-              expect(response_body).to eq('objects' => [
-                { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
-                  'size' => 1575078,
-                  'error' => {
-                    'code' => 404,
-                    'message' => "Object does not exist on the server or you don't have permissions to access it",
-                  }
-                },
-                { 'oid' => sample_oid,
-                  'size' => sample_size,
-                  'actions' => {
-                    'download' => {
-                      'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
-                      'header' => { 'Authorization' => @auth }
-                    }
+            expect(response_body).to eq('objects' => [
+              { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+                'size' => 1575078,
+                'error' => {
+                  'code' => 404,
+                  'message' => "Object does not exist on the server or you don't have permissions to access it",
+                }
+              },
+              { 'oid' => sample_oid,
+                'size' => sample_size,
+                'actions' => {
+                  'download' => {
+                    'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+                    'header' => { 'Authorization' => auth }
                   }
-                }])
-            end
+                }
+              }])
           end
         end
+      end
+
+      context 'when user is authenticated' do
+        let(:auth) { authorize(user) }
+
+        before do
+          env["HTTP_AUTHORIZATION"] = auth
+          project.team << [user, role]
+        end
+
+        it_behaves_like 'an authorized requests' do
+          let(:role) { :reporter }
+          let(:router) { lfs_router_auth }
+        end
 
         context 'when user does is not member of the project' do
-          before do
-            @auth = authorize(user)
-            env["HTTP_AUTHORIZATION"] = @auth
-            project.team << [user, :guest]
-          end
+          let(:role) { :guest }
 
           it 'responds with 403' do
             expect(lfs_router_auth.try_call.first).to eq(403)
@@ -278,11 +294,7 @@ describe Gitlab::Lfs::Router, lib: true do
         end
 
         context 'when user does not have download access' do
-          before do
-            @auth = authorize(user)
-            env["HTTP_AUTHORIZATION"] = @auth
-            project.team << [user, :guest]
-          end
+          let(:role) { :guest }
 
           it 'responds with 403' do
             expect(lfs_router_auth.try_call.first).to eq(403)
@@ -290,18 +302,19 @@ describe Gitlab::Lfs::Router, lib: true do
         end
       end
 
-      context 'when user is not authenticated' do
+      context 'when CI is authorized' do
+        let(:auth) { 'gitlab-ci-token:password' }
+
         before do
-          body = { 'operation' => 'download',
-                   'objects' => [
-                     { 'oid' => sample_oid,
-                       'size' => sample_size
-                     }],
+          env["HTTP_AUTHORIZATION"] = auth
+        end
 
-          }.to_json
-          env['rack.input'] = StringIO.new(body)
+        it_behaves_like 'an authorized requests' do
+          let(:router) { lfs_router_ci_auth }
         end
+      end
 
+      context 'when user is not authenticated' do
         describe 'is accessing public project' do
           before do
             public_project.lfs_objects << lfs_object
@@ -338,17 +351,17 @@ describe Gitlab::Lfs::Router, lib: true do
     end
 
     describe 'upload' do
-      describe 'when user is authenticated' do
-        before do
-          body = { 'operation' => 'upload',
-                   'objects' => [
-                     { 'oid' => sample_oid,
-                       'size' => sample_size
-                     }]
-          }.to_json
-          env['rack.input'] = StringIO.new(body)
-        end
+      before do
+        body = { 'operation' => 'upload',
+                 'objects' => [
+                   { 'oid' => sample_oid,
+                     'size' => sample_size
+                   }]
+        }.to_json
+        env['rack.input'] = StringIO.new(body)
+      end
 
+      describe 'when request is authenticated' do
         describe 'when user has project push access' do
           before do
             @auth = authorize(user)
@@ -440,15 +453,15 @@ describe Gitlab::Lfs::Router, lib: true do
             expect(lfs_router_auth.try_call.first).to eq(403)
           end
         end
-      end
 
-      context 'when user is not authenticated' do
-        before do
-          env['rack.input'] = StringIO.new(
-            { 'objects' => [], 'operation' => 'upload' }.to_json
-          )
+        context 'when CI is authorized' do
+          it 'responds with 401' do
+            expect(lfs_router_ci_auth.try_call.first).to eq(401)
+          end
         end
+      end
 
+      context 'when user is not authenticated' do
         context 'when user has push access' do
           before do
             project.team << [user, :master]
@@ -465,6 +478,18 @@ describe Gitlab::Lfs::Router, lib: true do
           end
         end
       end
+
+      context 'when CI is authorized' do
+        let(:auth) { 'gitlab-ci-token:password' }
+
+        before do
+          env["HTTP_AUTHORIZATION"] = auth
+        end
+
+        it "responds with status 403" do
+          expect(lfs_router_public_ci_auth.try_call.first).to eq(401)
+        end
+      end
     end
 
     describe 'unsupported' do
@@ -490,13 +515,68 @@ describe Gitlab::Lfs::Router, lib: true do
       env['REQUEST_METHOD'] = 'PUT'
     end
 
-    describe 'to one project' do
-      describe 'when user has push access to the project' do
+    shared_examples 'unauthorized' do
+      context 'and request is sent by gitlab-workhorse to authorize the request' do
         before do
-          project.team << [user, :master]
+          header_for_upload_authorize(router.project)
+        end
+
+        it 'responds with status 401' do
+          expect(router.try_call.first).to eq(401)
+        end
+      end
+
+      context 'and request is sent by gitlab-workhorse to finalize the upload' do
+        before do
+          headers_for_upload_finalize(router.project)
+        end
+
+        it 'responds with status 401' do
+          expect(router.try_call.first).to eq(401)
+        end
+      end
+
+      context 'and request is sent with a malformed headers' do
+        before do
+          env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
+          env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd"
+        end
+
+        it 'does not recognize it as a valid lfs command' do
+          expect(router.try_call).to eq(nil)
+        end
+      end
+    end
+
+    shared_examples 'forbidden' do
+      context 'and request is sent by gitlab-workhorse to authorize the request' do
+        before do
+          header_for_upload_authorize(router.project)
+        end
+
+        it 'responds with 403' do
+          expect(router.try_call.first).to eq(403)
+        end
+      end
+
+      context 'and request is sent by gitlab-workhorse to finalize the upload' do
+        before do
+          headers_for_upload_finalize(router.project)
+        end
+
+        it 'responds with 403' do
+          expect(router.try_call.first).to eq(403)
         end
+      end
+    end
+
+    describe 'to one project' do
+      describe 'when user is authenticated' do
+        describe 'when user has push access to the project' do
+          before do
+            project.team << [user, :developer]
+          end
 
-        describe 'when user is authenticated' do
           context 'and request is sent by gitlab-workhorse to authorize the request' do
             before do
               header_for_upload_authorize(project)
@@ -524,100 +604,35 @@ describe Gitlab::Lfs::Router, lib: true do
           end
         end
 
-        describe 'when user is unauthenticated' do
-          let(:lfs_router_noauth) { new_lfs_router(project, nil) }
+        describe 'and user does not have push access' do
+          let(:router) { lfs_router_auth }
 
-          context 'and request is sent by gitlab-workhorse to authorize the request' do
-            before do
-              header_for_upload_authorize(project)
-            end
-
-            it 'responds with status 401' do
-              expect(lfs_router_noauth.try_call.first).to eq(401)
-            end
-          end
-
-          context 'and request is sent by gitlab-workhorse to finalize the upload' do
-            before do
-              headers_for_upload_finalize(project)
-            end
-
-            it 'responds with status 401' do
-              expect(lfs_router_noauth.try_call.first).to eq(401)
-            end
-          end
-
-          context 'and request is sent with a malformed headers' do
-            before do
-              env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
-              env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd"
-            end
-
-            it 'does not recognize it as a valid lfs command' do
-              expect(lfs_router_noauth.try_call).to eq(nil)
-            end
-          end
+          it_behaves_like 'forbidden'
         end
       end
 
-      describe 'and user does not have push access' do
-        describe 'when user is authenticated' do
-          context 'and request is sent by gitlab-workhorse to authorize the request' do
-            before do
-              header_for_upload_authorize(project)
-            end
-
-            it 'responds with 403' do
-              expect(lfs_router_auth.try_call.first).to eq(403)
-            end
-          end
-
-          context 'and request is sent by gitlab-workhorse to finalize the upload' do
-            before do
-              headers_for_upload_finalize(project)
-            end
-
-            it 'responds with 403' do
-              expect(lfs_router_auth.try_call.first).to eq(403)
-            end
-          end
-        end
+      context 'when CI is authenticated' do
+        let(:router) { lfs_router_ci_auth }
 
-        describe 'when user is unauthenticated' do
-          let(:lfs_router_noauth) { new_lfs_router(project, nil) }
-
-          context 'and request is sent by gitlab-workhorse to authorize the request' do
-            before do
-              header_for_upload_authorize(project)
-            end
+        it_behaves_like 'unauthorized'
+      end
 
-            it 'responds with 401' do
-              expect(lfs_router_noauth.try_call.first).to eq(401)
-            end
-          end
+      context 'for unauthenticated' do
+        let(:router) { new_lfs_router(project) }
 
-          context 'and request is sent by gitlab-workhorse to finalize the upload' do
-            before do
-              headers_for_upload_finalize(project)
-            end
-
-            it 'responds with 401' do
-              expect(lfs_router_noauth.try_call.first).to eq(401)
-            end
-          end
-        end
+        it_behaves_like 'unauthorized'
       end
     end
 
-    describe "to a forked project" do
+    describe 'to a forked project' do
       let(:forked_project) { fork_project(public_project, user) }
 
-      describe 'when user has push access to the project' do
-        before do
-          forked_project.team << [user_two, :master]
-        end
+      describe 'when user is authenticated' do
+        describe 'when user has push access to the project' do
+          before do
+            forked_project.team << [user_two, :developer]
+          end
 
-        describe 'when user is authenticated' do
           context 'and request is sent by gitlab-workhorse to authorize the request' do
             before do
               header_for_upload_authorize(forked_project)
@@ -645,78 +660,28 @@ describe Gitlab::Lfs::Router, lib: true do
           end
         end
 
-        describe 'when user is unauthenticated' do
-          context 'and request is sent by gitlab-workhorse to authorize the request' do
-            before do
-              header_for_upload_authorize(forked_project)
-            end
-
-            it 'responds with status 401' do
-              expect(lfs_router_forked_noauth.try_call.first).to eq(401)
-            end
-          end
-
-          context 'and request is sent by gitlab-workhorse to finalize the upload' do
-            before do
-              headers_for_upload_finalize(forked_project)
-            end
+        describe 'and user does not have push access' do
+          let(:router) { lfs_router_forked_auth }
 
-            it 'responds with status 401' do
-              expect(lfs_router_forked_noauth.try_call.first).to eq(401)
-            end
-          end
+          it_behaves_like 'forbidden'
         end
       end
 
-      describe 'and user does not have push access' do
-        describe 'when user is authenticated' do
-          context 'and request is sent by gitlab-workhorse to authorize the request' do
-            before do
-              header_for_upload_authorize(forked_project)
-            end
+      context 'when CI is authenticated' do
+        let(:router) { lfs_router_forked_ci_auth }
 
-            it 'responds with 403' do
-              expect(lfs_router_forked_auth.try_call.first).to eq(403)
-            end
-          end
-
-          context 'and request is sent by gitlab-workhorse to finalize the upload' do
-            before do
-              headers_for_upload_finalize(forked_project)
-            end
-
-            it 'responds with 403' do
-              expect(lfs_router_forked_auth.try_call.first).to eq(403)
-            end
-          end
-        end
-
-        describe 'when user is unauthenticated' do
-          context 'and request is sent by gitlab-workhorse to authorize the request' do
-            before do
-              header_for_upload_authorize(forked_project)
-            end
-
-            it 'responds with 401' do
-              expect(lfs_router_forked_noauth.try_call.first).to eq(401)
-            end
-          end
+        it_behaves_like 'unauthorized'
+      end
 
-          context 'and request is sent by gitlab-workhorse to finalize the upload' do
-            before do
-              headers_for_upload_finalize(forked_project)
-            end
+      context 'for unauthenticated' do
+        let(:router) { lfs_router_forked_noauth }
 
-            it 'responds with 401' do
-              expect(lfs_router_forked_noauth.try_call.first).to eq(401)
-            end
-          end
-        end
+        it_behaves_like 'unauthorized'
       end
 
       describe 'and second project not related to fork or a source project' do
         let(:second_project) { create(:project) }
-        let(:lfs_router_second_project) { new_lfs_router(second_project, user) }
+        let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) }
 
         before do
           public_project.lfs_objects << lfs_object
@@ -745,8 +710,8 @@ describe Gitlab::Lfs::Router, lib: true do
     ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
   end
 
-  def new_lfs_router(project, user)
-    Gitlab::Lfs::Router.new(project, user, request)
+  def new_lfs_router(project, user: nil, ci: false)
+    Gitlab::Lfs::Router.new(project, user, ci, request)
   end
 
   def header_for_upload_authorize(project)
diff --git a/spec/lib/gitlab/gitignore_spec.rb b/spec/lib/gitlab/template/gitignore_spec.rb
similarity index 87%
rename from spec/lib/gitlab/gitignore_spec.rb
rename to spec/lib/gitlab/template/gitignore_spec.rb
index 72baa516cc4ddd289f1a77de9027aa347010d8df..bc0ec9325cc106368d866f62a4d49f49d5cce8bb 100644
--- a/spec/lib/gitlab/gitignore_spec.rb
+++ b/spec/lib/gitlab/template/gitignore_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
-describe Gitlab::Gitignore do
-  subject { Gitlab::Gitignore }
+describe Gitlab::Template::Gitignore do
+  subject { described_class }
 
   describe '.all' do
     it 'strips the gitignore suffix' do
@@ -24,7 +24,7 @@ describe Gitlab::Gitignore do
     it 'returns the Gitignore object of a valid file' do
       ruby = subject.find('Ruby')
 
-      expect(ruby).to be_a Gitlab::Gitignore
+      expect(ruby).to be_a Gitlab::Template::Gitignore
       expect(ruby.name).to eq('Ruby')
     end
   end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 1e6eb20ab39259239f9d330bf45dc53a41c3e6b3..ae55a01ebea85a375351e8e98056af2a30b38da7 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -401,23 +401,56 @@ describe Notify do
     end
 
     describe 'project access requested' do
-      let(:project) { create(:project) }
-      let(:user) { create(:user) }
-      let(:project_member) do
-        project.request_access(user)
-        project.members.request.find_by(user_id: user.id)
+      context 'for a project in a user namespace' do
+        let(:project) { create(:project).tap { |p| p.team << [p.owner, :master, p.owner] } }
+        let(:user) { create(:user) }
+        let(:project_member) do
+          project.request_access(user)
+          project.members.request.find_by(user_id: user.id)
+        end
+        subject { Notify.member_access_requested_email('project', project_member.id) }
+
+        it_behaves_like 'an email sent from GitLab'
+        it_behaves_like 'it should not have Gmail Actions links'
+        it_behaves_like "a user cannot unsubscribe through footer link"
+
+        it 'contains all the useful information' do
+          to_emails = subject.header[:to].addrs
+          expect(to_emails.size).to eq(1)
+          expect(to_emails[0].address).to eq(project.members.owners_and_masters.first.user.notification_email)
+
+          is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
+          is_expected.to have_body_text /#{project.name_with_namespace}/
+          is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
+          is_expected.to have_body_text /#{project_member.human_access}/
+        end
       end
-      subject { Notify.member_access_requested_email('project', project_member.id) }
 
-      it_behaves_like 'an email sent from GitLab'
-      it_behaves_like 'it should not have Gmail Actions links'
-      it_behaves_like "a user cannot unsubscribe through footer link"
+      context 'for a project in a group' do
+        let(:group_owner) { create(:user) }
+        let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } }
+        let(:project) { create(:project, namespace: group) }
+        let(:user) { create(:user) }
+        let(:project_member) do
+          project.request_access(user)
+          project.members.request.find_by(user_id: user.id)
+        end
+        subject { Notify.member_access_requested_email('project', project_member.id) }
 
-      it 'contains all the useful information' do
-        is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
-        is_expected.to have_body_text /#{project.name_with_namespace}/
-        is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
-        is_expected.to have_body_text /#{project_member.human_access}/
+        it_behaves_like 'an email sent from GitLab'
+        it_behaves_like 'it should not have Gmail Actions links'
+        it_behaves_like "a user cannot unsubscribe through footer link"
+
+        it 'contains all the useful information' do
+          to_emails = subject.header[:to].addrs
+          expect(to_emails.size).to eq(1)
+          expect(to_emails[0].address).to eq(group.members.owners_and_masters.first.user.notification_email)
+
+          is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
+          is_expected.to have_body_text /#{project.name_with_namespace}/
+          is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
+          is_expected.to have_body_text /#{project_member.human_access}/
+        end
       end
     end
 
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 5d1fa8226e56346fe577a7e859f0141d6fb56e96..8154001cf460e4f0f87038220d89e7d8daeea5f5 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -2,7 +2,12 @@ require 'spec_helper'
 
 describe Ci::Build, models: true do
   let(:project) { create(:project) }
-  let(:pipeline) { create(:ci_pipeline, project: project) }
+
+  let(:pipeline) do
+    create(:ci_pipeline, project: project,
+                         sha: project.commit.id)
+  end
+
   let(:build) { create(:ci_build, pipeline: pipeline) }
 
   it { is_expected.to validate_presence_of :ref }
@@ -36,32 +41,44 @@ describe Ci::Build, models: true do
     subject { build.ignored? }
 
     context 'if build is not allowed to fail' do
-      before { build.allow_failure = false }
+      before do
+        build.allow_failure = false
+      end
 
       context 'and build.status is success' do
-        before { build.status = 'success' }
+        before do
+          build.status = 'success'
+        end
 
         it { is_expected.to be_falsey }
       end
 
       context 'and build.status is failed' do
-        before { build.status = 'failed' }
+        before do
+          build.status = 'failed'
+        end
 
         it { is_expected.to be_falsey }
       end
     end
 
     context 'if build is allowed to fail' do
-      before { build.allow_failure = true }
+      before do
+        build.allow_failure = true
+      end
 
       context 'and build.status is success' do
-        before { build.status = 'success' }
+        before do
+          build.status = 'success'
+        end
 
         it { is_expected.to be_falsey }
       end
 
       context 'and build.status is failed' do
-        before { build.status = 'failed' }
+        before do
+          build.status = 'failed'
+        end
 
         it { is_expected.to be_truthy }
       end
@@ -75,7 +92,9 @@ describe Ci::Build, models: true do
 
     context 'if build.trace contains text' do
       let(:text) { 'example output' }
-      before { build.trace = text }
+      before do
+        build.trace = text
+      end
 
       it { is_expected.to include(text) }
       it { expect(subject.length).to be >= text.length }
@@ -188,7 +207,9 @@ describe Ci::Build, models: true do
         ]
       end
 
-      before { build.update_attributes(stage: 'stage') }
+      before do
+        build.update_attributes(stage: 'stage')
+      end
 
       it { is_expected.to eq(predefined_variables + yaml_variables) }
 
@@ -199,7 +220,9 @@ describe Ci::Build, models: true do
           ]
         end
 
-        before { build.update_attributes(tag: true) }
+        before do
+          build.update_attributes(tag: true)
+        end
 
         it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) }
       end
@@ -257,57 +280,6 @@ describe Ci::Build, models: true do
     end
   end
 
-  describe '#can_be_served?' do
-    let(:runner) { create(:ci_runner) }
-
-    before { build.project.runners << runner }
-
-    context 'when runner does not have tags' do
-      it 'can handle builds without tags' do
-        expect(build.can_be_served?(runner)).to be_truthy
-      end
-
-      it 'cannot handle build with tags' do
-        build.tag_list = ['aa']
-        expect(build.can_be_served?(runner)).to be_falsey
-      end
-    end
-
-    context 'when runner has tags' do
-      before { runner.tag_list = ['bb', 'cc'] }
-
-      shared_examples 'tagged build picker' do
-        it 'can handle build with matching tags' do
-          build.tag_list = ['bb']
-          expect(build.can_be_served?(runner)).to be_truthy
-        end
-
-        it 'cannot handle build without matching tags' do
-          build.tag_list = ['aa']
-          expect(build.can_be_served?(runner)).to be_falsey
-        end
-      end
-
-      context 'when runner can pick untagged jobs' do
-        it 'can handle builds without tags' do
-          expect(build.can_be_served?(runner)).to be_truthy
-        end
-
-        it_behaves_like 'tagged build picker'
-      end
-
-      context 'when runner can not pick untagged jobs' do
-        before { runner.run_untagged = false }
-
-        it 'can not handle builds without tags' do
-          expect(build.can_be_served?(runner)).to be_falsey
-        end
-
-        it_behaves_like 'tagged build picker'
-      end
-    end
-  end
-
   describe '#has_tags?' do
     context 'when build has tags' do
       subject { create(:ci_build, tag_list: ['tag']) }
@@ -348,7 +320,7 @@ describe Ci::Build, models: true do
       end
 
       it 'that cannot handle build' do
-        expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false)
+        expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false)
         is_expected.to be_falsey
       end
 
@@ -360,7 +332,9 @@ describe Ci::Build, models: true do
 
     %w(pending).each do |state|
       context "if commit_status.status is #{state}" do
-        before { build.status = state }
+        before do
+          build.status = state
+        end
 
         it { is_expected.to be_truthy }
 
@@ -379,7 +353,9 @@ describe Ci::Build, models: true do
 
     %w(success failed canceled running).each do |state|
       context "if commit_status.status is #{state}" do
-        before { build.status = state }
+        before do
+          build.status = state
+        end
 
         it { is_expected.to be_falsey }
       end
@@ -390,7 +366,10 @@ describe Ci::Build, models: true do
     subject { build.artifacts? }
 
     context 'artifacts archive does not exist' do
-      before { build.update_attributes(artifacts_file: nil) }
+      before do
+        build.update_attributes(artifacts_file: nil)
+      end
+
       it { is_expected.to be_falsy }
     end
 
@@ -623,7 +602,9 @@ describe Ci::Build, models: true do
       let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
 
       describe '#erase' do
-        before { build.erase(erased_by: user) }
+        before do
+          build.erase(erased_by: user)
+        end
 
         context 'erased by user' do
           let!(:user) { create(:user, username: 'eraser') }
@@ -660,7 +641,9 @@ describe Ci::Build, models: true do
         end
 
         context 'build has been erased' do
-          before { build.erase }
+          before do
+            build.erase
+          end
 
           it { is_expected.to be true }
         end
@@ -668,7 +651,9 @@ describe Ci::Build, models: true do
 
       context 'metadata and build trace are not available' do
         let!(:build) { create(:ci_build, :success, :artifacts) }
-        before { build.remove_artifacts_metadata! }
+        before do
+          build.remove_artifacts_metadata!
+        end
 
         describe '#erase' do
           it 'should not raise error' do
@@ -678,4 +663,10 @@ describe Ci::Build, models: true do
       end
     end
   end
+
+  describe '#commit' do
+    it 'returns commit pipeline has been created for' do
+      expect(build.commit).to eq project.commit
+    end
+  end
 end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 5d04d8ffcff625f49ab0547c673031ece948f55d..ef65eb99328fc7177b175009e1992f96681255e8 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -20,34 +20,36 @@ describe Ci::Runner, models: true do
   end
 
   describe '#display_name' do
-    it 'should return the description if it has a value' do
+    it 'returns the description if it has a value' do
       runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
       expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448'
     end
 
-    it 'should return the token if it does not have a description' do
+    it 'returns the token if it does not have a description' do
       runner = FactoryGirl.create(:ci_runner)
       expect(runner.display_name).to eq runner.description
     end
 
-    it 'should return the token if the description is an empty string' do
+    it 'returns the token if the description is an empty string' do
       runner = FactoryGirl.build(:ci_runner, description: '', token: 'token')
       expect(runner.display_name).to eq runner.token
     end
   end
 
-  describe :assign_to do
+  describe '#assign_to' do
     let!(:project) { FactoryGirl.create :empty_project }
     let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) }
 
-    before { shared_runner.assign_to(project) }
+    before do
+      shared_runner.assign_to(project)
+    end
 
     it { expect(shared_runner).to be_specific }
     it { expect(shared_runner.projects).to eq([project]) }
     it { expect(shared_runner.only_for?(project)).to be_truthy }
   end
 
-  describe :online do
+  describe '.online' do
     subject { Ci::Runner.online }
 
     before do
@@ -58,60 +60,269 @@ describe Ci::Runner, models: true do
     it { is_expected.to eq([@runner2])}
   end
 
-  describe :online? do
+  describe '#online?' do
     let(:runner) { FactoryGirl.create(:ci_runner, :shared) }
 
     subject { runner.online? }
 
     context 'never contacted' do
-      before { runner.contacted_at = nil }
+      before do
+        runner.contacted_at = nil
+      end
 
       it { is_expected.to be_falsey }
     end
 
     context 'contacted long time ago time' do
-      before { runner.contacted_at = 1.year.ago }
+      before do
+        runner.contacted_at = 1.year.ago
+      end
 
       it { is_expected.to be_falsey }
     end
 
     context 'contacted 1s ago' do
-      before { runner.contacted_at = 1.second.ago }
+      before do
+        runner.contacted_at = 1.second.ago
+      end
 
       it { is_expected.to be_truthy }
     end
   end
 
-  describe :status do
+  describe '#can_pick?' do
+    let(:project) { create(:project) }
+    let(:pipeline) { create(:ci_pipeline, project: project) }
+    let(:build) { create(:ci_build, pipeline: pipeline) }
+    let(:runner) { create(:ci_runner) }
+
+    before do
+      build.project.runners << runner
+    end
+
+    context 'when runner does not have tags' do
+      it 'can handle builds without tags' do
+        expect(runner.can_pick?(build)).to be_truthy
+      end
+
+      it 'cannot handle build with tags' do
+        build.tag_list = ['aa']
+
+        expect(runner.can_pick?(build)).to be_falsey
+      end
+    end
+
+    context 'when runner has tags' do
+      before do
+        runner.tag_list = ['bb', 'cc']
+      end
+
+      shared_examples 'tagged build picker' do
+        it 'can handle build with matching tags' do
+          build.tag_list = ['bb']
+
+          expect(runner.can_pick?(build)).to be_truthy
+        end
+
+        it 'cannot handle build without matching tags' do
+          build.tag_list = ['aa']
+
+          expect(runner.can_pick?(build)).to be_falsey
+        end
+      end
+
+      context 'when runner can pick untagged jobs' do
+        it 'can handle builds without tags' do
+          expect(runner.can_pick?(build)).to be_truthy
+        end
+
+        it_behaves_like 'tagged build picker'
+      end
+
+      context 'when runner cannot pick untagged jobs' do
+        before do
+          runner.run_untagged = false
+        end
+
+        it 'cannot handle builds without tags' do
+          expect(runner.can_pick?(build)).to be_falsey
+        end
+
+        it_behaves_like 'tagged build picker'
+      end
+    end
+
+    context 'when runner is locked' do
+      before do
+        runner.locked = true
+      end
+
+      shared_examples 'locked build picker' do
+        context 'when runner cannot pick untagged jobs' do
+          before do
+            runner.run_untagged = false
+          end
+
+          it 'cannot handle builds without tags' do
+            expect(runner.can_pick?(build)).to be_falsey
+          end
+        end
+
+        context 'when having runner tags' do
+          before do
+            runner.tag_list = ['bb', 'cc']
+          end
+
+          it 'cannot handle it for builds without matching tags' do
+            build.tag_list = ['aa']
+
+            expect(runner.can_pick?(build)).to be_falsey
+          end
+        end
+      end
+
+      context 'when serving the same project' do
+        it 'can handle it' do
+          expect(runner.can_pick?(build)).to be_truthy
+        end
+
+        it_behaves_like 'locked build picker'
+
+        context 'when having runner tags' do
+          before do
+            runner.tag_list = ['bb', 'cc']
+            build.tag_list = ['bb']
+          end
+
+          it 'can handle it for matching tags' do
+            expect(runner.can_pick?(build)).to be_truthy
+          end
+        end
+      end
+
+      context 'serving a different project' do
+        before do
+          runner.runner_projects.destroy_all
+        end
+
+        it 'cannot handle it' do
+          expect(runner.can_pick?(build)).to be_falsey
+        end
+
+        it_behaves_like 'locked build picker'
+
+        context 'when having runner tags' do
+          before do
+            runner.tag_list = ['bb', 'cc']
+            build.tag_list = ['bb']
+          end
+
+          it 'cannot handle it for matching tags' do
+            expect(runner.can_pick?(build)).to be_falsey
+          end
+        end
+      end
+    end
+  end
+
+  describe '#status' do
     let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) }
 
     subject { runner.status }
 
     context 'never connected' do
-      before { runner.contacted_at = nil }
+      before do
+        runner.contacted_at = nil
+      end
 
       it { is_expected.to eq(:not_connected) }
     end
 
     context 'contacted 1s ago' do
-      before { runner.contacted_at = 1.second.ago }
+      before do
+        runner.contacted_at = 1.second.ago
+      end
 
       it { is_expected.to eq(:online) }
     end
 
     context 'contacted long time ago' do
-      before { runner.contacted_at = 1.year.ago }
+      before do
+        runner.contacted_at = 1.year.ago
+      end
 
       it { is_expected.to eq(:offline) }
     end
 
     context 'inactive' do
-      before { runner.active = false }
+      before do
+        runner.active = false
+      end
 
       it { is_expected.to eq(:paused) }
     end
   end
 
+  describe '.assignable_for' do
+    let(:runner) { create(:ci_runner) }
+    let(:project) { create(:project) }
+    let(:another_project) { create(:project) }
+
+    before do
+      project.runners << runner
+    end
+
+    context 'with shared runners' do
+      before do
+        runner.update(is_shared: true)
+      end
+
+      context 'does not give owned runner' do
+        subject { Ci::Runner.assignable_for(project) }
+
+        it { is_expected.to be_empty }
+      end
+
+      context 'does not give shared runner' do
+        subject { Ci::Runner.assignable_for(another_project) }
+
+        it { is_expected.to be_empty }
+      end
+    end
+
+    context 'with unlocked runner' do
+      context 'does not give owned runner' do
+        subject { Ci::Runner.assignable_for(project) }
+
+        it { is_expected.to be_empty }
+      end
+
+      context 'does give a specific runner' do
+        subject { Ci::Runner.assignable_for(another_project) }
+
+        it { is_expected.to contain_exactly(runner) }
+      end
+    end
+
+    context 'with locked runner' do
+      before do
+        runner.update(locked: true)
+      end
+
+      context 'does not give owned runner' do
+        subject { Ci::Runner.assignable_for(project) }
+
+        it { is_expected.to be_empty }
+      end
+
+      context 'does not give a locked runner' do
+        subject { Ci::Runner.assignable_for(another_project) }
+
+        it { is_expected.to be_empty }
+      end
+    end
+  end
+
   describe "belongs_to_one_project?" do
     it "returns false if there are two projects runner assigned to" do
       runner = FactoryGirl.create(:ci_runner)
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index beca8708c9d081fe84354240303fe8f55227c7de..ba02d5fe97727fa8114064bbf08ca4d24bc24869 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -207,4 +207,16 @@ eos
       expect(commit.participants).to include(note1.author, note2.author)
     end
   end
+
+  describe '#uri_type' do
+    it 'returns the URI type at the given path' do
+      expect(commit.uri_type('files/html')).to be(:tree)
+      expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
+      expect(commit.uri_type('files/js/application.js')).to be(:blob)
+    end
+
+    it "returns nil if the path doesn't exists" do
+      expect(commit.uri_type('this/path/doesnt/exist')).to be_nil
+    end
+  end
 end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 8fb605fff8a45990fc4f5d88ec3ad87de32848ec..96397d7c8a9cf10852ec7f1ac49b8772f337f775 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,8 +1,13 @@
 require 'spec_helper'
 
 describe CommitStatus, models: true do
-  let(:pipeline) { FactoryGirl.create :ci_pipeline }
-  let(:commit_status) { FactoryGirl.create :commit_status, pipeline: pipeline }
+  let(:project) { create(:project) }
+
+  let(:pipeline) do
+    create(:ci_pipeline, project: project, sha: project.commit.id)
+  end
+
+  let(:commit_status) { create(:commit_status, pipeline: pipeline) }
 
   it { is_expected.to belong_to(:pipeline) }
   it { is_expected.to belong_to(:user) }
@@ -13,7 +18,7 @@ describe CommitStatus, models: true do
 
   it { is_expected.to delegate_method(:sha).to(:pipeline) }
   it { is_expected.to delegate_method(:short_sha).to(:pipeline) }
-  
+
   it { is_expected.to respond_to :success? }
   it { is_expected.to respond_to :failed? }
   it { is_expected.to respond_to :running? }
@@ -116,7 +121,7 @@ describe CommitStatus, models: true do
       it { is_expected.to be > 0.0 }
     end
   end
-  
+
   describe :latest do
     subject { CommitStatus.latest.order(:id) }
 
@@ -198,4 +203,10 @@ describe CommitStatus, models: true do
       end
     end
   end
+
+  describe '#commit' do
+    it 'returns commit pipeline has been created for' do
+      expect(commit_status.commit).to eq project.commit
+    end
+  end
 end
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index 7e4ea0f2d661d945b4d718972a64e54500520883..a9f4ef9ee5efcbdb8f28d41da52627e2f031915e 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -37,6 +37,16 @@ describe Participable, models: true do
       expect(participants).to include(user3)
     end
 
+    it 'caches the raw list of participants' do
+      instance = model.new
+      user1 = build(:user)
+
+      expect(instance).to receive(:raw_participants).once
+
+      instance.participants(user1)
+      instance.participants(user1)
+    end
+
     it 'supports attributes returning another Participable' do
       other_model = Class.new { include Participable }
 
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 26fbedbef2f83f76a8e0c52dbbe7f721cda1d8e8..49cf3d8633ad9ea4464311b83818ee2beac5dc3e 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -26,7 +26,7 @@ describe Key, models: true do
     end
   end
 
-  context "validation of uniqueness" do
+  context "validation of uniqueness (based on fingerprint uniqueness)" do
     let(:user) { create(:user) }
 
     it "accepts the key once" do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 3ed3202ac6c5e52821c42a1090b72752e8ceff1d..e9134a3d28324badff2e80058bf16ccd37c87d19 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -134,18 +134,6 @@ describe Member, models: true do
     it { is_expected.to respond_to(:user_email) }
   end
 
-  describe 'Callbacks' do
-    describe 'after_destroy :post_decline_request, if: :request?' do
-      let(:member) { create(:project_member, requested_at: Time.now.utc) }
-
-      it 'calls #post_decline_request' do
-        expect(member).to receive(:post_decline_request)
-
-        member.destroy
-      end
-    end
-  end
-
   describe ".add_user" do
     let!(:user)    { create(:user) }
     let(:project) { create(:project) }
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index eeb74a462acb2b077c0f2d7dc438df81736a143e..18439cac2a4cb62a5d3397ab41a19c60ae5b1168 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -61,16 +61,6 @@ describe GroupMember, models: true do
       end
     end
 
-    describe '#post_decline_request' do
-      it 'calls NotificationService.decline_group_access_request' do
-        member = create(:group_member, user: build_stubbed(:user), requested_at: Time.now)
-
-        expect_any_instance_of(NotificationService).to receive(:decline_group_access_request)
-
-        member.__send__(:post_decline_request)
-      end
-    end
-
     describe '#real_source_type' do
       subject { create(:group_member).real_source_type }
 
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 1e466f9c62045ef243b5b175bb61dc6fbd4ec3eb..bbf65edb27c077386fc12d575e1e6000b27b16a2 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -152,15 +152,5 @@ describe ProjectMember, models: true do
         member.__send__(:after_accept_request)
       end
     end
-
-    describe '#post_decline_request' do
-      it 'calls NotificationService.decline_project_access_request' do
-        member = create(:project_member, user: build_stubbed(:user), requested_at: Time.now)
-
-        expect_any_instance_of(NotificationService).to receive(:decline_project_access_request)
-
-        member.__send__(:post_decline_request)
-      end
-    end
   end
 end
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index ac85f3409225d882399a6571ad2aaa80423814b8..47e9253a10cac9df5271fef581408546f4a14bb6 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -9,8 +9,8 @@ describe API::API, api: true  do
   let!(:project) { create(:project, creator_id: user.id) }
   let!(:developer) { create(:project_member, :developer, user: user, project: project) }
   let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) }
-  let(:pipeline) { create(:ci_pipeline, project: project)}
-  let(:build) { create(:ci_build, pipeline: pipeline) }
+  let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id) }
+  let!(:build) { create(:ci_build, pipeline: pipeline) }
 
   describe 'GET /projects/:id/builds ' do
     let(:query) { '' }
@@ -23,6 +23,11 @@ describe API::API, api: true  do
         expect(json_response).to be_an Array
       end
 
+      it 'returns correct values' do
+        expect(json_response).not_to be_empty
+        expect(json_response.first['commit']['id']).to eq project.commit.id
+      end
+
       context 'filter project with one scope element' do
         let(:query) { 'scope=pending' }
 
@@ -132,7 +137,7 @@ describe API::API, api: true  do
 
   describe 'GET /projects/:id/builds/:build_id/trace' do
     let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
-    
+
     before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) }
 
     context 'authorized user' do
diff --git a/spec/requests/api/gitignores_spec.rb b/spec/requests/api/gitignores_spec.rb
deleted file mode 100644
index aab2d8c81b997ba7b37f0e9911b7158cb7abb0f1..0000000000000000000000000000000000000000
--- a/spec/requests/api/gitignores_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'spec_helper'
-
-describe API::Gitignores, api: true  do
-  include ApiHelpers
-
-  describe 'Entity Gitignore' do
-    before { get api('/gitignores/Ruby') }
-
-    it { expect(json_response['name']).to eq('Ruby') }
-    it { expect(json_response['content']).to include('*.gem') }
-  end
-
-  describe 'Entity GitignoresList' do
-    before { get api('/gitignores') }
-
-    it { expect(json_response.first['name']).not_to be_nil }
-    it { expect(json_response.first['content']).to be_nil }
-  end
-
-  describe 'GET /gitignores' do
-    it 'returns a list of available license templates' do
-      get api('/gitignores')
-
-      expect(response.status).to eq(200)
-      expect(json_response).to be_an Array
-      expect(json_response.size).to be > 15
-    end
-  end
-end
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 73ae8ef631c14dfb3d8f25d4bbb980484d858d5f..b4c826522a5d09a45f3f6f945cbd83386b6e5929 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -187,14 +187,16 @@ describe API::Runners, api: true  do
           update_runner(shared_runner.id, admin, description: "#{description}_updated",
                                                  active: !active,
                                                  tag_list: ['ruby2.1', 'pgsql', 'mysql'],
-                                                 run_untagged: 'false')
+                                                 run_untagged: 'false',
+                                                 locked: 'true')
           shared_runner.reload
 
           expect(response.status).to eq(200)
           expect(shared_runner.description).to eq("#{description}_updated")
           expect(shared_runner.active).to eq(!active)
           expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
-          expect(shared_runner.run_untagged?).to be false
+          expect(shared_runner.run_untagged?).to be(false)
+          expect(shared_runner.locked?).to be(true)
         end
       end
 
@@ -360,11 +362,13 @@ describe API::Runners, api: true  do
 
   describe 'POST /projects/:id/runners' do
     context 'authorized user' do
-      it 'should enable specific runner' do
-        specific_runner2 = create(:ci_runner).tap do |runner|
+      let(:specific_runner2) do
+        create(:ci_runner).tap do |runner|
           create(:ci_runner_project, runner: runner, project: project2)
         end
+      end
 
+      it 'should enable specific runner' do
         expect do
           post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
         end.to change{ project.runners.count }.by(+1)
@@ -375,7 +379,17 @@ describe API::Runners, api: true  do
         expect do
           post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
         end.to change{ project.runners.count }.by(0)
-        expect(response.status).to eq(201)
+        expect(response.status).to eq(409)
+      end
+
+      it 'should not enable locked runner' do
+        specific_runner2.update(locked: true)
+
+        expect do
+          post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
+        end.to change{ project.runners.count }.by(0)
+
+        expect(response.status).to eq(403)
       end
 
       it 'should not enable shared runner' do
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a6d5ade3013abc9e86a7db42c3a93b9a7759f562
--- /dev/null
+++ b/spec/requests/api/templates_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe API::Templates, api: true  do
+  include ApiHelpers
+
+  describe 'the Template Entity' do
+    before { get api('/gitignores/Ruby') }
+
+    it { expect(json_response['name']).to eq('Ruby') }
+    it { expect(json_response['content']).to include('*.gem') }
+  end
+
+  describe 'the TemplateList Entity' do
+    before { get api('/gitignores') }
+
+    it { expect(json_response.first['name']).not_to be_nil }
+    it { expect(json_response.first['content']).to be_nil }
+  end
+
+  context 'requesting gitignores' do
+    describe 'GET /gitignores' do
+      it 'returns a list of available gitignore templates' do
+        get api('/gitignores')
+
+        expect(response.status).to eq(200)
+        expect(json_response).to be_an Array
+        expect(json_response.size).to be > 15
+      end
+    end
+  end
+
+  context 'requesting gitlab-ci-ymls' do
+    describe 'GET /gitlab_ci_ymls' do
+      it 'returns a list of available gitlab_ci_ymls' do
+        get api('/gitlab_ci_ymls')
+
+        expect(response.status).to eq(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first['name']).not_to be_nil
+      end
+    end
+  end
+
+  describe 'GET /gitlab_ci_ymls/Ruby' do
+    it 'adds a disclaimer on the top' do
+      get api('/gitlab_ci_ymls/Ruby')
+
+      expect(response.status).to eq(200)
+      expect(json_response['content']).to start_with("# This file is a template,")
+    end
+  end
+end
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2395445e7fddcb1372b5010dea71680d712f174b
--- /dev/null
+++ b/spec/services/members/destroy_service_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe Members::DestroyService, services: true do
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+  let!(:member) { create(:project_member, source: project) }
+
+  context 'when member is nil' do
+    before do
+      project.team << [user, :developer]
+    end
+
+    it 'does not destroy the member' do
+      expect { destroy_member(nil, user) }.to raise_error(Gitlab::Access::AccessDeniedError)
+    end
+  end
+
+  context 'when current user cannot destroy the given member' do
+    before do
+      project.team << [user, :developer]
+    end
+
+    it 'does not destroy the member' do
+      expect { destroy_member(member, user) }.to raise_error(Gitlab::Access::AccessDeniedError)
+    end
+  end
+
+  context 'when current user can destroy the given member' do
+    before do
+      project.team << [user, :master]
+    end
+
+    it 'destroys the member' do
+      destroy_member(member, user)
+
+      expect(member).to be_destroyed
+    end
+
+    context 'when the given member is a requester' do
+      before do
+        member.update_column(:requested_at, Time.now)
+      end
+
+      it 'calls Member#after_decline_request' do
+        expect_any_instance_of(NotificationService).to receive(:decline_access_request).with(member)
+
+        destroy_member(member, user)
+      end
+
+      context 'when current user is the member' do
+        it 'does not call Member#after_decline_request' do
+          expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member)
+
+          destroy_member(member, member.user)
+        end
+      end
+
+      context 'when current user is the member and ' do
+        it 'does not call Member#after_decline_request' do
+          expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member)
+
+          destroy_member(member, member.user)
+        end
+      end
+    end
+  end
+
+  def destroy_member(member, user)
+    Members::DestroyService.new(member, user).execute
+  end
+end
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index 1abd87d7d3348306388f7c4843dbbf2a64f9bacd..b5e1fdb8ded40ae24e14226c5bb107d6daa0e10a 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -9,7 +9,7 @@ describe MergeWorker do
 
     before do
       source_project.team << [author, :master]
-      source_project.repository.expire_branch_names
+      source_project.repository.expire_branches_cache
     end
 
     it 'clears cache of source repo after removing source branch' do
diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore
index a8368751267c19e45c935c0f3d17cb8322b62dd3..f6b286cea98b98f849f504cd912054b7b046c1f0 100644
--- a/vendor/gitignore/Android.gitignore
+++ b/vendor/gitignore/Android.gitignore
@@ -2,7 +2,7 @@
 *.apk
 *.ap_
 
-# Files for the Dalvik VM
+# Files for the ART/Dalvik VM
 *.dex
 
 # Java class files
@@ -34,6 +34,7 @@ captures/
 
 # Intellij
 *.iml
+.idea/workspace.xml
 
 # Keystore files
 *.jks
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index b8bd0267bdf1a5e02793430b53cbbebd6c257f6f..4581ef2eeefc7832704f4a78f3018455f7a06178 100644
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
@@ -15,6 +15,7 @@
 
 # Fortran module files
 *.mod
+*.smod
 
 # Compiled Static libraries
 *.lai
diff --git a/vendor/gitignore/CMake.gitignore b/vendor/gitignore/CMake.gitignore
index b558e9afa6da4591ea15d0b78a5fd9886eb8d877..0cc7e4b527553e3b57cd57050a58d7a560675c69 100644
--- a/vendor/gitignore/CMake.gitignore
+++ b/vendor/gitignore/CMake.gitignore
@@ -4,3 +4,4 @@ CMakeScripts
 Makefile
 cmake_install.cmake
 install_manifest.txt
+CTestTestfile.cmake
diff --git a/vendor/gitignore/D.gitignore b/vendor/gitignore/D.gitignore
index b4433f8a512390abfa1908a00f46a87e13c4e6f6..74b926fc90129d656d623f6b3210503d97c22c6c 100644
--- a/vendor/gitignore/D.gitignore
+++ b/vendor/gitignore/D.gitignore
@@ -18,3 +18,7 @@
 .dub
 docs.json
 __dummy.html
+docs/
+
+# Code coverage
+*.lst
diff --git a/vendor/gitignore/Global/Bazaar.gitignore b/vendor/gitignore/Global/Bazaar.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..3cbbcbd11ec7c478b54f830c22c75c40915f9f96
--- /dev/null
+++ b/vendor/gitignore/Global/Bazaar.gitignore
@@ -0,0 +1,2 @@
+.bzr/
+.bzrignore
diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/OSX.gitignore
index 660b31353e8082941ae527149c107e4c26ea1e2a..5972fe50f66e4c7b4b5d87afde97758eeeb7c64f 100644
--- a/vendor/gitignore/Global/OSX.gitignore
+++ b/vendor/gitignore/Global/OSX.gitignore
@@ -1,4 +1,4 @@
-.DS_Store
+*.DS_Store
 .AppleDouble
 .LSOverride
 
@@ -15,6 +15,7 @@ Icon
 .TemporaryItems
 .Trashes
 .VolumeIcon.icns
+.com.apple.timemachine.donotpresent
 
 # Directories potentially created on remote AFP share
 .AppleDB
diff --git a/vendor/gitignore/Global/README.md b/vendor/gitignore/Global/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..06b6649bd9a5b3b0b4b678f3bfb8339bb523f435
--- /dev/null
+++ b/vendor/gitignore/Global/README.md
@@ -0,0 +1,10 @@
+## Globally Useful gitignores
+
+This directory contains globally useful gitignores,
+e.g. OS-specific and editor specific.
+
+For more on global gitignores:
+<https://help.github.com/articles/ignoring-files/#create-a-global-gitignore>
+
+And a good blog post about 'em:
+<http://augustl.com/blog/2009/global_gitignores>
diff --git a/vendor/gitignore/Global/SublimeText.gitignore b/vendor/gitignore/Global/SublimeText.gitignore
index 1d4e613759162e4307104d039181ff6e2c3650f4..69c8c2b29ce59a0ee4b1a19934460006a21b59b8 100644
--- a/vendor/gitignore/Global/SublimeText.gitignore
+++ b/vendor/gitignore/Global/SublimeText.gitignore
@@ -12,3 +12,16 @@
 
 # sftp configuration file
 sftp-config.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore
index 096abdd90b3be1288ae47044098d3bd981635469..a4ee41ab62b01d5467e6b817aa1107606ba478d4 100644
--- a/vendor/gitignore/Haskell.gitignore
+++ b/vendor/gitignore/Haskell.gitignore
@@ -16,3 +16,4 @@ cabal.sandbox.config
 *.hp
 *.eventlog
 .stack-work/
+cabal.project.local
diff --git a/vendor/gitignore/Julia.gitignore b/vendor/gitignore/Julia.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..381e0b6d252ba94bf42985df2fc68f68b9fb747b
--- /dev/null
+++ b/vendor/gitignore/Julia.gitignore
@@ -0,0 +1,4 @@
+*.jl.cov
+*.jl.*.cov
+*.jl.mem
+deps/deps.jl
diff --git a/vendor/gitignore/LICENSE b/vendor/gitignore/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..b8a103ac9b138a427f4318dd762256bd511cd9ab
--- /dev/null
+++ b/vendor/gitignore/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 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.
diff --git a/vendor/gitignore/Laravel.gitignore b/vendor/gitignore/Laravel.gitignore
index c491fa2bc6fef89af327dcd12c1373fcb4125d9a..1cd717b6921f78825776b993f42a451c5fed5a83 100644
--- a/vendor/gitignore/Laravel.gitignore
+++ b/vendor/gitignore/Laravel.gitignore
@@ -7,7 +7,6 @@ app/storage/
 
 # Laravel 5 & Lumen specific
 bootstrap/cache/
-storage/
 .env.*.php
 .env.php
 .env
diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore
index 3020bc327a7ce6cbdbcda32fe56deaf58f82654a..86f21d8e0ff7635d06025bddcae1cee3d7cd378f 100644
--- a/vendor/gitignore/Objective-C.gitignore
+++ b/vendor/gitignore/Objective-C.gitignore
@@ -24,6 +24,8 @@ xcuserdata/
 ## Obj-C/Swift specific
 *.hmap
 *.ipa
+*.dSYM.zip
+*.dSYM
 
 # CocoaPods
 #
@@ -49,3 +51,10 @@ Carthage/Build
 
 fastlane/report.xml
 fastlane/screenshots
+
+#Code Injection
+#
+# After new code Injection tools there's a generated folder /iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/
diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore
index fa24b2efee8cce6c12437e021c50aeeb12313d24..c7659c24f386b264711c5c951b3957372e3cecf1 100644
--- a/vendor/gitignore/Qt.gitignore
+++ b/vendor/gitignore/Qt.gitignore
@@ -34,5 +34,5 @@ Makefile*
 *.qmlproject.user.*
 
 # QtCtreator CMake
-CMakeLists.txt.user
+CMakeLists.txt.user*
 
diff --git a/vendor/gitignore/README.md b/vendor/gitignore/README.md
deleted file mode 100644
index 43131e815cca2ecc85ee396e3a239057171ac2de..0000000000000000000000000000000000000000
--- a/vendor/gitignore/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# .gitignore templates
-
-This directory contains language-specific .gitignore templates that are used by GitLab.
-
-These files were automatically pulled from [this repository](https://github.com/github/gitignore).
-Please submit pull requests to that repository. There is no need to edit the files in this directory.
-
-## Bulk Update
-
-To update this directory with the latest changes in the repository, run:
-
-```sh
-bundle exec rake gitlab:update_gitignore
-```
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index 2121e0a8038ff598480289af8d9bedd0a8b290fb..d8c256c1925e11e94c5b0a56919ec844517a7329 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -16,6 +16,10 @@ pickle-email-*.html
 config/initializers/secret_token.rb
 config/secrets.yml
 
+# dotenv
+# TODO Comment out this rule if environment variables can be committed
+.env
+
 ## Environment normalization:
 /.bundle
 /vendor/bundle
diff --git a/vendor/gitignore/Swift.gitignore b/vendor/gitignore/Swift.gitignore
index 8a29fa52af49ce255189b407970bfb8284eb29c1..2c22487b5e348527f441b3542e6ee0a726fff075 100644
--- a/vendor/gitignore/Swift.gitignore
+++ b/vendor/gitignore/Swift.gitignore
@@ -24,6 +24,8 @@ xcuserdata/
 ## Obj-C/Swift specific
 *.hmap
 *.ipa
+*.dSYM.zip
+*.dSYM
 
 ## Playgrounds
 timeline.xctimeline
diff --git a/vendor/gitignore/UnrealEngine.gitignore b/vendor/gitignore/UnrealEngine.gitignore
index 75b1186b0afd4292bf86accdcc1685b62f1a673d..be0e4913c3a7194e9aa462dcaf9d0774c5c3216d 100644
--- a/vendor/gitignore/UnrealEngine.gitignore
+++ b/vendor/gitignore/UnrealEngine.gitignore
@@ -37,6 +37,7 @@
 *.suo
 *.opensdf
 *.sdf
+*.VC.db
 *.VC.opendb
 
 # Precompiled Assets
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index f1e3d20e056857d2f29dd50d10cbdd1e18ecee4c..67acbf42f5ee14c6ed7089ef2aa6559f57c860cd 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -42,6 +42,7 @@ dlldata.c
 
 # DNX
 project.lock.json
+project.fragment.lock.json
 artifacts/
 
 *_i.c
diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..396d3f1b042f1c15ea4663c9c43d08f85d0ff9ff
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
@@ -0,0 +1,7 @@
+# Official docker image.
+image: docker:latest
+
+build:
+  stage: build
+  script:
+    - docker build -t test .
diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0b329aaf1c419971df5787a2502149718fc4e471
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
@@ -0,0 +1,18 @@
+# This template uses the non default language docker image
+# The image already has Hex installed. You might want to consider to use `elixir:latest`
+image: trenpixster/elixir:latest
+
+# Pic zero or more services to be used on all builds.
+# Only needed when using a docker container to run your tests in.
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+services:
+  - mysql:latest
+  - redis:latest
+  - postgres:latest
+
+before_script:
+  - mix deps.get
+
+mix:
+  script:
+  - mix test
diff --git a/vendor/gitlab-ci-yml/LICENSE b/vendor/gitlab-ci-yml/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..80f7b87b6c04506dee904f6840c857989d9b2da1
--- /dev/null
+++ b/vendor/gitlab-ci-yml/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 GitLab.org
+
+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.
diff --git a/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e5bce3503f326a7563b9bdb48a55b198280bc9e6
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml
@@ -0,0 +1,27 @@
+# Official framework image. Look for the different tagged releases at:
+# https://hub.docker.com/r/library/node/tags/
+image: node:latest
+
+# Pick zero or more services to be used on all builds.
+# Only needed when using a docker container to run your tests in.
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+services:
+  - mysql:latest
+  - redis:latest
+  - postgres:latest
+
+# This folder is cached between builds
+# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
+cache:
+  paths:
+  - node_modules/
+
+test_async:
+  script:
+   - npm install
+   - node ./specs/start.js ./specs/async.spec.js
+
+test_db:
+  script:
+   - npm install
+   - node ./specs/start.js ./specs/db-postgres.spec.js
diff --git a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7fcc0b436b577c969542fdaa64ea8603b76363cf
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml
@@ -0,0 +1,16 @@
+# Full project: https://gitlab.com/pages/brunch
+image: node:4.2.2
+
+pages:
+  cache:
+    paths:
+    - node_modules/
+
+  script:
+  - npm install -g brunch
+  - brunch build --production
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..791afdd23f10201a4f5d67b79c54e29380ac53fa
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml
@@ -0,0 +1,13 @@
+# Full project: https://gitlab.com/pages/doxygen
+image: alpine
+
+pages:
+  script:
+  - apk update && apk add doxygen
+  - doxygen doxygen/Doxyfile
+  - mv doxygen/documentation/html/ public/
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dd3ef14966899c1e44b6140738746747651be214
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml
@@ -0,0 +1,16 @@
+# Full project: https://gitlab.com/pages/harp
+image: node:4.2.2
+
+pages:
+  cache:
+    paths:
+    - node_modules
+
+  script:
+  - npm install -g harp
+  - harp compile ./ public
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b468d79bcad0ad3d04d70bc96d4f24a06c6cdeff
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml
@@ -0,0 +1,25 @@
+# Full project: https://gitlab.com/pages/hexo
+image: python:2.7
+
+cache:
+  paths:
+  - vendor/
+
+test:
+  stage: test
+  script:
+    - pip install hyde
+    - hyde gen
+  except:
+    - master
+
+pages:
+  stage: deploy
+  script:
+    - pip install hyde
+    - hyde gen -d public
+  artifacts:
+    paths:
+    - public
+  only:
+    - master
diff --git a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..249a168aa33dbeafb45e59deb0b6aa691177c201
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml
@@ -0,0 +1,12 @@
+# Full project: https://gitlab.com/pages/plain-html
+pages:
+  stage: deploy
+  script:
+  - mkdir .public
+  - cp -r * .public
+  - mv .public public
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..45df69752594922806a27b038cc50042ec71161b
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml
@@ -0,0 +1,11 @@
+# Full project: https://gitlab.com/pages/hugo
+image: publysher/hugo
+
+pages:
+  script:
+  - hugo
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f5b40f2b9f1fb322f6ccc1cead7b1aa32a1824b6
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml
@@ -0,0 +1,25 @@
+# Full project: https://gitlab.com/pages/hyde
+image: python:2.7
+
+cache:
+  paths:
+  - vendor/
+
+test:
+  stage: test
+  script:
+    - pip install hyde
+    - hyde gen
+  except:
+    - master
+
+pages:
+  stage: deploy
+  script:
+    - pip install hyde
+    - hyde gen -d public
+  artifacts:
+    paths:
+    - public
+  only:
+    - master
diff --git a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..36918fc005a15b47de1cbd71e3748bd5babe392e
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml
@@ -0,0 +1,24 @@
+# Full project: https://gitlab.com/pages/jekyll
+image: ruby:2.3
+
+test:
+  stage: test
+  script:
+  - gem install jekyll
+  - jekyll build -d test
+  artifacts:
+    paths:
+    - test
+  except:
+  - master
+
+pages:
+  stage: deploy
+  script:
+  - gem install jekyll
+  - jekyll build -d public
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c5c44a5d86cf90620a21f4a9daa4e12f4d1d755f
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml
@@ -0,0 +1,12 @@
+# Full project: https://gitlab.com/pages/hyde
+image: python:2.7
+
+pages:
+  script:
+  - pip install lektor
+  - lektor build --output-path public
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..50e8b7ccd46ad915fdb279aff09450c10c682ac0
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml
@@ -0,0 +1,17 @@
+# Full project: https://gitlab.com/pages/metalsmith
+image: node:4.2.2
+
+pages:
+  cache:
+    paths:
+    - node_modules/
+
+  script:
+  - npm install -g metalsmith
+  - npm install
+  - make build
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9f4cc0574d649737ef51081814ab47b5bb5e151a
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml
@@ -0,0 +1,27 @@
+# Full project: https://gitlab.com/pages/middleman
+image: ruby:2.3
+
+cache:
+  paths:
+  - vendor
+
+test:
+  script:
+  - apt-get update -yqqq
+  - apt-get install -y nodejs
+  - bundle install --path vendor
+  - bundle exec middleman build
+  except:
+    - master
+
+pages:
+  script:
+  - apt-get update -yqqq
+  - apt-get install -y nodejs
+  - bundle install --path vendor
+  - bundle exec middleman build
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b469b316ba558c4eb28dc173f4b551425c04daf1
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml
@@ -0,0 +1,12 @@
+# Full project: https://gitlab.com/pages/nanoc
+image: ruby:2.3
+
+pages:
+  script:
+  - bundle install -j4
+  - nanoc
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4762ec9acfd7bfcc0a4c307d7fdac85a11cf8794
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml
@@ -0,0 +1,15 @@
+# Full project: https://gitlab.com/pages/octopress
+image: ruby:2.3
+
+pages:
+  script:
+  - apt-get update -qq && apt-get install -qq nodejs
+  - bundle install -j4
+  - bundle exec rake generate
+  - mv public .public
+  - mv .public/octopress public
+  artifacts:
+    paths:
+    - public
+  only:
+  - master
diff --git a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c5f3154f587d84d5ca38df93d880b38912ef5c4e
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml
@@ -0,0 +1,10 @@
+# Full project: https://gitlab.com/pages/pelican
+image: python:2.7-alpine
+
+pages:
+  script:
+  - pip install -r requirements.txt
+  - pelican -s publishconf.py
+  artifacts:
+    paths:
+    - public/
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..78f3e39949fb4f6381601cd11585ef4be01e874a
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -0,0 +1,30 @@
+# Official language image. Look for the different tagged releases at:
+# https://hub.docker.com/r/library/ruby/tags/
+image: "ruby:2.3"
+
+# Pick zero or more services to be used on all builds.
+# Only needed when using a docker container to run your tests in.
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+services:
+  - mysql:latest
+  - redis:latest
+  - postgres:latest
+
+# This is a basic example for a gem or script which doesn't use
+# services such as redis or postgres
+before_script:
+  - gem install bundler         # Bundler is not installed with the image
+  - bundle install -j $(nproc)  # Install dependencies
+
+rubocop:
+  script:
+  - rubocop
+
+rspec:
+  script:
+  - rspec spec
+
+rails:
+  script:
+  - rake db:migrate
+  - rspec spec