diff --git a/.flayignore b/.flayignore
index 9c9875d4f9ef387c191ce57d128a7c2af1abd8b3..f120de527bd831e45ab712887685d117249d4713 100644
--- a/.flayignore
+++ b/.flayignore
@@ -1 +1,2 @@
 *.erb
+lib/gitlab/sanitizers/svg/whitelist.rb
diff --git a/.gitignore b/.gitignore
index 1bf9a47aef6de8a156e6ffb0e34c87de23efc445..9166512606d3cdcdfbaead1ea48ee84586b6990f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,3 +48,4 @@
 /vendor/bundle/*
 /builds/*
 /shared/*
+/.gitlab_workhorse_secret
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d0805125a426a16c05193010912972a33b8fcd51..b167fc749965a57a2a851b16d2ea5d013278f637 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -82,7 +82,7 @@ update-knapsack:
     - export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
     - export KNAPSACK_GENERATE_REPORT=true
     - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
-    - knapsack rspec
+    - knapsack rspec "--color --format documentation"
   artifacts:
     expire_in: 31d
     paths:
@@ -206,10 +206,15 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
     - bundle exec $CI_BUILD_NAME
 
 rubocop: *exec
+rake haml_lint: *exec
 rake scss_lint: *exec
 rake brakeman: *exec
-rake flog: *exec
-rake flay: *exec
+rake flog:
+  <<: *exec
+  allow_failure: yes
+rake flay:
+  <<: *exec
+  allow_failure: yes
 license_finder: *exec
 rake downtime_check: *exec
 
diff --git a/.haml-lint.yml b/.haml-lint.yml
new file mode 100644
index 0000000000000000000000000000000000000000..da9a43d9c6dcf23575c20b89e3bfb569d6530749
--- /dev/null
+++ b/.haml-lint.yml
@@ -0,0 +1,103 @@
+# Whether to ignore frontmatter at the beginning of HAML documents for
+# frameworks such as Jekyll/Middleman
+skip_frontmatter: false
+exclude:
+  - 'vendor/**/*'
+  - 'spec/**/*'
+
+linters:
+  AltText:
+    enabled: false
+
+  ClassAttributeWithStaticValue:
+    enabled: false
+
+  ClassesBeforeIds:
+    enabled: false
+
+  ConsecutiveComments:
+    enabled: false
+
+  ConsecutiveSilentScripts:
+    enabled: false
+    max_consecutive: 2
+
+  EmptyObjectReference:
+    enabled: true
+
+  EmptyScript:
+    enabled: true
+
+  FinalNewline:
+    enabled: false
+    present: true
+
+  HtmlAttributes:
+    enabled: false
+
+  ImplicitDiv:
+    enabled: false
+
+  LeadingCommentSpace:
+    enabled: false
+
+  LineLength:
+    enabled: false
+    max: 80
+
+  MultilinePipe:
+    enabled: false
+
+  MultilineScript:
+    enabled: true
+
+  ObjectReferenceAttributes:
+    enabled: true
+
+  RuboCop:
+    enabled: false
+    # These cops are incredibly noisy when it comes to HAML templates, so we
+    # ignore them.
+    ignored_cops:
+      - Lint/BlockAlignment
+      - Lint/EndAlignment
+      - Lint/Void
+      - Metrics/LineLength
+      - Style/AlignParameters
+      - Style/BlockNesting
+      - Style/ElseAlignment
+      - Style/FileName
+      - Style/FinalNewline
+      - Style/FrozenStringLiteralComment
+      - Style/IfUnlessModifier
+      - Style/IndentationWidth
+      - Style/Next
+      - Style/TrailingBlankLines
+      - Style/TrailingWhitespace
+      - Style/WhileUntilModifier
+
+  RubyComments:
+    enabled: false
+
+  SpaceBeforeScript:
+    enabled: false
+
+  SpaceInsideHashAttributes:
+    enabled: false
+    style: space
+
+  Indentation:
+    enabled: true
+    character: space # or tab
+
+  TagName:
+    enabled: true
+
+  TrailingWhitespace:
+    enabled: false
+
+  UnnecessaryInterpolation:
+    enabled: false
+
+  UnnecessaryStringOutput:
+    enabled: false
diff --git a/.rubocop.yml b/.rubocop.yml
index 5bd31ccf32915feed8f1e6628f0f7ac5ae4e1142..b054675d6775b745e70b60ac466b24e764ad30c1 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -767,26 +767,33 @@ Rails/ScopeArgs:
 RSpec/AnyInstance:
   Enabled: false
 
-# Check that the first argument to the top level describe is the tested class or
-# module.
+# Check for expectations where `be(...)` can replace `eql(...)`.
+RSpec/BeEql:
+  Enabled: false
+
+# Check that the first argument to the top level describe is a constant.
 RSpec/DescribeClass:
   Enabled: false
 
-# Use `described_class` for tested class / module.
+# Checks that tests use `described_class`.
+RSpec/DescribedClass:
+  Enabled: false
+
+# Checks that the second argument to `describe` specifies a method.
 RSpec/DescribeMethod:
   Enabled: false
 
-# Checks that the second argument to top level describe is the tested method
-# name.
-RSpec/DescribedClass:
+# Checks if an example group does not include any tests.
+RSpec/EmptyExampleGroup:
   Enabled: false
+  CustomIncludeMethods: []
 
-# Checks for long example.
+# Checks for long examples.
 RSpec/ExampleLength:
   Enabled: false
   Max: 5
 
-# Do not use should when describing your tests.
+# Checks that example descriptions do not start with "should".
 RSpec/ExampleWording:
   Enabled: false
   CustomTransform:
@@ -795,6 +802,10 @@ RSpec/ExampleWording:
     not: does not
   IgnoredWords: []
 
+# Checks for `expect(...)` calls containing literal values.
+RSpec/ExpectActual:
+  Enabled: false
+
 # Checks the file and folder naming of the spec file.
 RSpec/FilePath:
   Enabled: false
@@ -806,19 +817,65 @@ RSpec/FilePath:
 RSpec/Focus:
   Enabled: true
 
+# Checks the arguments passed to `before`, `around`, and `after`.
+RSpec/HookArgument:
+  Enabled: false
+  EnforcedStyle: implicit
+
+# Check that a consistent implict expectation style is used.
+# TODO (rspeicher): Available in rubocop-rspec 1.8.0
+# RSpec/ImplicitExpect:
+#   Enabled: true
+#   EnforcedStyle: is_expected
+
 # Checks for the usage of instance variables.
 RSpec/InstanceVariable:
   Enabled: false
 
-# Checks for multiple top-level describes.
+# Checks for `subject` definitions that come after `let` definitions.
+RSpec/LeadingSubject:
+  Enabled: false
+
+# Checks unreferenced `let!` calls being used for test setup.
+RSpec/LetSetup:
+  Enabled: false
+
+# Check that chains of messages are not being stubbed.
+RSpec/MessageChain:
+  Enabled: false
+
+# Checks for consistent message expectation style.
+RSpec/MessageExpectation:
+  Enabled: false
+  EnforcedStyle: allow
+
+# Checks for multiple top level describes.
 RSpec/MultipleDescribes:
   Enabled: false
 
-# Enforces the usage of the same method on all negative message expectations.
+# Checks if examples contain too many `expect` calls.
+RSpec/MultipleExpectations:
+  Enabled: false
+  Max: 1
+
+# Checks for explicitly referenced test subjects.
+RSpec/NamedSubject:
+  Enabled: false
+
+# Checks for nested example groups.
+RSpec/NestedGroups:
+  Enabled: false
+  MaxNesting: 2
+
+# Checks for consistent method usage for negating expectations.
 RSpec/NotToNot:
   EnforcedStyle: not_to
   Enabled: true
 
+# Checks for stubbed test subjects.
+RSpec/SubjectStub:
+  Enabled: false
+
 # Prefer using verifying doubles over normal doubles.
 RSpec/VerifiedDoubles:
   Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 20daf1619a7afa4f63eba5a33218eb23defe583f..87520c67dd54bfc3df9e1f3f431a554c1cd8daaf 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,21 +1,21 @@
 # This configuration was generated by
 # `rubocop --auto-gen-config --exclude-limit 0`
-# on 2016-07-13 12:36:08 -0600 using RuboCop version 0.41.2.
+# on 2016-09-14 15:44:53 -0400 using RuboCop version 0.42.0.
 # The point is for the user to remove these configuration records
 # one by one as the offenses are removed from the code base.
 # Note that changes in the inspected code, or installation of new
 # versions of RuboCop, may require this file to be generated again.
 
-# Offense count: 154
+# Offense count: 158
 Lint/AmbiguousRegexpLiteral:
   Enabled: false
 
-# Offense count: 43
+# Offense count: 41
 # Configuration parameters: AllowSafeAssignment.
 Lint/AssignmentInCondition:
   Enabled: false
 
-# Offense count: 14
+# Offense count: 16
 Lint/HandleExceptions:
   Enabled: false
 
@@ -23,28 +23,28 @@ Lint/HandleExceptions:
 Lint/Loop:
   Enabled: false
 
-# Offense count: 15
+# Offense count: 16
 Lint/ShadowingOuterLocalVariable:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 6
 # Cop supports --auto-correct.
 Lint/StringConversionInInterpolation:
   Enabled: false
 
-# Offense count: 44
+# Offense count: 49
 # Cop supports --auto-correct.
 # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
 Lint/UnusedBlockArgument:
   Enabled: false
 
-# Offense count: 129
+# Offense count: 144
 # Cop supports --auto-correct.
 # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
 Lint/UnusedMethodArgument:
   Enabled: false
 
-# Offense count: 12
+# Offense count: 9
 # Cop supports --auto-correct.
 Performance/PushSplat:
   Enabled: false
@@ -59,51 +59,51 @@ Performance/RedundantBlockCall:
 Performance/RedundantMatch:
   Enabled: false
 
-# Offense count: 24
+# Offense count: 27
 # Cop supports --auto-correct.
 # Configuration parameters: MaxKeyValuePairs.
 Performance/RedundantMerge:
   Enabled: false
 
-# Offense count: 60
+# Offense count: 61
 Rails/OutputSafety:
   Enabled: false
 
-# Offense count: 128
+# Offense count: 129
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: strict, flexible
 Rails/TimeZone:
   Enabled: false
 
-# Offense count: 12
+# Offense count: 15
 # Cop supports --auto-correct.
 # Configuration parameters: Include.
 # Include: app/models/**/*.rb
 Rails/Validation:
   Enabled: false
 
-# Offense count: 217
+# Offense count: 273
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
 # SupportedStyles: with_first_parameter, with_fixed_indentation
 Style/AlignParameters:
   Enabled: false
 
-# Offense count: 32
+# Offense count: 30
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: always, conditionals
 Style/AndOr:
   Enabled: false
 
-# Offense count: 47
+# Offense count: 50
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: percent_q, bare_percent
 Style/BarePercentLiterals:
   Enabled: false
 
-# Offense count: 258
+# Offense count: 289
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: braces, no_braces, context_dependent
@@ -126,14 +126,14 @@ Style/ColonMethodCall:
 Style/CommentAnnotation:
   Enabled: false
 
-# Offense count: 34
+# Offense count: 33
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly.
 # SupportedStyles: assign_to_condition, assign_inside_condition
 Style/ConditionalAssignment:
   Enabled: false
 
-# Offense count: 789
+# Offense count: 881
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: leading, trailing
@@ -144,11 +144,12 @@ Style/DotPosition:
 Style/DoubleNegation:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 4
+# Cop supports --auto-correct.
 Style/EachWithObject:
   Enabled: false
 
-# Offense count: 30
+# Offense count: 25
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: empty, nil, both
@@ -160,7 +161,7 @@ Style/EmptyElse:
 Style/EmptyLiteral:
   Enabled: false
 
-# Offense count: 123
+# Offense count: 135
 # Cop supports --auto-correct.
 # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
 Style/ExtraSpacing:
@@ -172,16 +173,16 @@ Style/ExtraSpacing:
 Style/FormatString:
   Enabled: false
 
-# Offense count: 48
+# Offense count: 51
 # Configuration parameters: MinBodyLength.
 Style/GuardClause:
   Enabled: false
 
-# Offense count: 11
+# Offense count: 9
 Style/IfInsideElse:
   Enabled: false
 
-# Offense count: 177
+# Offense count: 174
 # Cop supports --auto-correct.
 # Configuration parameters: MaxLineLength.
 Style/IfUnlessModifier:
@@ -194,7 +195,7 @@ Style/IfUnlessModifier:
 Style/IndentArray:
   Enabled: false
 
-# Offense count: 89
+# Offense count: 97
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
 # SupportedStyles: special_inside_parentheses, consistent, align_braces
@@ -208,7 +209,7 @@ Style/IndentHash:
 Style/Lambda:
   Enabled: false
 
-# Offense count: 6
+# Offense count: 5
 # Cop supports --auto-correct.
 Style/LineEndConcatenation:
   Enabled: false
@@ -218,17 +219,21 @@ Style/LineEndConcatenation:
 Style/MethodCallParentheses:
   Enabled: false
 
-# Offense count: 62
+# Offense count: 8
+Style/MethodMissing:
+  Enabled: false
+
+# Offense count: 85
 # Cop supports --auto-correct.
 Style/MutableConstant:
   Enabled: false
 
-# Offense count: 10
+# Offense count: 8
 # Cop supports --auto-correct.
 Style/NestedParenthesizedCalls:
   Enabled: false
 
-# Offense count: 12
+# Offense count: 13
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
 # SupportedStyles: skip_modifier_ifs, always
@@ -242,12 +247,19 @@ Style/Next:
 Style/NumericLiteralPrefix:
   Enabled: false
 
+# Offense count: 64
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: predicate, comparison
+Style/NumericPredicate:
+  Enabled: false
+
 # Offense count: 29
 # Cop supports --auto-correct.
 Style/ParallelAssignment:
   Enabled: false
 
-# Offense count: 208
+# Offense count: 264
 # Cop supports --auto-correct.
 # Configuration parameters: PreferredDelimiters.
 Style/PercentLiteralDelimiters:
@@ -265,7 +277,7 @@ Style/PercentQLiterals:
 Style/PerlBackrefs:
   Enabled: false
 
-# Offense count: 32
+# Offense count: 35
 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
 # NamePrefix: is_, has_, have_
 # NamePrefixBlacklist: is_, has_, have_
@@ -273,7 +285,7 @@ Style/PerlBackrefs:
 Style/PredicateName:
   Enabled: false
 
-# Offense count: 28
+# Offense count: 27
 # Cop supports --auto-correct.
 Style/PreferredHashMethods:
   Enabled: false
@@ -283,14 +295,14 @@ Style/PreferredHashMethods:
 Style/Proc:
   Enabled: false
 
-# Offense count: 20
+# Offense count: 22
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: compact, exploded
 Style/RaiseArgs:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 4
 # Cop supports --auto-correct.
 Style/RedundantBegin:
   Enabled: false
@@ -300,29 +312,29 @@ Style/RedundantBegin:
 Style/RedundantException:
   Enabled: false
 
-# Offense count: 23
+# Offense count: 24
 # Cop supports --auto-correct.
 Style/RedundantFreeze:
   Enabled: false
 
-# Offense count: 377
+# Offense count: 408
 # Cop supports --auto-correct.
 Style/RedundantSelf:
   Enabled: false
 
-# Offense count: 94
+# Offense count: 93
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
 # SupportedStyles: slashes, percent_r, mixed
 Style/RegexpLiteral:
   Enabled: false
 
-# Offense count: 17
+# Offense count: 18
 # Cop supports --auto-correct.
 Style/RescueModifier:
   Enabled: false
 
-# Offense count: 2
+# Offense count: 5
 # Cop supports --auto-correct.
 Style/SelfAssignment:
   Enabled: false
@@ -339,42 +351,42 @@ Style/SingleLineBlockParams:
 Style/SingleLineMethods:
   Enabled: false
 
-# Offense count: 119
+# Offense count: 124
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: space, no_space
 Style/SpaceBeforeBlockBraces:
   Enabled: false
 
-# Offense count: 11
+# Offense count: 10
 # Cop supports --auto-correct.
 # Configuration parameters: AllowForAlignment.
 Style/SpaceBeforeFirstArg:
   Enabled: false
 
-# Offense count: 130
+# Offense count: 141
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
 # SupportedStyles: space, no_space
 Style/SpaceInsideBlockBraces:
   Enabled: false
 
-# Offense count: 98
+# Offense count: 96
 # Cop supports --auto-correct.
 Style/SpaceInsideBrackets:
   Enabled: false
 
-# Offense count: 60
+# Offense count: 62
 # Cop supports --auto-correct.
 Style/SpaceInsideParens:
   Enabled: false
 
-# Offense count: 5
+# Offense count: 7
 # Cop supports --auto-correct.
 Style/SpaceInsidePercentLiteralDelimiters:
   Enabled: false
 
-# Offense count: 36
+# Offense count: 40
 # Cop supports --auto-correct.
 # Configuration parameters: SupportedStyles.
 # SupportedStyles: use_perl_names, use_english_names
@@ -388,21 +400,28 @@ Style/SpecialGlobalVars:
 Style/StringLiteralsInInterpolation:
   Enabled: false
 
-# Offense count: 24
+# Offense count: 32
 # Cop supports --auto-correct.
 # Configuration parameters: IgnoredMethods.
 # IgnoredMethods: respond_to, define_method
 Style/SymbolProc:
   Enabled: false
 
-# Offense count: 23
+# Offense count: 5
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment.
+# SupportedStyles: require_parentheses, require_no_parentheses
+Style/TernaryParentheses:
+  Enabled: false
+
+# Offense count: 24
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
 # SupportedStyles: comma, consistent_comma, no_comma
 Style/TrailingCommaInArguments:
   Enabled: false
 
-# Offense count: 113
+# Offense count: 102
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
 # SupportedStyles: comma, consistent_comma, no_comma
@@ -415,7 +434,7 @@ Style/TrailingCommaInLiteral:
 Style/TrailingUnderscoreVariable:
   Enabled: false
 
-# Offense count: 90
+# Offense count: 76
 # Cop supports --auto-correct.
 Style/TrailingWhitespace:
   Enabled: false
@@ -427,12 +446,12 @@ Style/TrailingWhitespace:
 Style/TrivialAccessors:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 2
 # Cop supports --auto-correct.
 Style/UnlessElse:
   Enabled: false
 
-# Offense count: 13
+# Offense count: 14
 # Cop supports --auto-correct.
 Style/UnneededInterpolation:
   Enabled: false
diff --git a/CHANGELOG b/CHANGELOG
index 2af2056979d6fe3909903d2cddb3e81c4ec61fd2..7a19af6b7655d5501a2058de3805036202a8a1a7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,33 +1,73 @@
 Please view this file on the master branch, on stable branches it's out of date.
 
 v 8.12.0 (unreleased)
+  - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
+  - Only check :can_resolve permission if the note is resolvable
+  - Bump fog-aws to v0.11.0 to support ap-south-1 region
   - Add ability to fork to a specific namespace using API. (ritave)
+  - Allow to set request_access_enabled for groups and projects
   - Cleanup misalignments in Issue list view !6206
+  - Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist
+  - Prune events older than 12 months. (ritave)
   - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell)
+  - Fix issues/merge-request templates dropdown for forked projects
   - Filter tags by name !6121
+  - Update gitlab shell secret file also when it is empty. !3774 (glensc)
+  - Give project selection dropdowns responsive width, make non-wrapping.
   - Make push events have equal vertical spacing.
+  - API: Ensure invitees are not returned in Members API.
   - Add two-factor recovery endpoint to internal API !5510
+  - Pass the "Remember me" value to the U2F authentication form
+  - Only update projects.last_activity_at once per hour when creating a new event
   - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps)
+  - Move pushes_since_gc from the database to Redis
   - Add font color contrast to external label in admin area (ClemMakesApps)
   - Change logo animation to CSS (ClemMakesApps)
   - Instructions for enabling Git packfile bitmaps !6104
+  - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint
+  - Fix long comments in diffs messing with table width
   - Fix pagination on user snippets page
+  - Run CI builds with the permissions of users !5735
+  - Fix sorting of issues in API
+  - Fix download artifacts button links !6407
+  - Sort project variables by key. !6275 (Diego Souza)
+  - Ensure specs on sorting of issues in API are deterministic on MySQL
+  - Added ability to use predefined CI variables for environment name
+  - Added ability to specify URL in environment configuration in gitlab-ci.yml
+  - Escape search term before passing it to Regexp.new !6241 (winniehell)
+  - Fix pinned sidebar behavior in smaller viewports !6169
+  - Fix file permissions change when updating a file on the Gitlab UI !5979
   - Change merge_error column from string to text type
   - Reduce contributions calendar data payload (ClemMakesApps)
+  - Replace contributions calendar timezone payload with dates (ClemMakesApps)
   - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
+  - Enable pipeline events by default !6278
+  - Move parsing of sidekiq ps into helper !6245 (pascalbetz)
+  - Added go to issue boards keyboard shortcut
   - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel)
   - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
+  - Fix blame table layout width
   - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps)
+  - Request only the LDAP attributes we need !6187
   - Center build stage columns in pipeline overview (ClemMakesApps)
+  - Fix bug with tooltip not hiding on discussion toggle button
   - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps)
+  - Fix bug stopping issue description being scrollable after selecting issue template
   - Remove suggested colors hover underline (ClemMakesApps)
   - Shorten task status phrase (ClemMakesApps)
+  - Fix project visibility level fields on settings
   - Add hover color to emoji icon (ClemMakesApps)
+  - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files
+  - Add textarea autoresize after comment (ClemMakesApps)
+  - Do not write SSH public key 'comments' to authorized_keys !6381
+  - Refresh todos count cache when an Issue/MR is deleted
   - Fix branches page dropdown sort alignment (ClemMakesApps)
+  - Hides merge request button on branches page is user doesn't have permissions
   - Add white background for no readme container (ClemMakesApps)
   - API: Expose issue confidentiality flag. (Robert Schilling)
   - Fix markdown anchor icon interaction (ClemMakesApps)
   - Test migration paths from 8.5 until current release !4874
+  - Replace animateEmoji timeout with eventListener (ClemMakesApps)
   - Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
   - Add `wiki_page_events` to project hook APIs (Ben Boeckel)
   - Remove Gitorious import
@@ -36,9 +76,14 @@ v 8.12.0 (unreleased)
   - Add Sentry logging to API calls
   - Add BroadcastMessage API
   - Use 'git update-ref' for safer web commits !6130
+  - Sort pipelines requested through the API
   - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
+  - Fix issue boards loading on large screens
+  - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084
+  - Show queued time when showing a pipeline !6084
   - Remove unused mixins (ClemMakesApps)
   - Add search to all issue board lists
+  - Scroll active tab into view on mobile
   - Fix groups sort dropdown alignment (ClemMakesApps)
   - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
   - Use JavaScript tooltips for mentions !5301 (winniehell)
@@ -49,13 +94,17 @@ v 8.12.0 (unreleased)
   - Add last commit time to repo view (ClemMakesApps)
   - Fix accessibility and visibility of project list dropdown button !6140
   - Fix missing flash messages on service edit page (airatshigapov)
-  - Added project specific enable/disable setting for LFS !5997
+  - Added project-specific enable/disable setting for LFS !5997
+  - Added group-specific enable/disable setting for LFS !6164
   - Don't expose a user's token in the `/api/v3/user` API (!6047)
   - Remove redundant js-timeago-pending from user activity log (ClemMakesApps)
   - Ability to manage project issues, snippets, wiki, merge requests and builds access level
   - Remove inconsistent font weight for sidebar's labels (ClemMakesApps)
   - Align add button on repository view (ClemMakesApps)
+  - Fix contributions calendar month label truncation (ClemMakesApps)
+  - Import release note descriptions from GitHub (EspadaV8)
   - Added tests for diff notes
+  - Add pipeline events to Slack integration !5525
   - Add a button to download latest successful artifacts for branches and tags !5142
   - Remove redundant pipeline tooltips (ClemMakesApps)
   - Expire commit info views after one day, instead of two weeks, to allow for user email updates
@@ -63,13 +112,16 @@ v 8.12.0 (unreleased)
   - Fix badge count alignment (ClemMakesApps)
   - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell)
   - Fix repo title alignment (ClemMakesApps)
+  - Change update interval of contacted_at
   - Fix branch title trailing space on hover (ClemMakesApps)
+  - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8)
   - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison)
   - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison)
   - Order award emoji tooltips in order they were added (EspadaV8)
   - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps)
   - Update merge_requests.md with a simpler way to check out a merge request. !5944
   - Fix button missing type (ClemMakesApps)
+  - Gitlab::Checks is now instrumented
   - Move to project dropdown with infinite scroll for better performance
   - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz)
   - Load branches asynchronously in Cherry Pick and Revert dialogs.
@@ -85,20 +137,52 @@ v 8.12.0 (unreleased)
   - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger)
   - Adds response mime type to transaction metric action when it's not HTML
   - Fix hover leading space bug in pipeline graph !5980
+  - Avoid conflict with admin labels when importing GitHub labels
   - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496
   - Fix repository page ui issues
+  - Avoid protected branches checks when verifying access without branch name
+  - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov)
   - Fixed invisible scroll controls on build page on iPhone
   - Fix error on raw build trace download for old builds stored in database !4822
   - Refactor the triggers page and documentation !6217
   - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska)
   - Use default clone protocol on "check out, review, and merge locally" help page URL
-
-v 8.11.5 (unreleased)
-  - Optimize branch lookups and force a repository reload for Repository#find_branch
-  - Fix member expiration date picker after update
+  - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska)
+  - Allow bulk update merge requests from merge requests index page
+  - Ensure validation messages are shown within the milestone form
+  - Add notification_settings API calls !5632 (mahcsig)
+  - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska)
+  - Fix URLs with anchors in wiki !6300 (houqp)
+  - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska)
+  - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225
+  - Fix Gitlab::Popen.popen thread-safety issue
+  - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska)
+  - Clean environment variables when running git hooks
+  - Fix Import/Export issues importing protected branches and some specific models
+  - Fix non-master branch readme display in tree view
+
+v 8.11.6
+  - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005
+  - Make merge conflict file size limit 200 KB, to match the docs. !6052
+  - Fix an error where we were unable to create a CommitStatus for running state. !6107
+  - Optimize discussion notes resolving and unresolving. !6141
+  - Fix GitLab import button. !6167
+  - Restore SSH Key title auto-population behavior. !6186
+  - Fix DB schema to match latest migration. !6256
+  - Exclude some pending or inactivated rows in Member scopes.
+
+v 8.11.5
+  - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087
+  - Fix member expiration date picker after update. !6184
   - Fix suggested colors options for new labels in the admin area. !6138
+  - Optimize discussion notes resolving and unresolving
   - Fix GitLab import button
+  - Fix confidential issues being exposed as public using gitlab.com export
+  - Remove gitorious from import_sources. !6180
+  - Scope webhooks/services that will run for confidential issues
   - Remove gitorious from import_sources
+  - Fix confidential issues being exposed as public using gitlab.com export
+  - Use oj gem for faster JSON processing
 
 v 8.11.4
   - Fix resolving conflicts on forks. !6082
@@ -112,13 +196,10 @@ v 8.11.4
   - Creating an issue through our API now emails label subscribers !5720
   - Block concurrent updates for Pipeline
   - Don't create groups for unallowed users when importing projects
-  - Fix resolving conflicts on forks
-  - Fix diff commenting on merge requests created prior to 8.10
-  - Don't create groups for unallowed users when importing projects
-  - Scope webhooks/services that will run for confidential issues
   - Fix issue boards leak private label names and descriptions
   - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
   - Remove gitorious. !5866
+  - Allow compare merge request versions
 
 v 8.11.3
   - Allow system info page to handle case where info is unavailable
@@ -129,6 +210,7 @@ v 8.11.3
   - Fix external issue tracker "Issues" link leading to 404s
   - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters
   - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
+  - Issues filters reset button
 
 v 8.11.2
   - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978
@@ -293,6 +375,13 @@ v 8.11.0
   - Update gitlab_git gem to 10.4.7
   - Simplify SQL queries of marking a todo as done
 
+v 8.10.9
+  - Exclude some pending or inactivated rows in Member scopes
+
+v 8.10.8
+  - Fix information disclosure in issue boards.
+  - Fix privilege escalation in project import.
+
 v 8.10.7
   - Upgrade Hamlit to 2.6.1. !5873
   - Upgrade Doorkeeper to 4.2.0. !5881
@@ -517,6 +606,10 @@ v 8.10.0
   - Export and import avatar as part of project import/export
   - Fix migration corrupting import data for old version upgrades
   - Show tooltip on GitLab export link in new project page
+  - Fix import_data wrongly saved as a result of an invalid import_url !5206
+
+v 8.9.9
+  - Exclude some pending or inactivated rows in Member scopes
 
 v 8.9.8
   - Upgrade Doorkeeper to 4.2.0. !5881
@@ -534,12 +627,6 @@ v 8.9.6
   - Keeps issue number when importing from Gitlab.com
   - Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska)
 
-v 8.9.7 (unreleased)
-  - Fix import_data wrongly saved as a result of an invalid import_url
-
-v 8.9.6
-  - Fix importing of events under notes for GitLab projects
-
 v 8.9.5
   - Add more debug info to import/export and memory killer. !5108
   - Fixed avatar alignment in new MR view. !5095
@@ -1805,7 +1892,7 @@ v 8.1.3
   - Use issue editor as cross reference comment author when issue is edited with a new mention
   - Add Facebook authentication
 
-v 8.1.1
+v 8.1.2
   - Fix cloning Wiki repositories via HTTP (Stan Hu)
   - Add migration to remove satellites directory
   - Fix specific runners visibility
@@ -2004,1692 +2091,5 @@ v 8.0.0
   - Redirect from incorrectly cased group or project path to correct one (Francesco Levorato)
   - Removed API calls from CE to CI
 
-v 7.14.3
-  - No changes
-
-v 7.14.2
-  - Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with ISO-encoded files (Stan Hu)
-  - Allow configuration of LDAP attributes GitLab will use for the new user account.
-
-v 7.14.1
-  - Improve abuse reports management from admin area
-  - Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
-  - Disabled DNS lookups for SSH in docker image (Rowan Wookey)
-  - Only include base URL in OmniAuth full_host parameter (Stan Hu)
-  - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu)
-  - Ability to enable SSL verification for Webhooks
-
-v 7.14.0
-  - Fix bug where non-project members of the target project could set labels on new merge requests.
-  - Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller)
-  - Fix redirection after sign in when using auto_sign_in_with_provider
-  - Upgrade gitlab_git to 7.2.14 to ignore CRLFs in .gitmodules (Stan Hu)
-  - Clear cache to prevent listing deleted branches after MR removes source branch (Stan Hu)
-  - Provide more feedback what went wrong if HipChat service failed test (Stan Hu)
-  - Fix bug where backslashes in inline diffs could be dropped (Stan Hu)
-  - Disable turbolinks when linking to Bitbucket import status (Stan Hu)
-  - Fix broken code import and display error messages if something went wrong with creating project (Stan Hu)
-  - Fix corrupted binary files when using API files endpoint (Stan Hu)
-  - Bump Haml to 4.0.7 to speed up textarea rendering (Stan Hu)
-  - Show incompatible projects in Bitbucket import status (Stan Hu)
-  - Fix coloring of diffs on MR Discussion-tab (Gert Goet)
-  - Fix "Network" and "Graphs" pages for branches with encoded slashes (Stan Hu)
-  - Fix errors deleting and creating branches with encoded slashes (Stan Hu)
-  - Always add current user to autocomplete controller to support filter by "Me" (Stan Hu)
-  - Fix multi-line syntax highlighting (Stan Hu)
-  - Fix network graph when branch name has single quotes (Stan Hu)
-  - Add "Confirm user" button in user admin page (Stan Hu)
-  - Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu)
-  - Add support for Unicode filenames in relative links (Hiroyuki Sato)
-  - Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki)
-  - Fix commit data retrieval when branch name has single quotes (Stan Hu)
-  - Check that project was actually created rather than just validated in import:repos task (Stan Hu)
-  - Fix full screen mode for snippet comments (Daniel Gerhardt)
-  - Fix 404 error in files view after deleting the last file in a repository (Stan Hu)
-  - Fix the "Reload with full diff" URL button (Stan Hu)
-  - Fix label read access for unauthenticated users (Daniel Gerhardt)
-  - Fix access to disabled features for unauthenticated users (Daniel Gerhardt)
-  - Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu)
-  - Fix file upload dialog for comment editing (Daniel Gerhardt)
-  - Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu)
-  - Return comments in created order in merge request API (Stan Hu)
-  - Disable internal issue tracker controller if external tracker is used (Stan Hu)
-  - Expire Rails cache entries after two weeks to prevent endless Redis growth
-  - Add support for destroying project milestones (Stan Hu)
-  - Allow custom backup archive permissions
-  - Add project star and fork count, group avatar URL and user/group web URL attributes to API
-  - Show who last edited a comment if it wasn't the original author
-  - Send notification to all participants when MR is merged.
-  - Add ability to manage user email addresses via the API.
-  - Show buttons to add license, changelog and contribution guide if they're missing.
-  - Tweak project page buttons.
-  - Disabled autocapitalize and autocorrect on login field (Daryl Chan)
-  - Mention group and project name in creation, update and deletion notices (Achilleas Pipinellis)
-  - Update gravatar link on profile page to link to configured gravatar host (Ben Bodenmiller)
-  - Remove redis-store TTL monkey patch
-  - Add support for CI skipped status
-  - Fetch code from forks to refs/merge-requests/:id/head when merge request created
-  - Remove comments and email addresses when publicly exposing ssh keys (Zeger-Jan van de Weg)
-  - Add "Check out branch" button to the MR page.
-  - Improve MR merge widget text and UI consistency.
-  - Improve text in MR "How To Merge" modal.
-  - Cache all events
-  - Order commits by date when comparing branches
-  - Fix bug causing error when the target branch of a symbolic ref was deleted
-  - Include branch/tag name in archive file and directory name
-  - Add dropzone upload progress
-  - Add a label for merged branches on branches page (Florent Baldino)
-  - Detect .mkd and .mkdn files as markdown (Ben Boeckel)
-  - Fix: User search feature in admin area does not respect filters
-  - Set max-width for README, issue and merge request description for easier read on big screens
-  - Update Flowdock integration to support new Flowdock API (Boyan Tabakov)
-  - Remove author from files view (Sven Strickroth)
-  - Fix infinite loop when SAML was incorrectly configured.
-
-v 7.13.5
-  - Satellites reverted
-
-v 7.13.4
-  - Allow users to send abuse reports
-
-v 7.13.3
-  - Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
-  - Allow users to send abuse reports
-  - Remove satellites
-  - Link username to profile on Group Members page (Tom Webster)
-
-v 7.13.2
-  - Fix randomly failed spec
-  - Create project services on Project creation
-  - Add admin_merge_request ability to Developer level and up
-  - Fix Error 500 when browsing projects with no HEAD (Stan Hu)
-  - Fix labels / assignee / milestone for the merge requests when issues are disabled
-  - Show the first tab automatically on MergeRequests#new
-  - Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt)
-  - Fix Gmail Actions
-
-v 7.13.1
-  - Fix: Label modifications are not reflected in existing notes and in the issue list
-  - Fix: Label not shown in the Issue list, although it's set through web interface
-  - Fix: Group/project references are linked incorrectly
-  - Improve documentation
-  - Fix of migration: Check if session_expire_delay column exists before adding the column
-  - Fix: ActionView::Template::Error
-  - Fix: "Create Merge Request" isn't always shown in event for newly pushed branch
-  - Fix bug causing "Remove source-branch" option not to work for merge requests from the same project.
-  - Render Note field hints consistently for "new" and "edit" forms
-
-v 7.13.0
-  - Remove repository graph log to fix slow cache updates after push event (Stan Hu)
-  - Only enable HSTS header for HTTPS and port 443 (Stan Hu)
-  - Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
-  - Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
-  - Add branch switching support for graphs (Daniel Gerhardt)
-  - Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
-  - Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
-  - Add support for unlocking users in admin settings (Stan Hu)
-  - Add Irker service configuration options (Stan Hu)
-  - Fix order of issues imported from GitHub (Hiroyuki Sato)
-  - Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart)
-  - Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI
-  - Add `two_factor_enabled` field to admin user API (Stan Hu)
-  - Fix invalid timestamps in RSS feeds (Rowan Wookey)
-  - Fix downloading of patches on public merge requests when user logged out (Stan Hu)
-  - Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu)
-  - Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu)
-  - Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu)
-  - Support commenting on diffs in side-by-side mode (Stan Hu)
-  - Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu)
-  - Return 40x error codes if branch could not be deleted in UI (Stan Hu)
-  - Remove project visibility icons from dashboard projects list
-  - Rename "Design" profile settings page to "Preferences".
-  - Allow users to customize their default Dashboard page.
-  - Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8
-  - Admin can edit and remove user identities
-  - Convert CRLF newlines to LF when committing using the web editor.
-  - API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged.
-  - Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled.
-  - Show a user's Two-factor Authentication status in the administration area.
-  - Explicit error when commit not found in the CI
-  - Improve performance for issue and merge request pages
-  - Users with guest access level can not set assignee, labels or milestones for issue and merge request
-  - Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels
-  - Better performance for pages with events list, issues list and commits list
-  - Faster automerge check and merge itself when source and target branches are in same repository
-  - Correctly show anonymous authorized applications under Profile > Applications.
-  - Query Optimization in MySQL.
-  - Allow users to be blocked and unblocked via the API
-  - Use native Postgres database cleaning during backup restore
-  - Redesign project page. Show README as default instead of activity. Move project activity to separate page
-  - Make left menu more hierarchical and less contextual by adding back item at top
-  - A fork can’t have a visibility level that is greater than the original project.
-  - Faster code search in repository and wiki. Fixes search page timeout for big repositories
-  - Allow administrators to disable 2FA for a specific user
-  - Add error message for SSH key linebreaks
-  - Store commits count in database (will populate with valid values only after first push)
-  - Rebuild cache after push to repository in background job
-  - Fix transferring of project to another group using the API.
-
-v 7.12.2
-  - Correctly show anonymous authorized applications under Profile > Applications.
-  - Faster automerge check and merge itself when source and target branches are in same repository
-  - Audit log for user authentication
-  - Allow custom label to be set for authentication providers.
-
-v 7.12.1
-  - Fix error when deleting a user who has projects (Stan Hu)
-  - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
-  - Add SAML to list of social_provider (Matt Firtion)
-  - Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets)
-  - Fix closed merge request scope at milestone page (Dmitriy Zaporozhets)
-  - Revert merge request states renaming
-  - Fix hooks for web based events with external issue references (Daniel Gerhardt)
-  - Improve performance for issue and merge request pages
-  - Compress database dumps to reduce backup size
-
-v 7.12.0
-  - Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu)
-  - Disable changing of target branch in new merge request page when a branch has already been specified (Stan Hu)
-  - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
-  - Update oauth button logos for Twitter and Google to recommended assets
-  - Update browser gem to version 0.8.0 for IE11 support (Stan Hu)
-  - Fix timeout when rendering file with thousands of lines.
-  - Add "Remember me" checkbox to LDAP signin form.
-  - Add session expiration delay configuration through UI application settings
-  - Don't notify users mentioned in code blocks or blockquotes.
-  - Omit link to generate labels if user does not have access to create them (Stan Hu)
-  - Show warning when a comment will add 10 or more people to the discussion.
-  - Disable changing of the source branch in merge request update API (Stan Hu)
-  - Shorten merge request WIP text.
-  - Add option to disallow users from registering any application to use GitLab as an OAuth provider
-  - Support editing target branch of merge request (Stan Hu)
-  - Refactor permission checks with issues and merge requests project settings (Stan Hu)
-  - Fix Markdown preview not working in Edit Milestone page (Stan Hu)
-  - Fix Zen Mode not closing with ESC key (Stan Hu)
-  - Allow HipChat API version to be blank and default to v2 (Stan Hu)
-  - Add file attachment support in Milestone description (Stan Hu)
-  - Fix milestone "Browse Issues" button.
-  - Set milestone on new issue when creating issue from index with milestone filter active.
-  - Make namespace API available to all users (Stan Hu)
-  - Add webhook support for note events (Stan Hu)
-  - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu)
-  - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu)
-  - Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu)
-  - Fix git blame syntax highlighting when different commits break up lines (Stan Hu)
-  - Add "Resend confirmation e-mail" link in profile settings (Stan Hu)
-  - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka)
-  - Disabled expansion of top/bottom blobs for new file diffs
-  - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka)
-  - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka)
-  - Use the user list from the target project in a merge request (Stan Hu)
-  - Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen)
-  - Add validation to wiki page creation (only [a-zA-Z0-9/_-] are allowed) (Jeroen van Baarsen)
-  - Fix new/empty milestones showing 100% completion value (Jonah Bishop)
-  - Add a note when an Issue or Merge Request's title changes
-  - Consistently refer to MRs as either Merged or Closed.
-  - Add Merged tab to MR lists.
-  - Prefix EmailsOnPush email subject with `[Git]`.
-  - Group project contributions by both name and email.
-  - Clarify navigation labels for Project Settings and Group Settings.
-  - Move user avatar and logout button to sidebar
-  - You can not remove user if he/she is an only owner of group
-  - User should be able to leave group. If not - show him proper message
-  - User has ability to leave project
-  - Add SAML support as an omniauth provider
-  - Allow to configure a URL to show after sign out
-  - Add an option to automatically sign-in with an Omniauth provider
-  - GitLab CI service sends .gitlab-ci.yml in each push call
-  - When remove project - move repository and schedule it removal
-  - Improve group removing logic
-  - Trigger create-hooks on backup restore task
-  - Add option to automatically link omniauth and LDAP identities
-  - Allow special character in users bio. I.e.: I <3 GitLab
-
-v 7.11.4
-  - Fix missing bullets when creating lists
-  - Set rel="nofollow" on external links
-
-v 7.11.3
-  - no changes
-  - Fix upgrader script (Martins Polakovs)
-
-v 7.11.2
-  - no changes
-
-v 7.11.1
-  - no changes
-
-v 7.11.0
-  - Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger)
-  - Get editing comments to work in Chrome 43 again.
-  - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu)
-  - Don't show duplicate deploy keys
-  - Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger)
-  - Make the first branch pushed to an empty repository the default HEAD (Stan Hu)
-  - Fix broken view when using a tag to display a tree that contains git submodules (Stan Hu)
-  - Make Reply-To config apply to change e-mail confirmation and other Devise notifications (Stan Hu)
-  - Add application setting to restrict user signups to e-mail domains (Stan Hu)
-  - Don't allow a merge request to be merged when its title starts with "WIP".
-  - Add a page title to every page.
-  - Allow primary email to be set to an email that you've already added.
-  - Fix clone URL field and X11 Primary selection (Dmitry Medvinsky)
-  - Ignore invalid lines in .gitmodules
-  - Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu)
-  - Redirect to sign in page after signing out.
-  - Fix "Hello @username." references not working by no longer allowing usernames to end in period.
-  - Fix "Revspec not found" errors when viewing diffs in a forked project with submodules (Stan Hu)
-  - Improve project page UI
-  - Fix broken file browsing with relative submodule in personal projects (Stan Hu)
-  - Add "Reply quoting selected text" shortcut key (`r`)
-  - Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention.
-  - Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention.
-  - When use change branches link at MR form - save source branch selection instead of target one
-  - Improve handling of large diffs
-  - Added GitLab Event header for project hooks
-  - Add Two-factor authentication (2FA) for GitLab logins
-  - Show Atom feed buttons everywhere where applicable.
-  - Add project activity atom feed.
-  - Don't crash when an MR from a fork has a cross-reference comment from the target project on one of its commits.
-  - Explain how to get a new password reset token in welcome emails
-  - Include commit comments in MR from a forked project.
-  - Group milestones by title in the dashboard and all other issue views.
-  - Query issues, merge requests and milestones with their IID through API (Julien Bianchi)
-  - Add default project and snippet visibility settings to the admin web UI.
-  - Show incompatible projects in Google Code import status (Stan Hu)
-  - Fix bug where commit data would not appear in some subdirectories (Stan Hu)
-  - Task lists are now usable in comments, and will show up in Markdown previews.
-  - Fix bug where avatar filenames were not actually deleted from the database during removal (Stan Hu)
-  - Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu)
-  - Protect OmniAuth request phase against CSRF.
-  - Don't send notifications to mentioned users that don't have access to the project in question.
-  - Add search issues/MR by number
-  - Change plots to bar graphs in commit statistics screen
-  - Move snippets UI to fluid layout
-  - Improve UI for sidebar. Increase separation between navigation and content
-  - Improve new project command options (Ben Bodenmiller)
-  - Add common method to force UTF-8 and use it to properly handle non-ascii OAuth user properties (Onur Küçük)
-  - Prevent sending empty messages to HipChat (Chulki Lee)
-  - Improve UI for mobile phones on dashboard and project pages
-  - Add room notification and message color option for HipChat
-  - Allow to use non-ASCII letters and dashes in project and namespace name. (Jakub Jirutka)
-  - Add footnotes support to Markdown (Guillaume Delbergue)
-  - Add current_sign_in_at to UserFull REST api.
-  - Make Sidekiq MemoryKiller shutdown signal configurable
-  - Add "Create Merge Request" buttons to commits and branches pages and push event.
-  - Show user roles by comments.
-  - Fix automatic blocking of auto-created users from Active Directory.
-  - Call merge request webhook for each new commits (Arthur Gautier)
-  - Use SIGKILL by default in Sidekiq::MemoryKiller
-  - Fix mentioning of private groups.
-  - Add style for <kbd> element in markdown
-  - Spin spinner icon next to "Checking for CI status..." on MR page.
-  - Fix reference links in dashboard activity and ATOM feeds.
-  - Ensure that the first added admin performs repository imports
-
-v 7.10.4
-  - Fix migrations broken in 7.10.2
-  - Make tags for GitLab installations running on MySQL case sensitive
-  - Get Gitorious importer to work again.
-  - Fix adding new group members from admin area
-  - Fix DB error when trying to tag a repository (Stan Hu)
-  - Fix Error 500 when searching Wiki pages (Stan Hu)
-  - Unescape branch names in compare commit (Stan Hu)
-  - Order commit comments chronologically in API.
-
-v 7.10.2
-  - Fix CI links on MR page
-
-v 7.10.0
-  - Ignore submodules that are defined in .gitmodules but are checked in as directories.
-  - Allow projects to be imported from Google Code.
-  - Remove access control for uploaded images to fix broken images in emails (Hannes Rosenögger)
-  - Allow users to be invited by email to join a group or project.
-  - Don't crash when project repository doesn't exist.
-  - Add config var to block auto-created LDAP users.
-  - Don't use HTML ellipsis in EmailsOnPush subject truncated commit message.
-  - Set EmailsOnPush reply-to address to committer email when enabled.
-  - Fix broken file browsing with a submodule that contains a relative link (Stan Hu)
-  - Fix persistent XSS vulnerability around profile website URLs.
-  - Fix project import URL regex to prevent arbitary local repos from being imported.
-  - Fix directory traversal vulnerability around uploads routes.
-  - Fix directory traversal vulnerability around help pages.
-  - Don't leak existence of project via search autocomplete.
-  - Don't leak existence of group or project via search.
-  - Fix bug where Wiki pages that included a '/' were no longer accessible (Stan Hu)
-  - Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu)
-  - Add a rake task to check repository integrity with `git fsck`
-  - Add ability to configure Reply-To address in gitlab.yml (Stan Hu)
-  - Move current user to the top of the list in assignee/author filters (Stan Hu)
-  - Fix broken side-by-side diff view on merge request page (Stan Hu)
-  - Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu)
-  - Allow HTML tags in Markdown input
-  - Fix code unfold not working on Compare commits page (Stan Hu)
-  - Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik)
-  - Fix "Import projects from" button to show the correct instructions (Stan Hu)
-  - Fix dots in Wiki slugs causing errors (Stan Hu)
-  - Make maximum attachment size configurable via Application Settings (Stan Hu)
-  - Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg)
-  - Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu)
-  - Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu)
-  - Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu)
-  - enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger)
-  - Fix a link in the patch update guide
-  - Add a service to support external wikis (Hannes Rosenögger)
-  - Omit the "email patches" link and fix plain diff view for merge commits
-  - List new commits for newly pushed branch in activity view.
-  - Add sidetiq gem dependency to match EE
-  - Add changelog, license and contribution guide links to project tab bar.
-  - Improve diff UI
-  - Fix alignment of navbar toggle button (Cody Mize)
-  - Fix checkbox rendering for nested task lists
-  - Identical look of selectboxes in UI
-  - Upgrade the gitlab_git gem to version 7.1.3
-  - Move "Import existing repository by URL" option to button.
-  - Improve error message when save profile has error.
-  - Passing the name of pushed ref to CI service (requires GitLab CI 7.9+)
-  - Add location field to user profile
-  - Fix print view for markdown files and wiki pages
-  - Fix errors when deleting old backups
-  - Improve GitLab performance when working with git repositories
-  - Add tag message and last commit to tag hook (Kamil Trzciński)
-  - Restrict permissions on backup files
-  - Improve oauth accounts UI in profile page
-  - Add ability to unlink connected accounts
-  - Replace commits calendar with faster contribution calendar that includes issues and merge requests
-  - Add inifinite scroll to user page activity
-  - Don't include system notes in issue/MR comment count.
-  - Don't mark merge request as updated when merge status relative to target branch changes.
-  - Link note avatar to user.
-  - Make Git-over-SSH errors more descriptive.
-  - Fix EmailsOnPush.
-  - Refactor issue filtering
-  - AJAX selectbox for issue assignee and author filters
-  - Fix issue with missing options in issue filtering dropdown if selected one
-  - Prevent holding Control-Enter or Command-Enter from posting comment multiple times.
-  - Prevent note form from being cleared when submitting failed.
-  - Improve file icons rendering on tree (Sullivan Sénéchal)
-  - API: Add pagination to project events
-  - Get issue links in notification mail to work again.
-  - Don't show commit comment button when user is not signed in.
-  - Fix admin user projects lists.
-  - Don't leak private group existence by redirecting from namespace controller to group controller.
-  - Ability to skip some items from backup (database, respositories or uploads)
-  - Archive repositories in background worker.
-  - Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace.
-  - Project labels are now available over the API under the "tag_list" field (Cristian Medina)
-  - Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz)
-  - Fix and improve help rendering (Sullivan Sénéchal)
-  - Fix final line in EmailsOnPush email diff being rendered as error.
-  - Prevent duplicate Buildkite service creation.
-  - Fix git over ssh errors 'fatal: protocol error: bad line length character'
-  - Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled
-  - Bust group page project list cache when namespace name or path changes.
-  - Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded
-  - Allow user to choose a public email to show on public profile
-  - Remove truncation from issue titles on milestone page (Jason Blanchard)
-  - Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
-  - Fix merge request comments on files with multiple commits
-  - Fix Resource Owner Password Authentication Flow
-  - Add icons to Add dropdown items.
-  - Allow admin to create public deploy keys that are accessible to any project.
-  - Warn when gitlab-shell version doesn't match requirement.
-  - Skip email confirmation when set by admin or via LDAP.
-  - Only allow users to reference groups, projects, issues, MRs, commits they have access to.
-
-v 7.9.4
-  - Security: Fix project import URL regex to prevent arbitary local repos from being imported
-  - Fixed issue where only 25 commits would load in file listings
-  - Fix LDAP identities  after config update
-
-v 7.9.3
-  - Contains no changes
-
-v 7.9.2
-  - Contains no changes
-
-v 7.9.1
-  - Include missing events and fix save functionality in admin service template settings form (Stan Hu)
-  - Fix "Import projects from" button to show the correct instructions (Stan Hu)
-  - Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu)
-  - Fix for LDAP with commas in DN
-  - Fix missing events and in admin Slack service template settings form (Stan Hu)
-  - Don't show commit comment button when user is not signed in.
-  - Downgrade gemnasium-gitlab-service gem
-
-v 7.9.0
-  - Add HipChat integration documentation (Stan Hu)
-  - Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu)
-  - Fix broken email images (Hannes Rosenögger)
-  - Automatically config git if user forgot, where possible (Zeger-Jan van de Weg)
-  - Fix mass SQL statements on initial push (Hannes Rosenögger)
-  - Add tag push notifications and normalize HipChat and Slack messages to be consistent (Stan Hu)
-  - Add comment notification events to HipChat and Slack services (Stan Hu)
-  - Add issue and merge request events to HipChat and Slack services (Stan Hu)
-  - Fix merge request URL passed to Webhooks. (Stan Hu)
-  - Fix bug that caused a server error when editing a comment to "+1" or "-1" (Stan Hu)
-  - Fix code preview theme setting for comments, issues, merge requests, and snippets (Stan Hu)
-  - Move labels/milestones tabs to sidebar
-  - Upgrade Rails gem to version 4.1.9.
-  - Improve error messages for file edit failures
-  - Improve UI for commits, issues and merge request lists
-  - Fix commit comments on first line of diff not rendering in Merge Request Discussion view.
-  - Allow admins to override restricted project visibility settings.
-  - Move restricted visibility settings from gitlab.yml into the web UI.
-  - Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev)
-  - Save web edit in new branch
-  - Fix ordering of imported but unchanged projects (Marco Wessel)
-  - Mobile UI improvements: make aside content expandable
-  - Expose avatar_url in projects API
-  - Fix checkbox alignment on the application settings page.
-  - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger)
-  - Fix mass-unassignment of issues (Robert Speicher)
-  - Fix hidden diff comments in merge request discussion view
-  - Allow user confirmation to be skipped for new users via API
-  - Add a service to send updates to an Irker gateway (Romain Coltel)
-  - Add brakeman (security scanner for Ruby on Rails)
-  - Slack username and channel options
-  - Add grouped milestones from all projects to dashboard.
-  - Webhook sends pusher email as well as commiter
-  - Add Bitbucket omniauth provider.
-  - Add Bitbucket importer.
-  - Support referencing issues to a project whose name starts with a digit
-  - Condense commits already in target branch when updating merge request source branch.
-  - Send notifications and leave system comments when bulk updating issues.
-  - Automatically link commit ranges to compare page: sha1...sha4 or sha1..sha4 (includes sha1 in comparison)
-  - Move groups page from profile to dashboard
-  - Starred projects page at dashboard
-  - Blocking user does not remove him/her from project/groups but show blocked label
-  - Change subject of EmailsOnPush emails to include namespace, project and branch.
-  - Change subject of EmailsOnPush emails to include first commit message when multiple were pushed.
-  - Remove confusing footer from EmailsOnPush mail body.
-  - Add list of changed files to EmailsOnPush emails.
-  - Add option to send EmailsOnPush emails from committer email if domain matches.
-  - Add option to disable code diffs in EmailOnPush emails.
-  - Wrap commit message in EmailsOnPush email.
-  - Send EmailsOnPush emails when deleting commits using force push.
-  - Fix EmailsOnPush email comparison link to include first commit.
-  - Fix highliht of selected lines in file
-  - Reject access to group/project avatar if the user doesn't have access.
-  - Add database migration to clean group duplicates with same path and name (Make sure you have a backup before update)
-  - Add GitLab active users count to rake gitlab:check
-  - Starred projects page at dashboard
-  - Make email display name configurable
-  - Improve json validation in hook data
-  - Use Emoji One
-  - Updated emoji help documentation to properly reference EmojiOne.
-  - Fix missing GitHub organisation repositories on import page.
-  - Added blue theme
-  - Remove annoying notice messages when create/update merge request
-  - Allow smb:// links in Markdown text.
-  - Filter merge request by title or description at Merge Requests page
-  - Block user if he/she was blocked in Active Directory
-  - Fix import pages not working after first load.
-  - Use custom LDAP label in LDAP signin form.
-  - Execute hooks and services when branch or tag is created or deleted through web interface.
-  - Block and unblock user if he/she was blocked/unblocked in Active Directory
-  - Raise recommended number of unicorn workers from 2 to 3
-  - Use same layout and interactivity for project members as group members.
-  - Prevent gitlab-shell character encoding issues by receiving its changes as raw data.
-  - Ability to unsubscribe/subscribe to issue or merge request
-  - Delete deploy key when last connection to a project is destroyed.
-  - Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther)
-  - Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup)
-  - Add canceled status for CI
-  - Send EmailsOnPush email when branch or tag is created or deleted.
-  - Faster merge request processing for large repository
-  - Prevent doubling AJAX request with each commit visit via Turbolink
-  - Prevent unnecessary doubling of js events on import pages and user calendar
-
-v 7.8.4
-  - Fix issue_tracker_id substitution in custom issue trackers
-  - Fix path and name duplication in namespaces
-
-v 7.8.3
-  - Bump version of gitlab_git fixing annotated tags without message
-
-v 7.8.2
-  - Fix service migration issue when upgrading from versions prior to 7.3
-  - Fix setting of the default use project limit via admin UI
-  - Fix showing of already imported projects for GitLab and Gitorious importers
-  - Fix response of push to repository to return "Not found" if user doesn't have access
-  - Fix check if user is allowed to view the file attachment
-  - Fix import check for case sensetive namespaces
-  - Increase timeout for Git-over-HTTP requests to 1 hour since large pulls/pushes can take a long time.
-  - Properly handle autosave local storage exceptions.
-  - Escape wildcards when searching LDAP by username.
-
-v 7.8.1
-  - Fix run of custom post receive hooks
-  - Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3
-  - Fix the warning for LDAP users about need to set password
-  - Fix avatars which were not shown for non logged in users
-  - Fix urls for the issues when relative url was enabled
-
-v 7.8.0
-  - Fix access control and protection against XSS for note attachments and other uploads.
-  - Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
-  - Make project search case insensitive (Hannes Rosenögger)
-  - Include issue/mr participants in list of recipients for reassign/close/reopen emails
-  - Expose description in groups API
-  - Better UI for project services page
-  - Cleaner UI for web editor
-  - Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
-  - Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen)
-  - View note image attachments in new tab when clicked instead of downloading them
-  - Improve sorting logic in UI and API. Explicitly define what sorting method is used by default
-  - Fix overflow at sidebar when have several items
-  - Add notes for label changes in issue and merge requests
-  - Show tags in commit view (Hannes Rosenögger)
-  - Only count a user's vote once on a merge request or issue (Michael Clarke)
-  - Increase font size when browse source files and diffs
-  - Service Templates now let you set default values for all services
-  - Create new file in empty repository using GitLab UI
-  - Ability to clone project using oauth2 token
-  - Upgrade Sidekiq gem to version 3.3.0
-  - Stop git zombie creation during force push check
-  - Show success/error messages for test setting button in services
-  - Added Rubocop for code style checks
-  - Fix commits pagination
-  - Async load a branch information at the commit page
-  - Disable blacklist validation for project names
-  - Allow configuring protection of the default branch upon first push (Marco Wessel)
-  - Add gitlab.com importer
-  - Add an ability to login with gitlab.com
-  - Add a commit calendar to the user profile (Hannes Rosenögger)
-  - Submit comment on command-enter
-  - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`.
-  - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" (Julien Bianchi and Hannes Rosenögger)
-  - Fix long broadcast message cut-off on left sidebar (Visay Keo)
-  - Add Project Avatars (Steven Thonus and Hannes Rosenögger)
-  - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
-  - Edit group members via API
-  - Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks)
-  - Add action property to merge request hook (Julien Bianchi)
-  - Remove duplicates from group milestone participants list.
-  - Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger)
-  - API: Access groups with their path (Julien Bianchi)
-  - Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard)
-  - Allow notification email to be set separately from primary email.
-  - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
-  - Don't have Markdown preview fail for long comments/wiki pages.
-  - When test webhook - show error message instead of 500 error page if connection to hook url was reset
-  - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
-  - Added persistent collapse button for left side nav bar (Jason Blanchard)
-  - Prevent losing unsaved comments by automatically restoring them when comment page is loaded again.
-  - Don't allow page to be scaled on mobile.
-  - Clean the username acquired from OAuth/LDAP so it doesn't fail username validation and block signing up.
-  - Show assignees in merge request index page (Kelvin Mutuma)
-  - Link head panel titles to relevant root page.
-  - Allow users that signed up via OAuth to set their password in order to use Git over HTTP(S).
-  - Show users button to share their newly created public or internal projects on twitter
-  - Add quick help links to the GitLab pricing and feature comparison pages.
-  - Fix duplicate authorized applications in user profile and incorrect application client count in admin area.
-  - Make sure Markdown previews always use the same styling as the eventual destination.
-  - Remove deprecated Group#owner_id from API
-  - Show projects user contributed to on user page. Show stars near project on user page.
-  - Improve database performance for GitLab
-  - Add Asana service (Jeremy Benoist)
-  - Improve project webhooks with extra data
-
-v 7.7.2
-  - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
-  - Fix issue when LDAP user can't login with existing GitLab account
-
-v 7.7.1
-  - Improve mention autocomplete performance
-  - Show setup instructions for GitHub import if disabled
-  - Allow use http for OAuth applications
-
-v 7.7.0
-  - Import from GitHub.com feature
-  - Add Jetbrains Teamcity CI service (Jason Lippert)
-  - Mention notification level
-  - Markdown preview in wiki (Yuriy Glukhov)
-  - Raise group avatar filesize limit to 200kb
-  - OAuth applications feature
-  - Show user SSH keys in admin area
-  - Developer can push to protected branches option
-  - Set project path instead of project name in create form
-  - Block Git HTTP access after 10 failed authentication attempts
-  - Updates to the messages returned by API (sponsored by O'Reilly Media)
-  - New UI layout with side navigation
-  - Add alert message in case of outdated browser (IE < 10)
-  - Added API support for sorting projects
-  - Update gitlab_git to version 7.0.0.rc14
-  - Add API project search filter option for authorized projects
-  - Fix File blame not respecting branch selection
-  - Change some of application settings on fly in admin area UI
-  - Redesign signin/signup pages
-  - Close standard input in Gitlab::Popen.popen
-  - Trigger GitLab CI when push tags
-  - When accept merge request - do merge using sidaekiq job
-  - Enable web signups by default
-  - Fixes for diff comments: drag-n-drop images, selecting images
-  - Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update
-  - Remove password strength indicator
-
-v 7.6.0
-  - Fork repository to groups
-  - New rugged version
-  - Add CRON=1 backup setting for quiet backups
-  - Fix failing wiki restore
-  - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
-  - Monokai highlighting style now more faithful to original design (Mark Riedesel)
-  - Create project with repository in synchrony
-  - Added ability to create empty repo or import existing one if project does not have repository
-  - Reactivate highlight.js language autodetection
-  - Mobile UI improvements
-  - Change maximum avatar file size from 100KB to 200KB
-  - Strict validation for snippet file names
-  - Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada)
-  - In the docker directory is a container template based on the Omnibus packages.
-  - Update Sidekiq to version 2.17.8
-  - Add author filter to project issues and merge requests pages
-  - Atom feed for user activity
-  - Support multiple omniauth providers for the same user
-  - Rendering cross reference in issue title and tooltip for merge request
-  - Show username in comments
-  - Possibility to create Milestones or Labels when Issues are disabled
-  - Fix bug with showing gpg signature in tag
-
-v 7.5.3
-  - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
-
-v 7.5.2
-  - Don't log Sidekiq arguments by default
-  - Fix restore of wiki repositories from backups
-
-v 7.5.1
-  - Add missing timestamps to 'members' table
-
-v 7.5.0
-  - API: Add support for Hipchat (Kevin Houdebert)
-  - Add time zone configuration in gitlab.yml (Sullivan Senechal)
-  - Fix LDAP authentication for Git HTTP access
-  - Run 'GC.start' after every EmailsOnPushWorker job
-  - Fix LDAP config lookup for provider 'ldap'
-  - Drop all sequences during Postgres database restore
-  - Project title links to project homepage (Ben Bodenmiller)
-  - Add Atlassian Bamboo CI service (Drew Blessing)
-  - Mentioned @user will receive email even if he is not participating in issue or commit
-  - Session API: Use case-insensitive authentication like in UI (Andrey Krivko)
-  - Tie up loose ends with annotated tags: API & UI (Sean Edge)
-  - Return valid json for deleting branch via API (sponsored by O'Reilly Media)
-  - Expose username in project events API (sponsored by O'Reilly Media)
-  - Adds comments to commits in the API
-  - Performance improvements
-  - Fix post-receive issue for projects with deleted forks
-  - New gitlab-shell version with custom hooks support
-  - Improve code
-  - GitLab CI 5.2+ support (does not support older versions)
-  - Fixed bug when you can not push commits starting with 000000 to protected branches
-  - Added a password strength indicator
-  - Change project name and path in one form
-  - Display renamed files in diff views (Vinnie Okada)
-  - Fix raw view for public snippets
-  - Use secret token with GitLab internal API.
-  - Add missing timestamps to 'members' table
-
-v 7.4.5
-  - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
-
-v 7.4.4
-  - No changes
-
-v 7.4.3
-  - Fix raw snippets view
-  - Fix security issue for member api
-  - Fix buildbox integration
-
-v 7.4.2
-  - Fix internal snippet exposing for unauthenticated users
-
-v 7.4.1
-  - Fix LDAP authentication for Git HTTP access
-  - Fix LDAP config lookup for provider 'ldap'
-  - Fix public snippets
-  - Fix 500 error on projects with nested submodules
-
-v 7.4.0
-  - Refactored membership logic
-  - Improve error reporting on users API (Julien Bianchi)
-  - Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally
-  - Default branch is protected by default
-  - Increase unicorn timeout to 60 seconds
-  - Sort search autocomplete projects by stars count so most popular go first
-  - Add README to tab on project show page
-  - Do not delete tmp/repositories itself during clean-up, only its contents
-  - Support for backup uploads to remote storage
-  - Prevent notes polling when there are not notes
-  - Internal ForkService: Prepare support for fork to a given namespace
-  - API: Add support for forking a project via the API (Bernhard Kaindl)
-  - API: filter project issues by milestone (Julien Bianchi)
-  - Fail harder in the backup script
-  - Changes to Slack service structure, only webhook url needed
-  - Zen mode for wiki and milestones (Robert Schilling)
-  - Move Emoji parsing to html-pipeline-gitlab (Robert Schilling)
-  - Font Awesome 4.2 integration (Sullivan Senechal)
-  - Add Pushover service integration (Sullivan Senechal)
-  - Add select field type for services options (Sullivan Senechal)
-  - Add cross-project references to the Markdown parser (Vinnie Okada)
-  - Add task lists to issue and merge request descriptions (Vinnie Okada)
-  - Snippets can be public, internal or private
-  - Improve danger zone: ask project path to confirm data-loss action
-  - Raise exception on forgery
-  - Show build coverage in Merge Requests (requires GitLab CI v5.1)
-  - New milestone and label links on issue edit form
-  - Improved repository graphs
-  - Improve event note display in dashboard and project activity views (Vinnie Okada)
-  - Add users sorting to admin area
-  - UI improvements
-  - Fix ambiguous sha problem with mentioned commit
-  - Fixed bug with apostrophe when at mentioning users
-  - Add active directory ldap option
-  - Developers can push to wiki repo. Protected branches does not affect wiki repo any more
-  - Faster rev list
-  - Fix branch removal
-
-v 7.3.2
-  - Fix creating new file via web editor
-  - Use gitlab-shell v2.0.1
-
-v 7.3.1
-  - Fix ref parsing in Gitlab::GitAccess
-  - Fix error 500 when viewing diff on a file with changed permissions
-  - Fix adding comments to MR when source branch is master
-  - Fix error 500 when searching description contains relative link
-
-v 7.3.0
-  - Always set the 'origin' remote in satellite actions
-  - Write authorized_keys in tmp/ during tests
-  - Use sockets to connect to Redis
-  - Add dormant New Relic gem (can be enabled via environment variables)
-  - Expire Rack sessions after 1 week
-  - Cleaner signin/signup pages
-  - Improved comments UI
-  - Better search with filtering, pagination etc
-  - Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov)
-  - Prevent project stars duplication when fork project
-  - Use the default Unicorn socket backlog value of 1024
-  - Support Unix domain sockets for Redis
-  - Store session Redis keys in 'session:gitlab:' namespace
-  - Deprecate LDAP account takeover based on partial LDAP email / GitLab username match
-  - Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy)
-  - Keyboard shortcuts for productivity (Robert Schilling)
-  - API: filter issues by state (Julien Bianchi)
-  - API: filter issues by labels (Julien Bianchi)
-  - Add system hook for ssh key changes
-  - Add blob permalink link (Ciro Santilli)
-  - Create annotated tags through UI and API (Sean Edge)
-  - Snippets search (Charles Bushong)
-  - Comment new push to existing MR
-  - Add 'ci' to the blacklist of forbidden names
-  - Improve text filtering on issues page
-  - Comment & Close button
-  - Process git push --all much faster
-  - Don't allow edit of system notes
-  - Project wiki search (Ralf Seidler)
-  - Enabled Shibboleth authentication support (Matus Banas)
-  - Zen mode (fullscreen) for issues/MR/notes (Robert Schilling)
-  - Add ability to configure webhook timeout via gitlab.yml (Wes Gurney)
-  - Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media)
-  - Add Redis socket support to 'rake gitlab:shell:install'
-
-v 7.2.1
-  - Delete orphaned labels during label migration (James Brooks)
-  - Security: prevent XSS with stricter MIME types for raw repo files
-
-v 7.2.0
-  - Explore page
-  - Add project stars (Ciro Santilli)
-  - Log Sidekiq arguments
-  - Better labels: colors, ability to rename and remove
-  - Improve the way merge request collects diffs
-  - Improve compare page for large diffs
-  - Expose the full commit message via API
-  - Fix 500 error on repository rename
-  - Fix bug when MR download patch return invalid diff
-  - Test gitlab-shell integration
-  - Repository import timeout increased from 2 to 4 minutes allowing larger repos to be imported
-  - API for labels (Robert Schilling)
-  - API: ability to set an import url when creating project for specific user
-
-v 7.1.1
-  - Fix cpu usage issue in Firefox
-  - Fix redirect loop when changing password by new user
-  - Fix 500 error on new merge request page
-
-v 7.1.0
-  - Remove observers
-  - Improve MR discussions
-  - Filter by description on Issues#index page
-  - Fix bug with namespace select when create new project page
-  - Show README link after description for non-master members
-  - Add @all mention for comments
-  - Dont show reply button if user is not signed in
-  - Expose more information for issues with webhook
-  - Add a mention of the merge request into the default merge request commit message
-  - Improve code highlight, introduce support for more languages like Go, Clojure, Erlang etc
-  - Fix concurrency issue in repository download
-  - Dont allow repository name start with ?
-  - Improve email threading (Pierre de La Morinerie)
-  - Cleaner help page
-  - Group milestones
-  - Improved email notifications
-  - Contributors API (sponsored by Mobbr)
-  - Fix LDAP TLS authentication (Boris HUISGEN)
-  - Show VERSION information on project sidebar
-  - Improve branch removal logic when accept MR
-  - Fix bug where comment form is spawned inside the Reply button
-  - Remove Dir.chdir from Satellite#lock for thread-safety
-  - Increased default git max_size value from 5MB to 20MB in gitlab.yml. Please update your configs!
-  - Show error message in case of timeout in satellite when create MR
-  - Show first 100 files for huge diff instead of hiding all
-  - Change default admin email from admin@local.host to admin@example.com
-
-v 7.0.0
-  - The CPU no longer overheats when you hold down the spacebar
-  - Improve edit file UI
-  - Add ability to upload group avatar when create
-  - Protected branch cannot be removed
-  - Developers can remove normal branches with UI
-  - Remove branch via API (sponsored by O'Reilly Media)
-  - Move protected branches page to Project settings area
-  - Redirect to Files view when create new branch via UI
-  - Drag and drop upload of image in every markdown-area (Earle Randolph Bunao and Neil Francis Calabroso)
-  - Refactor the markdown relative links processing
-  - Make it easier to implement other CI services for GitLab
-  - Group masters can create projects in group
-  - Deprecate ruby 1.9.3 support
-  - Only masters can rewrite/remove git tags
-  - Add X-Frame-Options SAMEORIGIN to Nginx config so Sidekiq admin is visible
-  - UI improvements
-  - Case-insensetive search for issues
-  - Update to rails 4.1
-  - Improve performance of application for projects and groups with a lot of members
-  - Formally support Ruby 2.1
-  - Include Nginx gitlab-ssl config
-  - Add manual language detection for highlight.js
-  - Added example.com/:username routing
-  - Show notice if your profile is public
-  - UI improvements for mobile devices
-  - Improve diff rendering performance
-  - Drag-n-drop for issues and merge requests between states at milestone page
-  - Fix '0 commits' message for huge repositories on project home page
-  - Prevent 500 error page when visit commit page from large repo
-  - Add notice about huge push over http to unicorn config
-  - File action in satellites uses default 30 seconds timeout instead of old 10 seconds one
-  - Overall performance improvements
-  - Skip init script check on omnibus-gitlab
-  - Be more selective when killing stray Sidekiqs
-  - Check LDAP user filter during sign-in
-  - Remove wall feature (no data loss - you can take it from database)
-  - Dont expose user emails via API unless you are admin
-  - Detect issues closed by Merge Request description
-  - Better email subject lines from email on push service (Alex Elman)
-  - Enable identicon for gravatar be default
-
-v 6.9.2
-  - Revert the commit that broke the LDAP user filter
-
-v 6.9.1
-  - Fix scroll to highlighted line
-  - Fix the pagination on load for commits page
-
-v 6.9.0
-  - Store Rails cache data in the Redis `cache:gitlab` namespace
-  - Adjust MySQL limits for existing installations
-  - Add db index on project_id+iid column. This prevents duplicate on iid (During migration duplicates will be removed)
-  - Markdown preview or diff during editing via web editor (Evgeniy Sokovikov)
-  - Give the Rails cache its own Redis namespace
-  - Add ability to set different ssh host, if different from http/https
-  - Fix syntax highlighting for code comments blocks
-  - Improve comments loading logic
-  - Stop refreshing comments when the tab is hidden
-  - Improve issue and merge request mobile UI (Drew Blessing)
-  - Document how to convert a backup to PostgreSQL
-  - Fix locale bug in backup manager
-  - Fix can not automerge when MR description is too long
-  - Fix wiki backup skip bug
-  - Two Step MR creation process
-  - Remove unwanted files from satellite working directory with git clean -fdx
-  - Accept merge request via API (sponsored by O'Reilly Media)
-  - Add more access checks during API calls
-  - Block SSH access for 'disabled' Active Directory users
-  - Labels for merge requests (Drew Blessing)
-  - Threaded emails by setting a Message-ID (Philip Blatter)
-
-v 6.8.0
-  - Ability to at mention users that are participating in issue and merge req. discussion
-  - Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu)
-  - Make user search case-insensitive (Christopher Arnold)
-  - Remove omniauth-ldap nickname bug workaround
-  - Drop all tables before restoring a Postgres backup
-  - Make the repository downloads path configurable
-  - Create branches via API (sponsored by O'Reilly Media)
-  - Changed permission of gitlab-satellites directory not to be world accessible
-  - Protected branch does not allow force push
-  - Fix popen bug in `rake gitlab:satellites:create`
-  - Disable connection reaping for MySQL
-  - Allow oauth signup without email for twitter and github
-  - Fix faulty namespace names that caused 500 on user creation
-  - Option to disable standard login
-  - Clean old created archives from repository downloads directory
-  - Fix download link for huge MR diffs
-  - Expose event and mergerequest timestamps in API
-  - Fix emails on push service when only one commit is pushed
-
-v 6.7.3
-  - Fix the merge notification email not being sent (Pierre de La Morinerie)
-  - Drop all tables before restoring a Postgres backup
-  - Remove yanked modernizr gem
-
-v 6.7.2
-  - Fix upgrader script
-
-v 6.7.1
-  - Fix GitLab CI integration
-
-v 6.7.0
-  - Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations
-  - Add support for Gemnasium as a Project Service (Olivier Gonzalez)
-  - Add edit file button to MergeRequest diff
-  - Public groups (Jason Hollingsworth)
-  - Cleaner headers in Notification Emails (Pierre de La Morinerie)
-  - Blob and tree gfm links to anchors work
-  - Piwik Integration (Sebastian Winkler)
-  - Show contribution guide link for new issue form (Jeroen van Baarsen)
-  - Fix CI status for merge requests from fork
-  - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
-  - New page load indicator that includes a spinner that scrolls with the page
-  - Converted all the help sections into markdown
-  - LDAP user filters
-  - Streamline the content of notification emails (Pierre de La Morinerie)
-  - Fixes a bug with group member administration (Matt DeTullio)
-  - Sort tag names using VersionSorter (Robert Speicher)
-  - Add GFM autocompletion for MergeRequests (Robert Speicher)
-  - Add webhook when a new tag is pushed (Jeroen van Baarsen)
-  - Add button for toggling inline comments in diff view
-  - Add retry feature for repository import
-  - Reuse the GitLab LDAP connection within each request
-  - Changed markdown new line behaviour to conform to markdown standards
-  - Fix global search
-  - Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5)
-  - Create and Update MR calls now support the description parameter (Greg Messner)
-  - Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository
-  - Added Slack service integration (Federico Ravasio)
-  - Better API responses for access_levels (sponsored by O'Reilly Media)
-  - Requires at least 2 unicorn workers
-  - Requires gitlab-shell v1.9+
-  - Replaced gemoji(due to closed licencing problem) with Phantom Open Emoji library(combined SIL Open Font License, MIT License and the CC 3.0 License)
-  - Fix `/:username.keys` response content type (Dmitry Medvinsky)
-
-v 6.6.5
-  - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
-  - Hide mr close button for comment form if merge request was closed or inline comment
-  - Adds ability to reopen closed merge request
-
-v 6.6.4
-  - Add missing html escape for highlighted code blocks in comments, issues
-
-v 6.6.3
-  - Fix 500 error when edit yourself from admin area
-  - Hide private groups for public profiles
-
-v 6.6.2
-  - Fix 500 error on branch/tag create or remove via UI
-
-v 6.6.1
-  - Fix 500 error on files tab if submodules presents
-
-v 6.6.0
-  - Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys
-  - Permissions: Developer now can manage issue tracker (modify any issue)
-  - Improve Code Compare page performance
-  - Group avatar
-  - Pygments.rb replaced with highlight.js
-  - Improve Merge request diff store logic
-  - Improve render performnace for MR show page
-  - Fixed Assembla hardcoded project name
-  - Jira integration documentation
-  - Refactored app/services
-  - Remove snippet expiration
-  - Mobile UI improvements (Drew Blessing)
-  - Fix block/remove UI for admin::users#show page
-  - Show users' group membership on users' activity page (Robert Djurasaj)
-  - User pages are visible without login if user is authorized to a public project
-  - Markdown rendered headers have id derived from their name and link to their id
-  - Improve application to work faster with large groups (100+ members)
-  - Multiple emails per user
-  - Show last commit for file when view file source
-  - Restyle Issue#show page and MR#show page
-  - Ability to filter by multiple labels for Issues page
-  - Rails version to 4.0.3
-  - Fixed attachment identifier displaying underneath note text (Jason Blanchard)
-
-v 6.5.1
-  - Fix branch selectbox when create merge request from fork
-
-v 6.5.0
-  - Dropdown menus on issue#show page for assignee and milestone (Jason Blanchard)
-  - Add color custimization and previewing to broadcast messages
-  - Fixed notes anchors
-  - Load new comments in issues dynamically
-  - Added sort options to Public page
-  - New filters (assigned/authored/all) for Dashboard#issues/merge_requests (sponsored by Say Media)
-  - Add project visibility icons to dashboard
-  - Enable secure cookies if https used
-  - Protect users/confirmation with rack_attack
-  - Default HTTP headers to protect against MIME-sniffing, force https if enabled
-  - Bootstrap 3 with responsive UI
-  - New repository download formats: tar.bz2, zip, tar (Jason Hollingsworth)
-  - Restyled accept widgets for MR
-  - SCSS refactored
-  - Use jquery timeago plugin
-  - Fix 500 error for rdoc files
-  - Ability to customize merge commit message (sponsored by Say Media)
-  - Search autocomplete via ajax
-  - Add website url to user profile
-  - Files API supports base64 encoded content (sponsored by O'Reilly Media)
-  - Added support for Go's repository retrieval (Bruno Albuquerque)
-
-v 6.4.3
-  - Don't use unicorn worker killer if PhusionPassenger is defined
-
-v 6.4.2
-  - Fixed wrong behaviour of script/upgrade.rb
-
-v 6.4.1
-  - Fixed bug with repository rename
-  - Fixed bug with project transfer
-
-v 6.4.0
-  - Added sorting to project issues page (Jason Blanchard)
-  - Assembla integration (Carlos Paramio)
-  - Fixed another 500 error with submodules
-  - UI: More compact issues page
-  - Minimal password length increased to 8 symbols
-  - Side-by-side diff view (Steven Thonus)
-  - Internal projects (Jason Hollingsworth)
-  - Allow removal of avatar (Drew Blessing)
-  - Project webhooks now support issues and merge request events
-  - Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth)
-  - Expire event cache on avatar creation/removal (Drew Blessing)
-  - Archiving old projects (Steven Thonus)
-  - Rails 4
-  - Add time ago tooltips to show actual date/time
-  - UI: Fixed UI for admin system hooks
-  - Ruby script for easier GitLab upgrade
-  - Do not remove Merge requests if fork project was removed
-  - Improve sign-in/signup UX
-  - Add resend confirmation link to sign-in page
-  - Set noreply@HOSTNAME for reply_to field in all emails
-  - Show GitLab API version on Admin#dashboard
-  - API Cross-origin resource sharing
-  - Show READMe link at project home page
-  - Show repo size for projects in Admin area
-
-v 6.3.0
-  - API for adding gitlab-ci service
-  - Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey)
-  - Restyle project home page
-  - Grammar fixes
-  - Show branches list (which branches contains commit) on commit page (Andrew Kumanyaev)
-  - Security improvements
-  - Added support for GitLab CI 4.0
-  - Fixed issue with 500 error when group did not exist
-  - Ability to leave project
-  - You can create file in repo using UI
-  - You can remove file from repo using UI
-  - API: dropped default_branch attribute from project during creation
-  - Project default_branch is not stored in db any more. It takes from repo now.
-  - Admin broadcast messages
-  - UI improvements
-  - Dont show last push widget if user removed this branch
-  - Fix 500 error for repos with newline in file name
-  - Extended html titles
-  - API: create/update/delete repo files
-  - Admin can transfer project to any namespace
-  - API: projects/all for admin users
-  - Fix recent branches order
-
-v 6.2.4
-  - Security: Cast API private_token to string (CVE-2013-4580)
-  - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
-  - Fix for Git SSH access for LDAP users
-
-v 6.2.3
-  - Security: More protection against CVE-2013-4489
-  - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
-  - Fix sidekiq rake tasks
-
-v 6.2.2
-  - Security: Update gitlab_git (CVE-2013-4489)
-
-v 6.2.1
-  - Security: Fix issue with generated passwords for new users
-
-v 6.2.0
-  - Public project pages are now visible to everyone (files, issues, wik, etc.)
-    THIS MEANS YOUR ISSUES AND WIKI FOR PUBLIC PROJECTS ARE PUBLICLY VISIBLE AFTER THE UPGRADE
-  - Add group access to permissions page
-  - Require current password to change one
-  - Group owner or admin can remove other group owners
-  - Remove group transfer since we have multiple owners
-  - Respect authorization in Repository API
-  - Improve UI for Project#files page
-  - Add more security specs
-  - Added search for projects by name to api (Izaak Alpert)
-  - Make default user theme configurable (Izaak Alpert)
-  - Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev)
-  - Rake tasks for webhooks management (Jonhnny Weslley)
-  - Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov)
-  - API: Remove group
-  - API: Remove project
-  - Avatar upload on profile page with a maximum of 100KB (Steven Thonus)
-  - Store the sessions in Redis instead of the cookie store
-  - Fixed relative links in markdown
-  - User must confirm their email if signup enabled
-  - User must confirm changed email
-
-v 6.1.0
-  - Project specific IDs for issues, mr, milestones
-    Above items will get a new id and for example all bookmarked issue urls will change.
-    Old issue urls are redirected to the new one if the issue id is too high for an internal id.
-  - Description field added to Merge Request
-  - API: Sudo api calls (Izaak Alpert)
-  - API: Group membership api (Izaak Alpert)
-  - Improved commit diff
-  - Improved large commit handling (Boyan Tabakov)
-  - Rewrite: Init script now less prone to errors and keeps better track of the service (Rovanion Luckey)
-  - Link issues, merge requests, and commits when they reference each other with GFM (Ash Wilson)
-  - Close issues automatically when pushing commits with a special message
-  - Improve user removal from admin area
-  - Invalidate events cache when project was moved
-  - Remove deprecated classes and rake tasks
-  - Add event filter for group and project show pages
-  - Add links to create branch/tag from project home page
-  - Add public-project? checkbox to new-project view
-  - Improved compare page. Added link to proceed into Merge Request
-  - Send an email to a user when they are added to group
-  - New landing page when you have 0 projects
-
-v 6.0.0
-  - Feature: Replace teams with group membership
-    We introduce group membership in 6.0 as a replacement for teams.
-    The old combination of groups and teams was confusing for a lot of people.
-    And when the members of a team where changed this wasn't reflected in the project permissions.
-    In GitLab 6.0 you will be able to add members to a group with a permission level for each member.
-    These group members will have access to the projects in that group.
-    Any changes to group members will immediately be reflected in the project permissions.
-    You can even have multiple owners for a group, greatly simplifying administration.
-  - Feature: Ability to have multiple owners for group
-  - Feature: Merge Requests between fork and project (Izaak Alpert)
-  - Feature: Generate fingerprint for ssh keys
-  - Feature: Ability to create and remove branches with UI
-  - Feature: Ability to create and remove git tags with UI
-  - Feature: Groups page in profile. You can leave group there
-  - API: Allow login with LDAP credentials
-  - Redesign: project settings navigation
-  - Redesign: snippets area
-  - Redesign: ssh keys page
-  - Redesign: buttons, blocks and other ui elements
-  - Add comment title to rss feed
-  - You can use arrows to navigate at tree view
-  - Add project filter on dashboard
-  - Cache project graph
-  - Drop support of root namespaces
-  - Default theme is classic now
-  - Cache result of methods like authorize_projects, project.team.members etc
-  - Remove $.ready events
-  - Fix onclick events being double binded
-  - Add notification level to group membership
-  - Move all project controllers/views under Projects:: module
-  - Move all profile controllers/views under Profiles:: module
-  - Apply user project limit only for personal projects
-  - Unicorn is default web server again
-  - Store satellites lock files inside satellites dir
-  - Disabled threadsafety mode in rails
-  - Fixed bug with loosing MR comments
-  - Improved MR comments logic
-  - Render readme file for projects in public area
-
-v 5.4.2
-  - Security: Cast API private_token to string (CVE-2013-4580)
-  - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
-
-v 5.4.1
-  - Security: Fixes for CVE-2013-4489
-  - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
-
-v 5.4.0
-  - Ability to edit own comments
-  - Documentation improvements
-  - Improve dashboard projects page
-  - Fixed nav for empty repos
-  - GitLab Markdown help page
-  - Misspelling fixes
-  - Added support of unicorn and fog gems
-  - Added client list to API doc
-  - Fix PostgreSQL database restoration problem
-  - Increase snippet content column size
-  - allow project import via git:// url
-  - Show participants on issues, including mentions
-  - Notify mentioned users with email
-
-v 5.3.0
-  - Refactored services
-  - Campfire service added
-  - HipChat service added
-  - Fixed bug with LDAP + git over http
-  - Fixed bug with google analytics code being ignored
-  - Improve sign-in page if ldap enabled
-  - Respect newlines in wall messages
-  - Generate the Rails secret token on first run
-  - Rename repo feature
-  - Init.d: remove gitlab.socket on service start
-  - Api: added teams api
-  - Api: Prevent blob content being escaped
-  - Api: Smart deploy key add behaviour
-  - Api: projects/owned.json return user owned project
-  - Fix bug with team assignation on project from #4109
-  - Advanced snippets: public/private, project/personal (Andrew Kulakov)
-  - Repository Graphs (Karlo Nicholas T. Soriano)
-  - Fix dashboard lost if comment on commit
-  - Update gitlab-grack. Fixes issue with --depth option
-  - Fix project events duplicate on project page
-  - Fix postgres error when displaying network graph.
-  - Fix dashboard event filter when navigate via turbolinks
-  - init.d: Ensure socket is removed before starting service
-  - Admin area: Style teams:index, group:show pages
-  - Own page for failed forking
-  - Scrum view for milestone
-
-v 5.2.0
-  - Turbolinks
-  - Git over http with ldap credentials
-  - Diff with better colors and some spacing on the corners
-  - Default values for project features
-  - Fixed huge_commit view
-  - Restyle project clone panel
-  - Move Gitlab::Git code to gitlab_git gem
-  - Move update docs in repo
-  - Requires gitlab-shell v1.4.0
-  - Fixed submodules listing under file tab
-  - Fork feature (Angus MacArthur)
-  - git version check in gitlab:check
-  - Shared deploy keys feature
-  - Ability to generate default labels set for issues
-  - Improve gfm autocomplete (Harold Luo)
-  - Added support for Google Analytics
-  - Code search feature (Javier Castro)
-
-v 5.1.0
-  - You can login with email or username now
-  - Corrected project transfer rollback when repository cannot be moved
-  - Move both repo and wiki when project transfer requested
-  - Admin area: project editing was removed from admin namespace
-  - Access: admin user has now access to any project.
-  - Notification settings
-  - Gitlab::Git set of objects to abstract from grit library
-  - Replace Unicorn web server with Puma
-  - Backup/Restore refactored. Backup dump project wiki too now
-  - Restyled Issues list. Show milestone version in issue row
-  - Restyled Merge Request list
-  - Backup now dump/restore uploads
-  - Improved performance of dashboard (Andrew Kumanyaev)
-  - File history now tracks renames (Akzhan Abdulin)
-  - Drop wiki migration tools
-  - Drop sqlite migration tools
-  - project tagging
-  - Paginate users in API
-  - Restyled network graph (Hiroyuki Sato)
-
-v 5.0.1
-  - Fixed issue with gitlab-grit being overridden by grit
-
-v 5.0.0
-  - Replaced gitolite with gitlab-shell
-  - Removed gitolite-related libraries
-  - State machine added
-  - Setup gitlab as git user
-  - Internal API
-  - Show team tab for empty projects
-  - Import repository feature
-  - Updated rails
-  - Use lambda for scopes
-  - Redesign admin area -> users
-  - Redesign admin area -> user
-  - Secure link to file attachments
-  - Add validations for Group and Team names
-  - Restyle team page for project
-  - Update capybara, rspec-rails, poltergeist to recent versions
-  - Wiki on git using Gollum
-  - Added Solarized Dark theme for code review
-  - Don't show user emails in autocomplete lists, profile pages
-  - Added settings tab for group, team, project
-  - Replace user popup with icons in header
-  - Handle project moving with gitlab-shell
-  - Added select2-rails for selectboxes with ajax data load
-  - Fixed search field on projects page
-  - Added teams to search autocomplete
-  - Move groups and teams on dashboard sidebar to sub-tabs
-  - API: improved return codes and docs. (Felix Gilcher, Sebastian Ziebell)
-  - Redesign wall to be more like chat
-  - Snippets, Wall features are disabled by default for new projects
-
-v 4.2.0
-  - Teams
-  - User show page. Via /u/username
-  - Show help contents on pages for better navigation
-  - Async gitolite calls
-  - added satellites logs
-  - can_create_group, can_create_team booleans for User
-  - Process webhooks async
-  - GFM: Fix images escaped inside links
-  - Network graph improved
-  - Switchable branches for network graph
-  - API: Groups
-  - Fixed project download
-
-v 4.1.0
-  - Optional Sign-Up
-  - Discussions
-  - Satellites outside of tmp
-  - Line numbers for blame
-  - Project public mode
-  - Public area with unauthorized access
-  - Load dashboard events with ajax
-  - remember dashboard filter in cookies
-  - replace resque with sidekiq
-  - fix routing issues
-  - cleanup rake tasks
-  - fix backup/restore
-  - scss cleanup
-  - show preview for note images
-  - improved network-graph
-  - get rid of app/roles/
-  - added new classes Team, Repository
-  - Reduce amount of gitolite calls
-  - Ability to add user in all group projects
-  - remove deprecated configs
-  - replaced Korolev font with open font
-  - restyled admin/dashboard page
-  - restyled admin/projects page
-
-v 4.0.0
-  - Remove project code and path from API. Use id instead
-  - Return valid cloneable url to repo for webhook
-  - Fixed backup issue
-  - Reorganized settings
-  - Fixed commits compare
-  - Refactored scss
-  - Improve status checks
-  - Validates presence of User#name
-  - Fixed postgres support
-  - Removed sqlite support
-  - Modified post-receive hook
-  - Milestones can be closed now
-  - Show comment events on dashboard
-  - Quick add team members via group#people page
-  - [API] expose created date for hooks and SSH keys
-  - [API] list, create issue notes
-  - [API] list, create snippet notes
-  - [API] list, create wall notes
-  - Remove project code - use path instead
-  - added username field to user
-  - rake task to fill usernames based on emails create namespaces for users
-  - STI Group < Namespace
-  - Project has namespace_id
-  - Projects with namespaces also namespaced in gitolite and stored in subdir
-  - Moving project to group will move it under group namespace
-  - Ability to move project from namespaces to another
-  - Fixes commit patches getting escaped (see #2036)
-  - Support diff and patch generation for commits and merge request
-  - MergeReqest doesn't generate a temporary file for the patch any more
-  - Update the UI to allow downloading Patch or Diff
-
-v 3.1.0
-  - Updated gems
-  - Services: Gitlab CI integration
-  - Events filter on dashboard
-  - Own namespace for redis/resque
-  - Optimized commit diff views
-  - add alphabetical order for projects admin page
-  - Improved web editor
-  - Commit stats page
-  - Documentation split and cleanup
-  - Link to commit authors everywhere
-  - Restyled milestones list
-  - added Milestone to Merge Request
-  - Restyled Top panel
-  - Refactored Satellite Code
-  - Added file line links
-  - moved from capybara-webkit to poltergeist + phantomjs
-
-v 3.0.3
-  - Fixed bug with issues list in Chrome
-  - New Feature: Import team from another project
-
-v 3.0.2
-  - Fixed gitlab:app:setup
-  - Fixed application error on empty project in admin area
-  - Restyled last push widget
-
-v 3.0.1
-  - Fixed git over http
-
-v 3.0.0
-  - Projects groups
-  - Web Editor
-  - Fixed bug with gitolite keys
-  - UI improved
-  - Increased performance of application
-  - Show user avatar in last commit when browsing Files
-  - Refactored Gitlab::Merge
-  - Use Font Awesome for icons
-  - Separate observing of Note and MergeRequests
-  - Milestone "All Issues" filter
-  - Fix issue close and reopen button text and styles
-  - Fix forward/back while browsing Tree hierarchy
-  - Show number of notes for commits and merge requests
-  - Added support pg from box and update installation doc
-  - Reject ssh keys that break gitolite
-  - [API] list one project hook
-  - [API] edit project hook
-  - [API] list project snippets
-  - [API] allow to authorize using private token in HTTP header
-  - [API] add user creation
-
-v 2.9.1
-  - Fixed resque custom config init
-
-v 2.9.0
-  - fixed inline notes bugs
-  - refactored rspecs
-  - refactored gitolite backend
-  - added factory_girl
-  - restyled projects list on dashboard
-  - ssh keys validation to prevent gitolite crash
-  - send notifications if changed permission in project
-  - scss refactoring. gitlab_bootstrap/ dir
-  - fix git push http body bigger than 112k problem
-  - list of labels  page under issues tab
-  - API for milestones, keys
-  - restyled buttons
-  - OAuth
-  - Comment order changed
-
-v 2.8.1
-  - ability to disable gravatars
-  - improved MR diff logic
-  - ssh key help page
-
-v 2.8.0
-  - Gitlab Flavored Markdown
-  - Bulk issues update
-  - Issues API
-  - Cucumber coverage increased
-  - Post-receive files fixed
-  - UI improved
-  - Application cleanup
-  - more cucumber
-  - capybara-webkit + headless
-
-v 2.7.0
-  - Issue Labels
-  - Inline diff
-  - Git HTTP
-  - API
-  - UI improved
-  - System hooks
-  - UI improved
-  - Dashboard events endless scroll
-  - Source performance increased
-
-v 2.6.0
-  - UI polished
-  - Improved network graph + keyboard nav
-  - Handle huge commits
-  - Last Push widget
-  - Bugfix
-  - Better performance
-  - Email in resque
-  - Increased test coverage
-  - Ability to remove branch with MR accept
-  - a lot of code refactored
-
-v 2.5.0
-  - UI polished
-  - Git blame for file
-  - Bugfix
-  - Email in resque
-  - Better test coverage
-
-v 2.4.0
-  - Admin area stats page
-  - Ability to block user
-  - Simplified dashboard area
-  - Improved admin area
-  - Bootstrap 2.0
-  - Responsive layout
-  - Big commits handling
-  - Performance improved
-  - Milestones
-
-v 2.3.1
-  - Issues pagination
-  - ssl fixes
-  - Merge Request pagination
-
-v 2.3.0
-  - Dashboard r1
-  - Search r1
-  - Project page
-  - Close merge request on push
-  - Persist MR diff after merge
-  - mysql support
-  - Documentation
-
-v 2.2.0
-  - We’ve added support of LDAP auth
-  - Improved permission logic (4 roles system)
-  - Protected branches (now only masters can push to protected branches)
-  - Usability improved
-  - twitter bootstrap integrated
-  - compare view between commits
-  - wiki feature
-  - now you can enable/disable issues, wiki, wall features per project
-  - security fixes
-  - improved code browsing (ajax branch switch etc)
-  - improved per-line commenting
-  - git submodules displayed
-  - moved to rails 3.2
-  - help section improved
-
-v 2.1.0
-  - Project tab r1
-  - List branches/tags
-  - per line comments
-  - mass user import
-
-v 2.0.0
-  - gitolite as main git host system
-  - merge requests
-  - project/repo access
-  - link to commit/issue feed
-  - design tab
-  - improved email notifications
-  - restyled dashboard
-  - bugfix
-
-v 1.2.2
-  - common config file gitlab.yml
-  - issues restyle
-  - snippets restyle
-  - clickable news feed header on dashboard
-  - bugfix
-
-v 1.2.1
-  - bugfix
-
-v 1.2.0
-  - new design
-  - user dashboard
-  - network graph
-  - markdown support for comments
-  - encoding issues
-  - wall like twitter timeline
-
-v 1.1.0
-  - project dashboard
-  - wall redesigned
-  - feature: code snippets
-  - fixed horizontal scroll on file preview
-  - fixed app crash if commit message has invalid chars
-  - bugfix & code cleaning
-
-v 1.0.2
-  - fixed bug with empty project
-  - added adv validation for project path & code
-  - feature: issues can be sortable
-  - bugfix
-  - username displayed on top panel
-
-v 1.0.1
-  - fixed: with invalid source code for commit
-  - fixed: lose branch/tag selection when use tree navigation
-  - when history clicked - display path
-  - bug fix & code cleaning
-
-v 1.0.0
-  - bug fix
-  - projects preview mode
-
-v 0.9.6
-  - css fix
-  - new repo empty tree until restart server - fixed
-
-v 0.9.4
-  - security improved
-  - authorization improved
-  - html escaping
-  - bug fix
-  - increased test coverage
-  - design improvements
-
-v 0.9.1
-  - increased test coverage
-  - design improvements
-  - new issue email notification
-  - updated app name
-  - issue redesigned
-  - issue can be edit
-
-v 0.8.0
-  - syntax highlight for main file types
-  - redesign
-  - stability
-  - security fixes
-  - increased test coverage
-  - email notification
+v 7.14.3 through 0.8.0
+  - See changelogs/archive.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c77dcd96a7d1b15e9e267a52b66449adf4d2a8f9..d5e15bfce14b15db5d5f2a65e2bfb31280145548 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -91,19 +91,7 @@ This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
 
 ## Implement design & UI elements
 
-### Design reference
-
-The GitLab design reference can be found in the [gitlab-design] project.
-The designs are made using Antetype (`.atype` files). You can use the
-[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
-(the PNG is 1:1).
-
-The current designs can be found in the [`gitlab8.atype` file].
-
-### UI development kit
-
-Implemented UI elements can also be found at https://gitlab.com/help/ui. Please
-note that this page isn't comprehensive at this time.
+Please see the [UI Guide for building GitLab].
 
 ## Issue tracker
 
@@ -289,6 +277,8 @@ request is as follows:
 1. For more complex migrations, write tests.
 1. Merge requests **must** adhere to the [merge request performance
    guidelines](doc/development/merge_request_performance_guidelines.md).
+1. For tests that use Capybara or PhantomJS, see this [article on how
+   to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
 
 The **official merge window** is in the beginning of the month from the 1st to
 the 7th day of the month. This is the best time to submit an MR and get
@@ -489,7 +479,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
 [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
 [scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
 [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
-[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
-[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
-[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
+[UI Guide for building GitLab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/ui_guide.md
 [license-finder-doc]: doc/development/licensing.md
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 18091983f59ddde8105e566545a0d9e4a12a4f1c..1545d966571dc86b54c98f888a0e6451501f8c81 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-3.4.0
+3.5.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index b4d6d12101febdd4c5792ced9aae7600069d928e..100435be135a32ae8974fe4dd281c4d3a9d62e02 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.7.11
+0.8.2
diff --git a/Gemfile b/Gemfile
index 2aab2fa473ae8d428488064694c7e110a11cdeeb..3bfd02ee4d35a446a64eff3809c9ed13e20fce8e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -26,7 +26,7 @@ gem 'omniauth-auth0',         '~> 1.4.1'
 gem 'omniauth-azure-oauth2',  '~> 0.0.6'
 gem 'omniauth-bitbucket',     '~> 0.0.2'
 gem 'omniauth-cas3',          '~> 1.1.2'
-gem 'omniauth-facebook',      '~> 3.0.0'
+gem 'omniauth-facebook',      '~> 4.0.0'
 gem 'omniauth-github',        '~> 1.1.1'
 gem 'omniauth-gitlab',        '~> 1.0.0'
 gem 'omniauth-google-oauth2', '~> 0.4.1'
@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2'
 
 # Extracting information from a git repository
 # Provide access to Gitlab::Git library
-gem 'gitlab_git', '~> 10.6.3'
+gem 'gitlab_git', '~> 10.6.6'
 
 # LDAP Auth
 # GitLab fork with several improvements to original library. For full list of changes
@@ -206,6 +206,9 @@ gem 'mousetrap-rails', '~> 1.4.6'
 # Detect and convert string character encoding
 gem 'charlock_holmes', '~> 0.7.3'
 
+# Faster JSON
+gem 'oj', '~> 2.17.4'
+
 # Parse time & duration
 gem 'chronic', '~> 0.10.2'
 gem 'chronic_duration', '~> 0.10.6'
@@ -295,9 +298,10 @@ group :development, :test do
   gem 'spring-commands-spinach',  '~> 1.1.0'
   gem 'spring-commands-teaspoon', '~> 0.0.2'
 
-  gem 'rubocop', '~> 0.41.2', require: false
-  gem 'rubocop-rspec', '~> 1.5.0', require: false
+  gem 'rubocop', '~> 0.42.0', require: false
+  gem 'rubocop-rspec', '~> 1.7.0', require: false
   gem 'scss_lint', '~> 0.47.0', require: false
+  gem 'haml_lint', '~> 0.18.2', require: false
   gem 'simplecov', '0.12.0', require: false
   gem 'flog', '~> 4.3.2', require: false
   gem 'flay', '~> 2.6.1', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index fbdde038ae93475bc1b91abdef8c1b1cad822a42..bbaed6b6a4741cd09345f36c1cfadf503fb8cd36 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -189,7 +189,7 @@ GEM
     erubis (2.7.0)
     escape_utils (1.1.1)
     eventmachine (1.0.8)
-    excon (0.49.0)
+    excon (0.52.0)
     execjs (2.6.0)
     expression_parser (0.9.0)
     factory_girl (4.5.0)
@@ -215,8 +215,8 @@ GEM
     flowdock (0.7.1)
       httparty (~> 0.7)
       multi_json
-    fog-aws (0.9.2)
-      fog-core (~> 1.27)
+    fog-aws (0.11.0)
+      fog-core (~> 1.38)
       fog-json (~> 1.0)
       fog-xml (~> 0.1)
       ipaddress (~> 0.8)
@@ -225,7 +225,7 @@ GEM
       fog-core (~> 1.27)
       fog-json (~> 1.0)
       fog-xml (~> 0.1)
-    fog-core (1.40.0)
+    fog-core (1.42.0)
       builder
       excon (~> 0.49)
       formatador (~> 0.2)
@@ -279,7 +279,7 @@ GEM
       diff-lcs (~> 1.1)
       mime-types (>= 1.16, < 3)
       posix-spawn (~> 0.3)
-    gitlab_git (10.6.3)
+    gitlab_git (10.6.6)
       activesupport (~> 4.0)
       charlock_holmes (~> 0.7.3)
       github-linguist (~> 4.7.0)
@@ -322,11 +322,18 @@ GEM
     grape-entity (0.4.8)
       activesupport
       multi_json (>= 1.3.2)
+    haml (4.0.7)
+      tilt
+    haml_lint (0.18.2)
+      haml (~> 4.0)
+      rake (>= 10, < 12)
+      rubocop (>= 0.36.0)
+      sysexits (~> 1.1)
     hamlit (2.6.1)
       temple (~> 0.7.6)
       thor
       tilt
-    hashie (3.4.3)
+    hashie (3.4.4)
     health_check (2.1.0)
       rails (>= 4.0)
     hipchat (1.5.2)
@@ -394,7 +401,7 @@ GEM
       mime-types (>= 1.16, < 4)
     mail_room (0.8.0)
     method_source (0.8.2)
-    mime-types (2.99.2)
+    mime-types (2.99.3)
     mimemagic (0.3.0)
     mini_portile2 (2.1.0)
     minitest (5.7.0)
@@ -420,6 +427,7 @@ GEM
       rack (>= 1.2, < 3)
     octokit (4.3.0)
       sawyer (~> 0.7.0, >= 0.5.3)
+    oj (2.17.4)
     omniauth (1.3.1)
       hashie (>= 1.2, < 4)
       rack (>= 1.0, < 3)
@@ -437,7 +445,7 @@ GEM
       addressable (~> 2.3)
       nokogiri (~> 1.6.6)
       omniauth (~> 1.2)
-    omniauth-facebook (3.0.0)
+    omniauth-facebook (4.0.0)
       omniauth-oauth2 (~> 1.2)
     omniauth-github (1.1.2)
       omniauth (~> 1.0)
@@ -584,7 +592,7 @@ GEM
       railties (>= 4.2.0, < 5.1)
     rinku (2.0.0)
     rotp (2.1.2)
-    rouge (2.0.5)
+    rouge (2.0.6)
     rqrcode (0.7.0)
       chunky_png
     rqrcode-rails3 (0.1.7)
@@ -612,14 +620,14 @@ GEM
     rspec-retry (0.4.5)
       rspec-core
     rspec-support (3.5.0)
-    rubocop (0.41.2)
+    rubocop (0.42.0)
       parser (>= 2.3.1.1, < 3.0)
       powerpack (~> 0.1)
       rainbow (>= 1.99.1, < 3.0)
       ruby-progressbar (~> 1.7)
       unicode-display_width (~> 1.0, >= 1.0.1)
-    rubocop-rspec (1.5.0)
-      rubocop (>= 0.40.0)
+    rubocop-rspec (1.7.0)
+      rubocop (>= 0.42.0)
     ruby-fogbugz (0.2.1)
       crack (~> 0.4)
     ruby-prof (0.15.9)
@@ -723,6 +731,7 @@ GEM
     stringex (2.5.2)
     sys-filesystem (1.1.6)
       ffi
+    sysexits (1.2.0)
     systemu (2.6.5)
     task_list (1.0.2)
       html-pipeline
@@ -754,7 +763,7 @@ GEM
     unf (0.1.4)
       unf_ext
     unf_ext (0.0.7.2)
-    unicode-display_width (1.1.0)
+    unicode-display_width (1.1.1)
     unicorn (4.9.0)
       kgio (~> 2.6)
       rack
@@ -858,7 +867,7 @@ DEPENDENCIES
   github-linguist (~> 4.7.0)
   github-markup (~> 1.4)
   gitlab-flowdock-git-hook (~> 1.0.1)
-  gitlab_git (~> 10.6.3)
+  gitlab_git (~> 10.6.6)
   gitlab_meta (= 7.0)
   gitlab_omniauth-ldap (~> 1.2.1)
   gollum-lib (~> 4.2)
@@ -866,6 +875,7 @@ DEPENDENCIES
   gon (~> 6.1.0)
   grape (~> 0.15.0)
   grape-entity (~> 0.4.2)
+  haml_lint (~> 0.18.2)
   hamlit (~> 2.6.1)
   health_check (~> 2.1.0)
   hipchat (~> 1.5.0)
@@ -895,12 +905,13 @@ DEPENDENCIES
   nokogiri (~> 1.6.7, >= 1.6.7.2)
   oauth2 (~> 1.2.0)
   octokit (~> 4.3.0)
+  oj (~> 2.17.4)
   omniauth (~> 1.3.1)
   omniauth-auth0 (~> 1.4.1)
   omniauth-azure-oauth2 (~> 0.0.6)
   omniauth-bitbucket (~> 0.0.2)
   omniauth-cas3 (~> 1.1.2)
-  omniauth-facebook (~> 3.0.0)
+  omniauth-facebook (~> 4.0.0)
   omniauth-github (~> 1.1.1)
   omniauth-gitlab (~> 1.0.0)
   omniauth-google-oauth2 (~> 0.4.1)
@@ -935,8 +946,8 @@ DEPENDENCIES
   rqrcode-rails3 (~> 0.1.7)
   rspec-rails (~> 3.5.0)
   rspec-retry (~> 0.4.5)
-  rubocop (~> 0.41.2)
-  rubocop-rspec (~> 1.5.0)
+  rubocop (~> 0.42.0)
+  rubocop-rspec (~> 1.7.0)
   ruby-fogbugz (~> 0.2.1)
   ruby-prof (~> 0.15.9)
   sanitize (~> 2.0)
diff --git a/README.md b/README.md
index 3df8bfa04c748663267a71f1bf7f871f6211c789..9661a554b9f16452cbbd4f0731b7c66d5adc6db7 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,11 @@
 [![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
 [![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
 [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
 
 ## Canonical source
 
-The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
+The cannonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
 
 ## Open source software to collaborate on code
 
diff --git a/app/assets/javascripts/LabelManager.js b/app/assets/javascripts/LabelManager.js
index 151455ce4a3a9f49c928593e864639e555d497cb..d4a4c7abaa1fb3932036d9320cc01a195a2677f4 100644
--- a/app/assets/javascripts/LabelManager.js
+++ b/app/assets/javascripts/LabelManager.js
@@ -3,6 +3,7 @@
     LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time';
 
     function LabelManager(opts) {
+      // Defaults
       var ref, ref1, ref2;
       if (opts == null) {
         opts = {};
@@ -28,6 +29,7 @@
       $btn = $(e.currentTarget);
       $label = $("#" + ($btn.data('domId')));
       action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
+      // Make sure tooltip will hide
       $tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby')));
       $tooltip.tooltip('destroy');
       return _this.toggleLabelPriority($label, action);
@@ -42,6 +44,7 @@
       url = $label.find('.js-toggle-priority').data('url');
       $target = this.prioritizedLabels;
       $from = this.otherLabels;
+      // Optimistic update
       if (action === 'remove') {
         $target = this.otherLabels;
         $from = this.prioritizedLabels;
@@ -53,6 +56,7 @@
         $target.find('.empty-message').addClass('hidden');
       }
       $label.detach().appendTo($target);
+      // Return if we are not persisting state
       if (!persistState) {
         return;
       }
@@ -61,6 +65,7 @@
           url: url,
           type: 'DELETE'
         });
+        // Restore empty message
         if (!$from.find('li').length) {
           $from.find('.empty-message').removeClass('hidden');
         }
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 84b292e59c643756ceefa00024be7529557b8a68..6df2ecf57a298d823000ccd3cc0013a79af962b0 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -24,6 +24,8 @@
         return callback(group);
       });
     },
+    // Return groups list. Filtered by query
+    // Only active groups retrieved
     groups: function(query, skip_ldap, callback) {
       var url = Api.buildUrl(Api.groupsPath);
       return $.ajax({
@@ -38,6 +40,7 @@
         return callback(groups);
       });
     },
+    // Return namespaces list. Filtered by query
     namespaces: function(query, callback) {
       var url = Api.buildUrl(Api.namespacesPath);
       return $.ajax({
@@ -52,6 +55,7 @@
         return callback(namespaces);
       });
     },
+    // Return projects list. Filtered by query
     projects: function(query, order, callback) {
       var url = Api.buildUrl(Api.projectsPath);
       return $.ajax({
@@ -82,6 +86,7 @@
         return callback(message.responseJSON);
       });
     },
+    // Return group projects list. Filtered by query
     groupProjects: function(group_id, query, callback) {
       var url = Api.buildUrl(Api.groupProjectsPath)
         .replace(':id', group_id);
@@ -97,6 +102,7 @@
         return callback(projects);
       });
     },
+    // Return text for a specific license
     licenseText: function(key, data, callback) {
       var url = Api.buildUrl(Api.licensePath)
         .replace(':key', key);
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 43a679501a77c0b6aa3af292a70067cece3b2320..c029bf3b5ca03b0611635250f3613139c32b0ec7 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -1,3 +1,9 @@
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
 /*= require jquery2 */
 /*= require jquery-ui/autocomplete */
 /*= require jquery-ui/datepicker */
@@ -76,6 +82,7 @@
     }
   };
 
+  // Disable button if text field is empty
   window.disableButtonIfEmptyField = function(field_selector, button_selector) {
     var closest_submit, field;
     field = $(field_selector);
@@ -92,6 +99,7 @@
     });
   };
 
+  // Disable button if any input field with given selector is empty
   window.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
     var closest_submit, updateButtons;
     closest_submit = form.find(button_selector);
@@ -128,6 +136,8 @@
   window.addEventListener("hashchange", shiftWindow);
 
   window.onload = function() {
+    // Scroll the window to avoid the topnav bar
+    // https://github.com/twitter/bootstrap/issues/1768
     if (location.hash) {
       return setTimeout(shiftWindow, 100);
     }
@@ -149,6 +159,8 @@
       return $(this).select().one('mouseup', function(e) {
         return e.preventDefault();
       });
+    // Click a .js-select-on-focus field, select the contents
+    // Prevent a mouseup event from deselecting the input
     });
     $('.remove-row').bind('ajax:success', function() {
       $(this).tooltip('destroy')
@@ -163,6 +175,7 @@
     });
     $('select.select2').select2({
       width: 'resolve',
+      // Initialize select2 selects
       dropdownAutoWidth: true
     });
     $('.js-select2').bind('select2-close', function() {
@@ -170,25 +183,28 @@
         $('.select2-container-active').removeClass('select2-container-active');
         return $(':focus').blur();
       }), 1);
+    // Close select2 on escape
     });
+    // Initialize tooltips
     $body.tooltip({
       selector: '.has-tooltip, [data-toggle="tooltip"]',
       placement: function(_, el) {
-        var $el;
-        $el = $(el);
-        return $el.data('placement') || 'bottom';
+        return $(el).data('placement') || 'bottom';
       }
     });
     $('.trigger-submit').on('change', function() {
       return $(this).parents('form').submit();
+    // Form submitter
     });
     gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
+    // Flash
     if ((flash = $(".flash-container")).length > 0) {
       flash.click(function() {
         return $(this).fadeOut();
       });
       flash.show();
     }
+    // Disable form buttons while a form is submitting
     $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function(e) {
       var buttons;
       buttons = $('[type="submit"]', this);
@@ -209,6 +225,7 @@
       }
     });
     $('.account-box').hover(function() {
+      // Show/Hide the profile menu when hovering the account box
       return $(this).toggleClass('hover');
     });
     $document.on('click', '.diff-content .js-show-suppressed-diff', function() {
@@ -216,6 +233,7 @@
       $container = $(this).parent();
       $container.next('table').show();
       return $container.remove();
+    // Commit show suppressed diff
     });
     $('.navbar-toggle').on('click', function() {
       $('.header-content .title').toggle();
@@ -223,6 +241,7 @@
       $('.header-content .navbar-collapse').toggle();
       return $('.navbar-toggle').toggleClass('active');
     });
+    // Show/hide comments on diff
     $body.on("click", ".js-toggle-diff-comments", function(e) {
       var $this = $(this);
       $this.toggleClass('active');
@@ -232,6 +251,7 @@
       } else {
         notesHolders.hide();
       }
+      $this.trigger('blur');
       return e.preventDefault();
     });
     $document.off("click", '.js-confirm-danger');
@@ -286,42 +306,9 @@
     gl.awardsHandler = new AwardsHandler();
     checkInitialSidebarSize();
     new Aside();
-    if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') {
-      $.cookie('pin_nav', 'false', {
-        path: gon.relative_url_root || '/',
-        expires: 365 * 10
-      });
-      $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
-      $('.navbar-fixed-top').removeClass('header-pinned-nav');
-    }
-    $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
-      var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
-      e.preventDefault();
-      $pinBtn = $(e.currentTarget);
-      $page = $('.page-with-sidebar');
-      $topNav = $('.navbar-fixed-top');
-      $tooltip = $("#" + ($pinBtn.attr('aria-describedby')));
-      doPinNav = !$page.is('.page-sidebar-pinned');
-      tooltipText = 'Pin navigation';
-      $(this).toggleClass('is-active');
-      if (doPinNav) {
-        $page.addClass('page-sidebar-pinned');
-        $topNav.addClass('header-pinned-nav');
-      } else {
-        $tooltip.remove();
-        $page.removeClass('page-sidebar-pinned').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
-        $topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded');
-      }
-      $.cookie('pin_nav', doPinNav, {
-        path: gon.relative_url_root || '/',
-        expires: 365 * 10
-      });
-      if ($.cookie('pin_nav') === 'true' || doPinNav) {
-        tooltipText = 'Unpin navigation';
-      }
-      $tooltip.find('.tooltip-inner').text(tooltipText);
-      return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
-    });
+
+    // bind sidebar events
+    new gl.Sidebar();
 
     // Custom time ago
     gl.utils.shortTimeAgo($('.js-short-timeago'));
diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js
index 7116512d6b7be2d5ba3e6062f8d6cb589572f20e..a9aec6e8ea48950c66ae6bf9ff05e8cb7be218b0 100644
--- a/app/assets/javascripts/autosave.js
+++ b/app/assets/javascripts/autosave.js
@@ -16,7 +16,7 @@
     }
 
     Autosave.prototype.restore = function() {
-      var e, error, text;
+      var e, text;
       if (window.localStorage == null) {
         return;
       }
@@ -41,7 +41,7 @@
       if ((text != null ? text.length : void 0) > 0) {
         try {
           return window.localStorage.setItem(this.key, text);
-        } catch (undefined) {}
+        } catch (error) {}
       } else {
         return this.reset();
       }
@@ -53,7 +53,7 @@
       }
       try {
         return window.localStorage.removeItem(this.key);
-      } catch (undefined) {}
+      } catch (error) {}
     };
 
     return Autosave;
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index ad12cb906e15bd9c0740840aa9cf9fa58ea094b5..0decc6d09e6d9c3a0f50492372e23305efc4b2c0 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -86,6 +86,8 @@
     AwardsHandler.prototype.positionMenu = function($menu, $addBtn) {
       var css, position;
       position = $addBtn.data('position');
+      // The menu could potentially be off-screen or in a hidden overflow element
+      // So we position the element absolute in the body
       css = {
         top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px"
       };
@@ -255,12 +257,12 @@
     };
 
     AwardsHandler.prototype.animateEmoji = function($emoji) {
-      var className;
-      className = 'pulse animated';
+      var className = 'pulse animated once short';
       $emoji.addClass(className);
-      return setTimeout((function() {
-        return $emoji.removeClass(className);
-      }), 321);
+
+      $emoji.on('webkitAnimationEnd animationEnd', function() {
+        $(this).removeClass(className);
+      });
     };
 
     AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) {
@@ -284,6 +286,7 @@
       if (emojiIcon.length > 0) {
         unicodeName = emojiIcon.data('unicode-name');
       } else {
+        // Find by alias
         unicodeName = $(".emoji-menu-content [data-aliases*=':" + emoji + ":']").data('unicode-name');
       }
       return "emoji-" + unicodeName;
@@ -350,8 +353,10 @@
         return function(ev) {
           var found_emojis, h5, term, ul;
           term = $(ev.target).val();
+          // Clean previous search results
           $('ul.emoji-menu-search, h5.emoji-search').remove();
           if (term) {
+            // Generate a search result block
             h5 = $('<h5>').text('Search results');
             found_emojis = _this.searchEmojis(term).show();
             ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index f977a1e8a7b072806b0bd7cdaacadfd24ee94262..dc8ae601961e2434f9523ac63621a6b05eafedc4 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,7 +1,5 @@
 
 /*= require jquery.ba-resize */
-
-
 /*= require autosize */
 
 (function() {
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
index 3631d1b74ac17a6e9da357919cedc1c72f1a8861..1df681a4816bd4b4fb763810566e40566a459b1f 100644
--- a/app/assets/javascripts/behaviors/details_behavior.js
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -5,6 +5,12 @@
       container = $(this).closest(".js-details-container");
       return container.toggleClass("open");
     });
+    // Show details content. Hides link after click.
+    //
+    // %div
+    //   %a.js-details-expand
+    //   %div.js-details-content
+    //
     return $("body").on("click", ".js-details-expand", function(e) {
       $(this).next('.js-details-content').removeClass("hide");
       $(this).hide();
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index 3527d0a95fc584bda085641ce28a822f39640bde..54b7360ab41632b2c6f88cefecee7d4f8fb542ea 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -1,6 +1,20 @@
-
+// Quick Submit behavior
+//
+// When a child field of a form with a `js-quick-submit` class receives a
+// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
+// is submitted.
+//
 /*= require extensions/jquery */
 
+//
+// ### Example Markup
+//
+//   <form action="/foo" class="js-quick-submit">
+//     <input type="text" />
+//     <textarea></textarea>
+//     <input type="submit" value="Submit" />
+//   </form>
+//
 (function() {
   var isMac, keyCodeIs;
 
@@ -17,6 +31,7 @@
 
   $(document).on('keydown.quick_submit', '.js-quick-submit', function(e) {
     var $form, $submit_button;
+    // Enter
     if (!keyCodeIs(e, 13)) {
       return;
     }
@@ -33,8 +48,11 @@
     return $form.submit();
   });
 
+  // If the user tabs to a submit button on a `js-quick-submit` form, display a
+  // tooltip to let them know they could've used the hotkey
   $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function(e) {
     var $this, title;
+    // Tab
     if (!keyCodeIs(e, 9)) {
       return;
     }
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index db0b36b24e9e53f081ed466fc30e6047541fdd84..894034bdd541b4da146a779c2f1e2f2fd10aeb63 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -1,6 +1,18 @@
-
+// Requires Input behavior
+//
+// When called on a form with input fields with the `required` attribute, the
+// form's submit button will be disabled until all required fields have values.
+//
 /*= require extensions/jquery */
 
+//
+// ### Example Markup
+//
+//   <form class="js-requires-input">
+//     <input type="text" required="required">
+//     <input type="submit" value="Submit">
+//   </form>
+//
 (function() {
   $.fn.requiresInput = function() {
     var $button, $form, fieldSelector, requireInput, required;
@@ -11,14 +23,17 @@
     requireInput = function() {
       var values;
       values = _.map($(fieldSelector, $form), function(field) {
+        // Collect the input values of *all* required fields
         return field.value;
       });
+      // Disable the button if any required fields are empty
       if (values.length && _.any(values, _.isEmpty)) {
         return $button.disable();
       } else {
         return $button.enable();
       }
     };
+    // Set initial button state
     requireInput();
     return $form.on('change input', fieldSelector, requireInput);
   };
@@ -27,6 +42,8 @@
     var $form, hideOrShowHelpBlock;
     $form = $('form.js-requires-input');
     $form.requiresInput();
+    // Hide or Show the help block when creating a new project
+    // based on the option selected
     hideOrShowHelpBlock = function(form) {
       var selected;
       selected = $('.js-select-namespace option:selected');
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 5467e3edc69278fb5e8d89b1e2c145df027a0a32..a6ce378d67a4c1197d7f4386c1173cbb8a9e19e3 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,5 +1,12 @@
 (function(w) {
   $(function() {
+    // Toggle button. Show/hide content inside parent container.
+    // Button does not change visibility. If button has icon - it changes chevron style.
+    //
+    // %div.js-toggle-container
+    //   %a.js-toggle-button
+    //   %div.js-toggle-content
+    //
     $('body').on('click', '.js-toggle-button', function(e) {
       e.preventDefault();
       $(this)
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index f4044f22db20f514b2adf7ebbc8dcb50ae08894f..8cca1aa923237ba0db0ff9c648df447ac58823c9 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -8,6 +8,8 @@
         autoDiscover: false,
         autoProcessQueue: false,
         url: form.attr('action'),
+        // Rails uses a hidden input field for PUT
+        // http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
         method: method,
         clickable: true,
         uploadMultiple: false,
@@ -36,6 +38,7 @@
             formData.append('commit_message', form.find('.js-commit-message').val());
           });
         },
+        // Override behavior of adding error underneath preview
         error: function(file, errorMessage) {
           var stripped;
           stripped = $("<div/>").html(errorMessage).text();
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index b0a37ef0e0a4f57b1b76b51ab24c3f63e48bc432..95352164d76a8e63bf384ca28a282561e724c185 100644
--- a/app/assets/javascripts/blob/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -13,6 +13,9 @@
       this.buildDropdown();
       this.bindEvents();
       this.onFilenameUpdate();
+
+      this.autosizeUpdateEvent = document.createEvent('Event');
+      this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
     }
 
     TemplateSelector.prototype.buildDropdown = function() {
@@ -66,9 +69,16 @@
       // be added by all subclasses.
     };
 
+    // To be implemented on the extending class
+    // e.g.
+    // Api.gitignoreText item.name, @requestFileSuccess.bind(@)
     TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
       this.editor.setValue(file.content, 1);
       if (!skipFocus) this.editor.focus();
+
+      if (this.editor instanceof jQuery) {
+        this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
+      }
     };
 
     TemplateSelector.prototype.startLoadingSpinner = function() {
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index 649c79daee8b13a8eeed7a7d055d594ab3f584b8..b846bab04243bf0d325b2a2e958aec05d8b471dd 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -18,6 +18,8 @@
         return function() {
           return $("#file-content").val(_this.editor.getValue());
         };
+      // Before a form submission, move the content from the Ace editor into the
+      // submitted textarea
       })(this));
       this.initModePanesAndLinks();
       new BlobLicenseSelectors({
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index 50fc11d77374f76cdbeaa92f324985ddedecb8ab..474805c143770f801ed99d512c993601971dc5a4 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -34,6 +34,11 @@
       },
       issues () {
         this.$nextTick(() => {
+          if (this.scrollHeight() <= this.listHeight() && this.list.issuesSize > this.list.issues.length) {
+            this.list.page++;
+            this.list.getIssues(false);
+          }
+
           if (this.scrollHeight() > this.listHeight()) {
             this.showCount = true;
           } else {
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
index 1e0148e579817a38b91e81c3428d39d3bdae303b..5fef972517809f72de9fbb0e14067976fbfc9029 100644
--- a/app/assets/javascripts/breakpoints.js
+++ b/app/assets/javascripts/breakpoints.js
@@ -23,6 +23,7 @@
         if ($(allDeviceSelector.join(",")).length) {
           return;
         }
+        // Create all the elements
         els = $.map(BREAKPOINTS, function(breakpoint) {
           return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
         });
@@ -40,6 +41,7 @@
       BreakpointInstance.prototype.getBreakpointSize = function() {
         var $visibleDevice;
         $visibleDevice = this.visibleDevice;
+        // the page refreshed via turbolinks
         if (!$visibleDevice().length) {
           this.setup();
         }
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 4d066f1364686f4bf19df67a4e1087ec5ae60bf8..78d21c0552a639fa3830452b118924503e2fe474 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -16,6 +16,7 @@
       this.toggleSidebar = bind(this.toggleSidebar, this);
       this.updateDropdown = bind(this.updateDropdown, this);
       clearInterval(Build.interval);
+      // Init breakpoint checker
       this.bp = Breakpoints.get();
       $('.js-build-sidebar').niceScroll();
 
@@ -26,10 +27,11 @@
       $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
       $(window).off('resize.build').on('resize.build', this.hideSidebar);
       $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
+      $('#js-build-scroll > a').off('click').on('click', this.stepTrace);
       this.updateArtifactRemoveDate();
       if ($('#build-trace').length) {
         this.getInitialBuildTrace();
-        this.initScrollButtonAffix();
+        this.initScrollButtons();
       }
       if (this.build_status === "running" || this.build_status === "pending") {
         $('#autoscroll-button').on('click', function() {
@@ -42,6 +44,9 @@
             $(this).data("state", "enabled");
             return $(this).text("disable autoscroll");
           }
+        //
+        // Bind autoscroll button to follow build output
+        //
         });
         Build.interval = setInterval((function(_this) {
           return function() {
@@ -49,6 +54,10 @@
               return _this.getBuildTrace();
             }
           };
+        //
+        // Check for new build output if user still watching build page
+        // Only valid for runnig build when output changes during time
+        //
         })(this), 4000);
       }
     }
@@ -98,7 +107,7 @@
       }
     };
 
-    Build.prototype.initScrollButtonAffix = function() {
+    Build.prototype.initScrollButtons = function() {
       var $body, $buildScroll, $buildTrace;
       $buildScroll = $('#js-build-scroll');
       $body = $('body');
@@ -157,6 +166,14 @@
       this.populateJobs(stage);
     };
 
+    Build.prototype.stepTrace = function(e) {
+      e.preventDefault();
+      $currentTarget = $(e.currentTarget);
+      $.scrollTo($currentTarget.attr('href'), {
+        offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
+      });
+    };
+
     return Build;
 
   })();
diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image-file.js
index c0d0b2d049fae7feb9eeb7ce431df914a518a6ac..e893491b19bb47c0504f88b59a95c8fe36c5ef89 100644
--- a/app/assets/javascripts/commit/image-file.js
+++ b/app/assets/javascripts/commit/image-file.js
@@ -2,6 +2,7 @@
   this.ImageFile = (function() {
     var prepareFrames;
 
+    // Width where images must fits in, for 2-up this gets divided by 2
     ImageFile.availWidth = 900;
 
     ImageFile.viewModes = ['two-up', 'swipe'];
@@ -9,6 +10,7 @@
     function ImageFile(file) {
       this.file = file;
       this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
+        // Determine if old and new file has same dimensions, if not show 'two-up' view
         return function(deletedWidth, deletedHeight) {
           return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
             if (width === deletedWidth && height === deletedHeight) {
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 37f168c51902ba2b5b01efb0614815e5fdd3a166..9132089adcdde55f080e64cc69c3992ac627cdbc 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -45,6 +45,7 @@
           CommitsList.content.html(data.html);
           return history.replaceState({
             page: commitsUrl
+          // Change url so if user reload a page - search results are saved
           }, document.title, commitsUrl);
         },
         dataType: "json"
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
index c43af17442b46be1cdd0c041d486d6ac6bdff599..3e20db7e3081a8f04df95287f517de2899e51a27 100644
--- a/app/assets/javascripts/copy_to_clipboard.js
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -6,14 +6,19 @@
 
   genericSuccess = function(e) {
     showTooltip(e.trigger, 'Copied!');
+    // Clear the selection and blur the trigger so it loses its border
     e.clearSelection();
     return $(e.trigger).blur();
   };
 
+  // Safari doesn't support `execCommand`, so instead we inform the user to
+  // copy manually.
+  //
+  // See http://clipboardjs.com/#browser-support
   genericError = function(e) {
     var key;
     if (/Mac/i.test(navigator.userAgent)) {
-      key = '&#8984;';
+      key = '&#8984;'; // Command
     } else {
       key = 'Ctrl';
     }
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 3dd7ceba92faaa61683d2304f521d12274f1b207..c8634b78f2bcd1ac96fb323896f938f28197b974 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -39,6 +39,9 @@
             bottom: unfoldBottom,
             offset: offset,
             unfold: unfold,
+            // indent is used to compensate for single space indent to fit
+            // '+' and '-' prepended to diff lines,
+            // see https://gitlab.com/gitlab-org/gitlab-ce/issues/707
             indent: 1,
             view: file.data('view')
           };
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 6e620e9976c1c52d890b20795c7c168c152e168f..ddf11ecf34c74a39a81e7e95e6a28cd45fa81393 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -23,6 +23,7 @@
         case 'projects:boards:show':
           shortcut_handler = new ShortcutsNavigation();
           break;
+        case 'projects:merge_requests:index':
         case 'projects:issues:index':
           Issuable.init();
           new IssuableBulkActions();
@@ -92,7 +93,8 @@
           new MergedButtons();
           break;
         case "projects:merge_requests:conflicts":
-          new MergeConflictResolver()
+          window.mcui = new MergeConflictResolver()
+          break;
         case 'projects:merge_requests:index':
           shortcut_handler = new ShortcutsNavigation();
           Issuable.init();
@@ -167,6 +169,8 @@
           }
           break;
         case 'projects:network:show':
+          // Ensure we don't create a particular shortcut handler here. This is
+          // already created, where the network graph is created.
           shortcut_handler = true;
           break;
         case 'projects:forks:new':
@@ -266,12 +270,14 @@
               shortcut_handler = new ShortcutsNavigation();
           }
       }
+      // If we haven't installed a custom shortcut handler, install the default one
       if (!shortcut_handler) {
         return new Shortcuts();
       }
     };
 
     Dispatcher.prototype.initSearch = function() {
+      // Only when search form is present
       if ($('.search').length) {
         return new SearchAutocomplete();
       }
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index 5a725a41fd123f9d4084136f621dde967c8d4b4a..bf68b7e3a9b757a3d51293645b906c6d5960a1df 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -2,6 +2,7 @@
   this.DueDateSelect = (function() {
     function DueDateSelect() {
       var $datePicker, $dueDate, $loading;
+      // Milestone edit/new form
       $datePicker = $('.datepicker');
       if ($datePicker.length) {
         $dueDate = $('#milestone_due_date');
@@ -16,6 +17,7 @@
         e.preventDefault();
         return $.datepicker._clearDate($datePicker);
       });
+      // Issuable sidebar
       $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
       $('.js-due-date-select').each(function(i, dropdown) {
         var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL;
@@ -38,6 +40,7 @@
         });
         addDueDate = function(isDropdown) {
           var data, date, mediumDate, value;
+          // Create the post date
           value = $("input[name='" + fieldName + "']").val();
           if (value !== '') {
             date = new Date(value.replace(new RegExp('-', 'g'), ','));
diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js
index ae3dde63da382bfb6cb24695daeb34579f1c5c2f..4978e24949c2624b3d9d082b9e62a8598166c4eb 100644
--- a/app/assets/javascripts/extensions/jquery.js
+++ b/app/assets/javascripts/extensions/jquery.js
@@ -1,3 +1,4 @@
+// Disable an element and add the 'disabled' Bootstrap class
 (function() {
   $.fn.extend({
     disable: function() {
@@ -5,6 +6,7 @@
     }
   });
 
+  // Enable an element and remove the 'disabled' Bootstrap class
   $.fn.extend({
     enable: function() {
       return $(this).removeAttr('disabled').removeClass('disabled');
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 3dca06d36b1f9115f2352d515d7b4ff50301c001..d0786bf00531431ee6889c42c875a239aba2734b 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -1,3 +1,4 @@
+// Creates the variables for setting up GFM auto-completion
 (function() {
   if (window.GitLab == null) {
     window.GitLab = {};
@@ -8,18 +9,22 @@
     dataLoaded: false,
     cachedData: {},
     dataSource: '',
+    // Emoji
     Emoji: {
       template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
     },
+    // Team Members
     Members: {
       template: '<li>${username} <small>${title}</small></li>'
     },
     Labels: {
       template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
     },
+    // Issues and MergeRequests
     Issues: {
       template: '<li><small>${id}</small> ${title}</li>'
     },
+    // Milestones
     Milestones: {
       template: '<li>${title}</li>'
     },
@@ -48,8 +53,11 @@
       }
     },
     setup: function(input) {
+      // Add GFM auto-completion to all input fields, that accept GFM input.
       this.input = input || $('.js-gfm-input');
+      // destroy previous instances
       this.destroyAtWho();
+      // set up instances
       this.setupAtWho();
       if (this.dataSource) {
         if (!this.dataLoading && !this.cachedData) {
@@ -63,6 +71,11 @@
                 return _this.loadData(data);
               });
             };
+          // We should wait until initializations are done
+          // and only trigger the last .setup since
+          // The previous .dataSource belongs to the previous issuable
+          // and the last one will have the **proper** .dataSource property
+          // TODO: Make this a singleton and turn off events when moving to another page
           })(this), 1000);
         }
         if (this.cachedData != null) {
@@ -71,6 +84,7 @@
       }
     },
     setupAtWho: function() {
+      // Emoji
       this.input.atwho({
         at: ':',
         displayTpl: (function(_this) {
@@ -90,6 +104,7 @@
           beforeInsert: this.DefaultOptions.beforeInsert
         }
       });
+      // Team Members
       this.input.atwho({
         at: '@',
         displayTpl: (function(_this) {
@@ -321,13 +336,22 @@
     loadData: function(data) {
       this.cachedData = data;
       this.dataLoaded = true;
+      // load members
       this.input.atwho('load', '@', data.members);
+      // load issues
       this.input.atwho('load', 'issues', data.issues);
+      // load milestones
       this.input.atwho('load', 'milestones', data.milestones);
+      // load merge requests
       this.input.atwho('load', 'mergerequests', data.mergerequests);
+      // load emojis
       this.input.atwho('load', ':', data.emojis);
+      // load labels
       this.input.atwho('load', '~', data.labels);
+      // load commands
       this.input.atwho('load', '/', data.commands);
+      // This trigger at.js again
+      // otherwise we would be stuck with loading until the user types
       return $(':focus').trigger('keyup');
     }
   };
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 77b2082cba07e6fcead94cab49b169cdbf840e89..c05cda25bbd9960a2a328d98c869e8d71020659f 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -21,12 +21,14 @@
       $clearButton = $inputContainer.find('.js-dropdown-input-clear');
       this.indeterminateIds = [];
       $clearButton.on('click', (function(_this) {
+        // Clear click
         return function(e) {
           e.preventDefault();
           e.stopPropagation();
           return _this.input.val('').trigger('keyup').focus();
         };
       })(this));
+      // Key events
       timeout = "";
       this.input
         .on('keydown', function (e) {
@@ -49,6 +51,7 @@
           if (keyCode === 13 && !options.elIsInput) {
             return false;
           }
+          // Only filter asynchronously only if option remote is set
           if (this.options.remote) {
             clearTimeout(timeout);
             return timeout = setTimeout(function() {
@@ -79,11 +82,27 @@
       if ((data != null) && !this.options.filterByText) {
         results = data;
         if (search_text !== '') {
+          // When data is an array of objects therefore [object Array] e.g.
+          // [
+          //   { prop: 'foo' },
+          //   { prop: 'baz' }
+          // ]
           if (_.isArray(data)) {
             results = fuzzaldrinPlus.filter(data, search_text, {
               key: this.options.keys
             });
           } else {
+            // If data is grouped therefore an [object Object]. e.g.
+            // {
+            //   groupName1: [
+            //     { prop: 'foo' },
+            //     { prop: 'baz' }
+            //   ],
+            //   groupName2: [
+            //     { prop: 'abc' },
+            //     { prop: 'def' }
+            //   ]
+            // }
             if (gl.utils.isObject(data)) {
               results = {};
               for (key in data) {
@@ -140,6 +159,7 @@
           this.options.beforeSend();
         }
         return this.dataEndpoint("", (function(_this) {
+          // Fetch the data by calling the data funcfion
           return function(data) {
             if (_this.options.success) {
               _this.options.success(data);
@@ -171,6 +191,7 @@
           };
         })(this)
       });
+    // Fetch the data through ajax if the data is a string
     };
 
     return GitLabDropdownRemote;
@@ -209,13 +230,18 @@
       self = this;
       selector = $(this.el).data("target");
       this.dropdown = selector != null ? $(selector) : $(this.el).parent();
+      // Set Defaults
       ref = this.options, this.filterInput = (ref1 = ref.filterInput) != null ? ref1 : this.getElement(FILTER_INPUT), this.highlight = (ref2 = ref.highlight) != null ? ref2 : false, this.filterInputBlur = (ref3 = ref.filterInputBlur) != null ? ref3 : true;
+      // If no input is passed create a default one
       self = this;
+      // If selector was passed
       if (_.isString(this.filterInput)) {
         this.filterInput = this.getElement(this.filterInput);
       }
       searchFields = this.options.search ? this.options.search.fields : [];
       if (this.options.data) {
+        // If we provided data
+        // data could be an array of objects or a group of arrays
         if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
           this.fullData = this.options.data;
           currentIndex = -1;
@@ -232,10 +258,12 @@
                   return _this.filter.input.trigger('keyup');
                 }
               };
+            // Remote data
             })(this)
           });
         }
       }
+      // Init filterable
       if (this.options.filterable) {
         this.filter = new GitLabDropdownFilter(this.filterInput, {
           elIsInput: $(this.el).is('input'),
@@ -278,12 +306,14 @@
           })(this)
         });
       }
+      // Event listeners
       this.dropdown.on("shown.bs.dropdown", this.opened);
       this.dropdown.on("hidden.bs.dropdown", this.hidden);
       $(this.el).on("update.label", this.updateLabel);
       this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate);
       this.dropdown.on('keyup', (function(_this) {
         return function(e) {
+          // Escape key
           if (e.which === 27) {
             return $('.dropdown-menu-close', _this.dropdown).trigger('click');
           }
@@ -322,11 +352,18 @@
           if (self.options.clicked) {
             self.options.clicked(selected, $el, e);
           }
-          return $el.trigger('blur');
+
+          // Update label right after all modifications in dropdown has been done
+          if (self.options.toggleLabel) {
+            self.updateLabel(selected, $el, self);
+          }
+
+          $el.trigger('blur');
         });
       }
     }
 
+    // Finds an element inside wrapper element
     GitLabDropdown.prototype.getElement = function(selector) {
       return this.dropdown.find(selector);
     };
@@ -344,6 +381,7 @@
         }
       }
       menu.toggleClass(PAGE_TWO_CLASS);
+      // Focus first visible input on active page
       return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus();
     };
 
@@ -351,23 +389,28 @@
       var full_html, groupData, html, name;
       this.renderedData = data;
       if (this.options.filterable && data.length === 0) {
+        // render no matching results
         html = [this.noResults()];
       } else {
+        // Handle array groups
         if (gl.utils.isObject(data)) {
           html = [];
           for (name in data) {
             groupData = data[name];
             html.push(this.renderItem({
               header: name
+            // Add header for each group
             }, name));
             this.renderData(groupData, name).map(function(item) {
               return html.push(item);
             });
           }
         } else {
+          // Render each row
           html = this.renderData(data);
         }
       }
+      // Render the full menu
       full_html = this.renderMenu(html);
       return this.appendMenu(full_html);
     };
@@ -406,6 +449,7 @@
       if (this.options.setActiveIds) {
         this.options.setActiveIds.call(this);
       }
+      // Makes indeterminate items effective
       if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
         this.parseData(this.fullData);
       }
@@ -427,6 +471,8 @@
       if (this.options.filterable) {
         $input.blur().val("");
       }
+      // Triggering 'keyup' will re-render the dropdown which is not always required
+      // specially if we want to keep the state of the dropdown needed for bulk-assignment
       if (!this.options.persistWhenHide) {
         $input.trigger("keyup");
       }
@@ -439,6 +485,7 @@
       return this.dropdown.trigger('hidden.gl.dropdown');
     };
 
+    // Render the full menu
     GitLabDropdown.prototype.renderMenu = function(html) {
       var menu_html;
       menu_html = "";
@@ -450,6 +497,7 @@
       return menu_html;
     };
 
+    // Append the menu into the dropdown
     GitLabDropdown.prototype.appendMenu = function(html) {
       var selector;
       selector = '.dropdown-content';
@@ -465,35 +513,42 @@
         group = false;
       }
       if (index == null) {
+        // Render the row
         index = false;
       }
       html = "";
+      // Divider
       if (data === "divider") {
         return "<li class='divider'></li>";
       }
+      // Separator is a full-width divider
       if (data === "separator") {
         return "<li class='separator'></li>";
       }
+      // Header
       if (data.header != null) {
         return _.template('<li class="dropdown-header"><%- header %></li>')({ header: data.header });
       }
       if (this.options.renderRow) {
+        // Call the render function
         html = this.options.renderRow.call(this.options, data, this);
       } else {
         if (!selected) {
           value = this.options.id ? this.options.id(data) : data.id;
-          fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName;
+          fieldName = this.options.fieldName;
 
           field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
           if (field.length) {
             selected = true;
           }
         }
+        // Set URL
         if (this.options.url != null) {
           url = this.options.url(data);
         } else {
           url = data.url != null ? data.url : '#';
         }
+        // Set Text
         if (this.options.text != null) {
           text = this.options.text(data);
         } else {
@@ -540,6 +595,7 @@
 
     GitLabDropdown.prototype.rowClicked = function(el) {
       var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value;
+      fieldName = this.options.fieldName;
       isInput = $(this.el).is('input');
       if (this.renderedData) {
         groupName = el.data('group');
@@ -551,14 +607,15 @@
           selectedObject = this.renderedData[selectedIndex];
         }
       }
+      field = [];
       fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName;
       value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
       if (isInput) {
         field = $(this.el);
-      } else {
-        field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(value) + "']");
+      } else if(value) {
+        field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
       }
-      if (el.hasClass(ACTIVE_CLASS)) {
+      if (field.length && el.hasClass(ACTIVE_CLASS)) {
         el.removeClass(ACTIVE_CLASS);
         if (isInput) {
           field.val('');
@@ -568,7 +625,7 @@
       } else if (el.hasClass(INDETERMINATE_CLASS)) {
         el.addClass(ACTIVE_CLASS);
         el.removeClass(INDETERMINATE_CLASS);
-        if (value == null) {
+        if (field.length && value == null) {
           field.remove();
         }
         if (!field.length && fieldName) {
@@ -581,36 +638,30 @@
             this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
           }
         }
-        if (value == null) {
+        if (field.length && value == null) {
           field.remove();
         }
+        // Toggle active class for the tick mark
         el.addClass(ACTIVE_CLASS);
         if (value != null) {
           if (!field.length && fieldName) {
             this.addInput(fieldName, value, selectedObject);
-          } else {
+          } else if (field.length) {
             field.val(value).trigger('change');
           }
         }
       }
 
-      // Update label right after input has been added
-      if (this.options.toggleLabel) {
-        this.updateLabel(selectedObject, el, this);
-      }
-
       return selectedObject;
     };
 
     GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
       var $input;
+      // Create hidden input for form
       $input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
       if (this.options.inputId != null) {
         $input.attr('id', this.options.inputId);
       }
-      if (selectedObject && selectedObject.type) {
-        $input.attr('data-type', selectedObject.type);
-      }
       return this.dropdown.before($input);
     };
 
@@ -625,6 +676,7 @@
       if (this.dropdown.find(".dropdown-toggle-page").length) {
         selector = ".dropdown-page-one " + selector;
       }
+      // simulate a click on the first link
       $el = $(selector, this.dropdown);
       if ($el.length) {
         var href = $el.attr('href');
@@ -653,11 +705,15 @@
             e.stopImmediatePropagation();
             PREV_INDEX = currentIndex;
             $listItems = $(selector, _this.dropdown);
+            // if @options.filterable
+            //   $input.blur()
             if (currentKeyCode === 40) {
+              // Move down
               if (currentIndex < ($listItems.length - 1)) {
                 currentIndex += 1;
               }
             } else if (currentKeyCode === 38) {
+              // Move up
               if (currentIndex > 0) {
                 currentIndex -= 1;
               }
@@ -685,24 +741,32 @@
 
     GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
       var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
+      // Remove the class for the previously focused row
       $('.is-focused', this.dropdown).removeClass('is-focused');
+      // Update the class for the row at the specific index
       $listItem = $listItems.eq(index);
       $listItem.find('a:first-child').addClass("is-focused");
+      // Dropdown content scroll area
       $dropdownContent = $listItem.closest('.dropdown-content');
       dropdownScrollTop = $dropdownContent.scrollTop();
       dropdownContentHeight = $dropdownContent.outerHeight();
       dropdownContentTop = $dropdownContent.prop('offsetTop');
       dropdownContentBottom = dropdownContentTop + dropdownContentHeight;
+      // Get the offset bottom of the list item
       listItemHeight = $listItem.outerHeight();
       listItemTop = $listItem.prop('offsetTop');
       listItemBottom = listItemTop + listItemHeight;
       if (!index) {
+        // Scroll the dropdown content to the top
         $dropdownContent.scrollTop(0)
       } else if (index === ($listItems.length - 1)) {
+        // Scroll the dropdown content to the bottom
         $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight'));
       } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) {
+        // Scroll the dropdown content down
         $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING);
       } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) {
+        // Scroll the dropdown content up
         return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING);
       }
     };
@@ -732,4 +796,4 @@
     });
   };
 
-}).call(this);
+}).call(this);
\ No newline at end of file
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 528a673eb15c07a341806fd35d1ca9452a953b52..2703adc07052cb2da0eee652fd6289f9e834126f 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -3,12 +3,15 @@
     function GLForm(form) {
       this.form = form;
       this.textarea = this.form.find('textarea.js-gfm-input');
+      // Before we start, we should clean up any previous data for this form
       this.destroy();
+      // Setup the form
       this.setupForm();
       this.form.data('gl-form', this);
     }
 
     GLForm.prototype.destroy = function() {
+      // Clean form listeners
       this.clearEventListeners();
       return this.form.data('gl-form', null);
     };
@@ -21,12 +24,15 @@
         this.form.find('.div-dropzone').remove();
         this.form.addClass('gfm-form');
         disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
+        // remove notify commit author checkbox for non-commit notes
         GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
         new DropzoneInput(this.form);
         autosize(this.textarea);
+        // form and textarea event listeners
         this.addEventListeners();
         gl.text.init(this.form);
       }
+      // hide discard button
       this.form.find('.js-note-discard').hide();
       return this.form.show();
     };
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js
index b95faadc8e72f17e7cdb90eab9203622916bee02..4886da9f21fa479cfd1d59adfce3ead9924b6d2c 100644
--- a/app/assets/javascripts/graphs/graphs_bundle.js
+++ b/app/assets/javascripts/graphs/graphs_bundle.js
@@ -1,7 +1,11 @@
-
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
 /*= require_tree . */
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
index a646ca1d84f00fb45ee004bd060adc960c9c7be8..7d9d4d7c679c17a17abd38f407d1ce8b1c166175 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -204,6 +204,7 @@
 
     function ContributorsAuthorGraph(data1) {
       this.data = data1;
+      // Don't split graph size in half for mobile devices.
       if ($(window).width() < 768) {
         this.width = $('.content').width() - 80;
       } else {
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index fd5b6dc0ddd405ff5ee3a9076ec9cb5b95de803f..7c2eebcdd44e8b674964cc20cfa5f96d37fc4376 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -38,6 +38,7 @@
               return _this.formatSelection.apply(_this, args);
             },
             dropdownCssClass: "ajax-groups-dropdown",
+            // we do not want to escape markup since we are displaying html in results
             escapeMarkup: function(m) {
               return m;
             }
diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js.es6
similarity index 75%
rename from app/assets/javascripts/issuable.js
rename to app/assets/javascripts/issuable.js.es6
index d0305c6c6a1194e60452e393967c890c9825a0b2..53faaa38a0cf99ba1af5bfbd8dcd397947dd1147 100644
--- a/app/assets/javascripts/issuable.js
+++ b/app/assets/javascripts/issuable.js.es6
@@ -8,6 +8,7 @@
       Issuable.initTemplates();
       Issuable.initSearch();
       Issuable.initChecks();
+      Issuable.initResetFilters();
       return Issuable.initLabelFilterRemove();
     },
     initTemplates: function() {
@@ -37,9 +38,11 @@
       return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) {
         var $button;
         $button = $(this);
+        // Remove the label input box
         $('input[name="label_name[]"]').filter(function() {
           return this.value === $button.data('label');
         }).remove();
+        // Submit the form to get new data
         Issuable.filterResults($('.filter-form'));
         return $('.js-label-select').trigger('update.label');
       });
@@ -55,6 +58,17 @@
         return Turbolinks.visit(issuesUrl);
       };
     })(this),
+    initResetFilters: function() {
+      $('.reset-filters').on('click', function(e) {
+        e.preventDefault();
+        const target = e.target;
+        const $form = $(target).parents('.js-filter-form');
+        const baseIssuesUrl = target.href;
+
+        $form.attr('action', baseIssuesUrl);
+        Turbolinks.visit(baseIssuesUrl);
+      });
+    },
     initChecks: function() {
       this.issuableBulkActions = $('.bulk-update').data('bulkActions');
       $('.check_all_issues').off('click').on('click', function() {
@@ -64,19 +78,22 @@
       return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this));
     },
     checkChanged: function() {
-      var checked_issues, ids;
-      checked_issues = $('.selected_issue:checked');
-      if (checked_issues.length > 0) {
-        ids = $.map(checked_issues, function(value) {
+      const $checkedIssues = $('.selected_issue:checked');
+      const $updateIssuesIds = $('#update_issuable_ids');
+      const $issuesOtherFilters = $('.issues-other-filters');
+      const $issuesBulkUpdate = $('.issues_bulk_update');
+
+      if ($checkedIssues.length > 0) {
+        let ids = $.map($checkedIssues, function(value) {
           return $(value).data('id');
         });
-        $('#update_issues_ids').val(ids);
-        $('.issues-other-filters').hide();
-        $('.issues_bulk_update').show();
+        $updateIssuesIds.val(ids);
+        $issuesOtherFilters.hide();
+        $issuesBulkUpdate.show();
       } else {
-        $('#update_issues_ids').val([]);
-        $('.issues_bulk_update').hide();
-        $('.issues-other-filters').show();
+        $updateIssuesIds.val([]);
+        $issuesBulkUpdate.hide();
+        $issuesOtherFilters.show();
         this.issuableBulkActions.willUpdateLabels = false;
       }
       return true;
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index e6422602ce811df735af174dd53a6a009268ffcf..261bf6137c2a326620020e8d1c92ade6db57eccd 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,10 +1,6 @@
 
 /*= require flash */
-
-
 /*= require jquery.waitforimages */
-
-
 /*= require task_list */
 
 (function() {
@@ -13,6 +9,7 @@
   this.Issue = (function() {
     function Issue() {
       this.submitNoteForm = bind(this.submitNoteForm, this);
+      // Prevent duplicate event bindings
       this.disableTaskList();
       if ($('a.btn-close').length) {
         this.initTaskList();
@@ -99,6 +96,8 @@
         url: $('form.js-issuable-update').attr('action'),
         data: patchData
       });
+    // TODO (rspeicher): Make the issue description inline-editable like a note so
+    // that we can re-use its form here
     };
 
     Issue.prototype.initMergeRequests = function() {
@@ -128,6 +127,8 @@
     Issue.prototype.initCanCreateBranch = function() {
       var $container;
       $container = $('#new-branch');
+      // If the user doesn't have the required permissions the container isn't
+      // rendered at all.
       if ($container.length === 0) {
         return;
       }
diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js
index 98d3358ba921da1fd57dae38f22a1cddd6e65143..62a7fc9a06c6d0b2d546ef84450dab7c2957f469 100644
--- a/app/assets/javascripts/issues-bulk-assignment.js
+++ b/app/assets/javascripts/issues-bulk-assignment.js
@@ -1,14 +1,17 @@
 (function() {
   this.IssuableBulkActions = (function() {
     function IssuableBulkActions(opts) {
+      // Set defaults
       var ref, ref1, ref2;
       if (opts == null) {
         opts = {};
       }
-      this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issues-list .issue');
+      this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li');
+      // Save instance
       this.form.data('bulkActions', this);
       this.willUpdateLabels = false;
       this.bindEvents();
+      // Fixes bulk-assign not working when navigating through pages
       Issuable.initChecks();
     }
 
@@ -86,6 +89,7 @@
       ref1 = this.getLabelsFromSelection();
       for (j = 0, len1 = ref1.length; j < len1; j++) {
         id = ref1[j];
+        // Only the ones that we are not going to keep
         if (labelsToKeep.indexOf(id) === -1) {
           result.push(id);
         }
@@ -106,7 +110,7 @@
           state_event: this.form.find('input[name="update[state_event]"]').val(),
           assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
           milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
-          issues_ids: this.form.find('input[name="update[issues_ids]"]').val(),
+          issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
           subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
           add_label_ids: [],
           remove_label_ids: []
@@ -147,6 +151,8 @@
       indeterminatedLabels = this.getUnmarkedIndeterminedLabels();
       labelsToApply = this.getLabelsToApply();
       indeterminatedLabels.map(function(id) {
+        // We need to exclude label IDs that will be applied
+        // By not doing this will cause issues from selection to not add labels at all
         if (labelsToApply.indexOf(id) === -1) {
           return result.push(id);
         }
diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js
index fe071fca67ca4396c312976be004e4e87cd875a5..cb16e2ba81444c03dac6bae555150518da78d5ae 100644
--- a/app/assets/javascripts/labels.js
+++ b/app/assets/javascripts/labels.js
@@ -26,13 +26,16 @@
       var previewColor;
       previewColor = $('input#label_color').val();
       return $('div.label-color-preview').css('background-color', previewColor);
+    // Updates the the preview color with the hex-color input
     };
 
+    // Updates the preview color with a click on a suggested color
     Labels.prototype.setSuggestedColor = function(e) {
       var color;
       color = $(e.currentTarget).data('color');
       $('input#label_color').val(color);
       this.updateColorPreview();
+      // Notify the form, that color has changed
       $('.label-form').trigger('keyup');
       return e.preventDefault();
     };
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index bab23ff5ac04ffbc32d5093f0584b8eaa48913a7..3f15a117ca887bfdb2e250cfb2dfd081a7759d51 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -156,15 +156,17 @@
                 selectedClass.push('is-indeterminate');
               }
               if (active.indexOf(label.id) !== -1) {
+                // Remove is-indeterminate class if the item will be marked as active
                 i = selectedClass.indexOf('is-indeterminate');
                 if (i !== -1) {
                   selectedClass.splice(i, 1);
                 }
                 selectedClass.push('is-active');
+                // Add input manually
                 instance.addInput(this.fieldName, label.id);
               }
             }
-            if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + escape(this.id(label)) + "']").length) {
+            if (this.id(label) && $form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + this.id(label).toString().replace(/'/g, '\\\'') + "']").length) {
               selectedClass.push('is-active');
             }
             if ($dropdown.hasClass('js-multiselect') && removesAll) {
@@ -172,6 +174,7 @@
             }
             if (label.duplicate) {
               spacing = 100 / label.color.length;
+              // Reduce the colors to 4
               label.color = label.color.filter(function(color, i) {
                 return i < 4;
               });
@@ -192,11 +195,13 @@
             } else {
               colorEl = '';
             }
+            // We need to identify which items are actually labels
             if (label.id) {
               selectedClass.push('label-item');
               $a.attr('data-label-id', label.id);
             }
             $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title);
+            // Return generated html
             return $li.html($a).prop('outerHTML');
           },
           persistWhenHide: $dropdown.data('persistWhenHide'),
@@ -238,6 +243,7 @@
             isIssueIndex = page === 'projects:issues:index';
             isMRIndex = page === 'projects:merge_requests:index';
             $selectbox.hide();
+            // display:block overrides the hide-collapse rule
             $value.removeAttr('style');
             if (page === 'projects:boards:show') {
               return;
@@ -255,6 +261,7 @@
               }
             }
             if ($dropdown.hasClass('js-filter-bulk-update')) {
+              // If we are persisting state we need the classes
               if (!this.options.persistWhenHide) {
                 return $dropdown.parent().find('.is-active, .is-indeterminate').removeClass();
               }
@@ -324,7 +331,9 @@
       if ($('.selected_issue:checked').length) {
         return;
       }
+      // Remove inputs
       $('.issues_bulk_update .labels-filter input[type="hidden"]').remove();
+      // Also restore button text
       return $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label');
     };
 
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index ce472f3bcd0aac3d6e411a4e637295610dc2a1a2..8e2fc0d147981426a248f581879d0a4dc1864981 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -10,11 +10,13 @@
   };
 
   $(function() {
-    hideEndFade($('.scrolling-tabs'));
+    var $scrollingTabs = $('.scrolling-tabs');
+
+    hideEndFade($scrollingTabs);
     $(window).off('resize.nav').on('resize.nav', function() {
-      return hideEndFade($('.scrolling-tabs'));
+      return hideEndFade($scrollingTabs);
     });
-    return $('.scrolling-tabs').on('scroll', function(event) {
+    $scrollingTabs.off('scroll').on('scroll', function(event) {
       var $this, currentPosition, maxPosition;
       $this = $(this);
       currentPosition = $this.scrollLeft();
@@ -22,6 +24,23 @@
       $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
       return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
     });
+
+    $scrollingTabs.each(function () {
+      var $this = $(this),
+          scrollingTabWidth = $this.width(),
+          $active = $this.find('.active'),
+          activeWidth = $active.width();
+
+      if ($active.length) {
+        var offset = $active.offset().left + activeWidth;
+
+        if (offset > scrollingTabWidth - 30) {
+          var scrollLeft = scrollingTabWidth / 2;
+          scrollLeft = (offset - scrollLeft) - (activeWidth / 2);
+          $this.scrollLeft(scrollLeft);
+        }
+      }
+    });
   });
 
 }).call(this);
diff --git a/app/assets/javascripts/lib/chart.js b/app/assets/javascripts/lib/chart.js
index 8d5e52286b7be5206cb7936ae77d5019d113b5c2..d9b07c10a49bcec7b32b5f5778c48532204c6561 100644
--- a/app/assets/javascripts/lib/chart.js
+++ b/app/assets/javascripts/lib/chart.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/lib/cropper.js b/app/assets/javascripts/lib/cropper.js
index 8ee81804513b831003744d9a772cd7849e029dc9..a88e640f298ceb424f38adda00b2e95c3615096e 100644
--- a/app/assets/javascripts/lib/cropper.js
+++ b/app/assets/javascripts/lib/cropper.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/lib/d3.js b/app/assets/javascripts/lib/d3.js
index 31e6033e75666a5a8f8844f9ebbbb0e6cdde88a1..ee1baf5480394633a30f3c419395643bdc02f353 100644
--- a/app/assets/javascripts/lib/d3.js
+++ b/app/assets/javascripts/lib/d3.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/lib/raphael.js b/app/assets/javascripts/lib/raphael.js
index 923c575dcfe637946f7344a714029775ad98f9d5..6df427bc2b1b31dc2cfa3409e601ed5c1b497210 100644
--- a/app/assets/javascripts/lib/raphael.js
+++ b/app/assets/javascripts/lib/raphael.js
@@ -1,13 +1,8 @@
 
 /*= require raphael */
-
-
 /*= require g.raphael */
-
-
 /*= require g.bar */
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index d4d5927d3b03e7abfed017f1bdb941210f7f2d24..8fdf4646cd8e7ba8e24db50cc959ae6986e8807d 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -29,6 +29,7 @@
       if (setTimeago) {
         $timeagoEls.timeago();
         $timeagoEls.tooltip('destroy');
+        // Recreate with custom template
         return $timeagoEls.tooltip({
           template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
         });
diff --git a/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb b/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb
deleted file mode 100644
index 80f9936b9c2039ee8e1625128ad862e26d8847e0..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-gl.emojiAliases = ->
-  JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>')
diff --git a/app/assets/javascripts/lib/utils/emoji_aliases.js.erb b/app/assets/javascripts/lib/utils/emoji_aliases.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..aeb86c9fa5bc7758a85242bb7fd2242a6ac02b26
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/emoji_aliases.js.erb
@@ -0,0 +1,6 @@
+(function() {
+  gl.emojiAliases = function() {
+    return JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>');
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index 42b6ac0589ed3ac8361dee2a34be4e80f62b3516..5b338b00d76a44f00796289954b88a2a840f3999 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -6,6 +6,7 @@
       notification = new Notification(message, opts);
       setTimeout(function() {
         return notification.close();
+      // Hide the notification after X amount of seconds
       }, 8000);
       if (onclick) {
         return notification.onclick = onclick;
@@ -22,12 +23,16 @@
         body: body,
         icon: icon
       };
+      // Let's check if the browser supports notifications
       if (!('Notification' in window)) {
 
+      // do nothing
       } else if (Notification.permission === 'granted') {
+        // If it's okay let's create a notification
         return notificationGranted(message, opts, onclick);
       } else if (Notification.permission !== 'denied') {
         return Notification.requestPermission(function(permission) {
+          // If the user accepts, let's create a notification
           if (permission === 'granted') {
             return notificationGranted(message, opts, onclick);
           }
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index b6636de57670329772e56601c6ef9d73a916e18d..d761a844be96ae3d9e9a10224a86d5d9b58f8450 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -29,6 +29,7 @@
       lineBefore = this.lineBefore(text, textArea);
       lineAfter = this.lineAfter(text, textArea);
       if (lineBefore === blockTag && lineAfter === blockTag) {
+        // To remove the block tag we have to select the line before & after
         if (blockTag != null) {
           textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
           textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
@@ -63,11 +64,11 @@
       if (!inserted) {
         try {
           document.execCommand("ms-beginUndoUnit");
-        } catch (undefined) {}
+        } catch (error) {}
         textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
         try {
           document.execCommand("ms-endUndoUnit");
-        } catch (undefined) {}
+        } catch (error) {}
       }
       return this.moveCursor(textArea, tag, wrap);
     };
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 533310cc87c22d88a863663d13698f1a64c1ef42..f84a20cf0feb884f03c76a2e7804d5803e6593c8 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -7,6 +7,8 @@
     if ((base = w.gl).utils == null) {
       base.utils = {};
     }
+    // Returns an array containing the value(s) of the
+    // of the key passed as an argument
     w.gl.utils.getParameterValues = function(sParam) {
       var i, sPageURL, sParameterName, sURLVariables, values;
       sPageURL = decodeURIComponent(window.location.search.substring(1));
@@ -23,6 +25,8 @@
       }
       return values;
     };
+    // @param {Object} params - url keys and value to merge
+    // @param {String} url
     w.gl.utils.mergeUrlParams = function(params, url) {
       var lastChar, newUrl, paramName, paramValue, pattern;
       newUrl = decodeURIComponent(url);
@@ -37,12 +41,14 @@
           newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
         }
       }
+      // Remove a trailing ampersand
       lastChar = newUrl[newUrl.length - 1];
       if (lastChar === '&') {
         newUrl = newUrl.slice(0, -1);
       }
       return newUrl;
     };
+    // removes parameter query string from url. returns the modified url
     w.gl.utils.removeParamQueryString = function(url, param) {
       var urlVariables, variables;
       url = decodeURIComponent(url);
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index f145bd3ad74a4658b25a34a12409930d844ada8d..93daea1dce790e93a2760a598a9ef5c50065df18 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -1,17 +1,49 @@
-
+// LineHighlighter
+//
+// Handles single- and multi-line selection and highlight for blob views.
+//
 /*= require jquery.scrollTo */
 
+//
+// ### Example Markup
+//
+//   <div id="blob-content-holder">
+//     <div class="file-content">
+//       <div class="line-numbers">
+//         <a href="#L1" id="L1" data-line-number="1">1</a>
+//         <a href="#L2" id="L2" data-line-number="2">2</a>
+//         <a href="#L3" id="L3" data-line-number="3">3</a>
+//         <a href="#L4" id="L4" data-line-number="4">4</a>
+//         <a href="#L5" id="L5" data-line-number="5">5</a>
+//       </div>
+//       <pre class="code highlight">
+//         <code>
+//           <span id="LC1" class="line">...</span>
+//           <span id="LC2" class="line">...</span>
+//           <span id="LC3" class="line">...</span>
+//           <span id="LC4" class="line">...</span>
+//           <span id="LC5" class="line">...</span>
+//         </code>
+//       </pre>
+//     </div>
+//   </div>
+//
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
   this.LineHighlighter = (function() {
+    // CSS class applied to highlighted lines
     LineHighlighter.prototype.highlightClass = 'hll';
 
+    // Internal copy of location.hash so we're not dependent on `location` in tests
     LineHighlighter.prototype._hash = '';
 
     function LineHighlighter(hash) {
       var range;
       if (hash == null) {
+        // Initialize a LineHighlighter object
+        //
+        // hash - String URL hash for dependency injection in tests
         hash = location.hash;
       }
       this.setHash = bind(this.setHash, this);
@@ -24,6 +56,8 @@
         if (range[0]) {
           this.highlightRange(range);
           $.scrollTo("#L" + range[0], {
+            // Scroll to the first highlighted line on initial load
+            // Offset -50 for the sticky top bar, and another -100 for some context
             offset: -150
           });
         }
@@ -32,6 +66,12 @@
 
     LineHighlighter.prototype.bindEvents = function() {
       $('#blob-content-holder').on('mousedown', 'a[data-line-number]', this.clickHandler);
+      // While it may seem odd to bind to the mousedown event and then throw away
+      // the click event, there is a method to our madness.
+      //
+      // If not done this way, the line number anchor will sometimes keep its
+      // active state even when the event is cancelled, resulting in an ugly border
+      // around the link and/or a persisted underline text decoration.
       return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
         return event.preventDefault();
       });
@@ -44,6 +84,8 @@
       lineNumber = $(event.target).closest('a').data('line-number');
       current = this.hashToRange(this._hash);
       if (!(current[0] && event.shiftKey)) {
+        // If there's no current selection, or there is but Shift wasn't held,
+        // treat this like a single-line selection.
         this.setHash(lineNumber);
         return this.highlightLine(lineNumber);
       } else if (event.shiftKey) {
@@ -59,10 +101,23 @@
 
     LineHighlighter.prototype.clearHighlight = function() {
       return $("." + this.highlightClass).removeClass(this.highlightClass);
+    // Unhighlight previously highlighted lines
     };
 
+    // Convert a URL hash String into line numbers
+    //
+    // hash - Hash String
+    //
+    // Examples:
+    //
+    //   hashToRange('#L5')    # => [5, null]
+    //   hashToRange('#L5-15') # => [5, 15]
+    //   hashToRange('#foo')   # => [null, null]
+    //
+    // Returns an Array
     LineHighlighter.prototype.hashToRange = function(hash) {
       var first, last, matches;
+      //?L(\d+)(?:-(\d+))?$/)
       matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
       if (matches && matches.length) {
         first = parseInt(matches[1]);
@@ -73,10 +128,16 @@
       }
     };
 
+    // Highlight a single line
+    //
+    // lineNumber - Line number to highlight
     LineHighlighter.prototype.highlightLine = function(lineNumber) {
       return $("#LC" + lineNumber).addClass(this.highlightClass);
     };
 
+    // Highlight all lines within a range
+    //
+    // range - Array containing the starting and ending line numbers
     LineHighlighter.prototype.highlightRange = function(range) {
       var i, lineNumber, ref, ref1, results;
       if (range[1]) {
@@ -90,6 +151,7 @@
       }
     };
 
+    // Set the URL hash string
     LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
       var hash;
       if (lastLineNumber) {
@@ -101,10 +163,15 @@
       return this.__setLocationHash__(hash);
     };
 
+    // Make the actual hash change in the browser
+    //
+    // This method is stubbed in tests.
     LineHighlighter.prototype.__setLocationHash__ = function(value) {
       return history.pushState({
         turbolinks: false,
         url: value
+      // We're using pushState instead of assigning location.hash directly to
+      // prevent the page from scrolling on the hashchange event
       }, document.title, value);
     };
 
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 56ebf84c4f6d0484fd808a93ab090716dabc4e90..05644b3d03c5ccfcd849d891124dda58426223e0 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,10 +1,6 @@
 
 /*= require jquery.waitforimages */
-
-
 /*= require task_list */
-
-
 /*= require merge_request_tabs */
 
 (function() {
@@ -12,6 +8,11 @@
 
   this.MergeRequest = (function() {
     function MergeRequest(opts) {
+      // Initialize MergeRequest behavior
+      //
+      // Options:
+      //   action - String, current controller action
+      //
       this.opts = opts != null ? opts : {};
       this.submitNoteForm = bind(this.submitNoteForm, this);
       this.$el = $('.merge-request');
@@ -21,6 +22,7 @@
         };
       })(this));
       this.initTabs();
+      // Prevent duplicate event bindings
       this.disableTaskList();
       this.initMRBtnListeners();
       if ($("a.btn-close").length) {
@@ -28,14 +30,17 @@
       }
     }
 
+    // Local jQuery finder
     MergeRequest.prototype.$ = function(selector) {
       return this.$el.find(selector);
     };
 
     MergeRequest.prototype.initTabs = function() {
       if (this.opts.action !== 'new') {
+        // `MergeRequests#new` has no tab-persisting or lazy-loading behavior
         window.mrTabs = new MergeRequestTabs(this.opts);
       } else {
+        // Show the first tab (Commits)
         return $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show');
       }
     };
@@ -96,6 +101,8 @@
         url: $('form.js-issuable-update').attr('action'),
         data: patchData
       });
+    // TODO (rspeicher): Make the merge request description inline-editable like a
+    // note so that we can re-use its form here
     };
 
     return MergeRequest;
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index ad08209d61e55f598b2019c4fbc6b11da707848a..18bbfa7a4591aba98384d184f8962d448df0d29c 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,6 +1,49 @@
-
+// MergeRequestTabs
+//
+// Handles persisting and restoring the current tab selection and lazily-loading
+// content on the MergeRequests#show page.
+//
 /*= require jquery.cookie */
 
+//
+// ### Example Markup
+//
+//   <ul class="nav-links merge-request-tabs">
+//     <li class="notes-tab active">
+//       <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1">
+//         Discussion
+//       </a>
+//     </li>
+//     <li class="commits-tab">
+//       <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits">
+//         Commits
+//       </a>
+//     </li>
+//     <li class="diffs-tab">
+//       <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs">
+//         Diffs
+//       </a>
+//     </li>
+//   </ul>
+//
+//   <div class="tab-content">
+//     <div class="notes tab-pane active" id="notes">
+//       Notes Content
+//     </div>
+//     <div class="commits tab-pane" id="commits">
+//       Commits Content
+//     </div>
+//     <div class="diffs tab-pane" id="diffs">
+//       Diffs Content
+//     </div>
+//   </div>
+//
+//   <div class="mr-loading-status">
+//     <div class="loading">
+//       Loading Animation
+//     </div>
+//   </div>
+//
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
@@ -19,6 +62,7 @@
       this.setCurrentAction = bind(this.setCurrentAction, this);
       this.tabShown = bind(this.tabShown, this);
       this.showTab = bind(this.showTab, this);
+      // Store the `location` object, allowing for easier stubbing in tests
       this._location = location;
       this.bindEvents();
       this.activateTab(this.opts.action);
@@ -77,6 +121,7 @@
       }
     };
 
+    // Activate a tab based on the current action
     MergeRequestTabs.prototype.activateTab = function(action) {
       if (action === 'show') {
         action = 'notes';
@@ -84,20 +129,48 @@
       return $(".merge-request-tabs a[data-action='" + action + "']").tab('show');
     };
 
+    // Replaces the current Merge Request-specific action in the URL with a new one
+    //
+    // If the action is "notes", the URL is reset to the standard
+    // `MergeRequests#show` route.
+    //
+    // Examples:
+    //
+    //   location.pathname # => "/namespace/project/merge_requests/1"
+    //   setCurrentAction('diffs')
+    //   location.pathname # => "/namespace/project/merge_requests/1/diffs"
+    //
+    //   location.pathname # => "/namespace/project/merge_requests/1/diffs"
+    //   setCurrentAction('notes')
+    //   location.pathname # => "/namespace/project/merge_requests/1"
+    //
+    //   location.pathname # => "/namespace/project/merge_requests/1/diffs"
+    //   setCurrentAction('commits')
+    //   location.pathname # => "/namespace/project/merge_requests/1/commits"
+    //
+    // Returns the new URL String
     MergeRequestTabs.prototype.setCurrentAction = function(action) {
       var new_state;
+      // Normalize action, just to be safe
       if (action === 'show') {
         action = 'notes';
       }
       this.currentAction = action;
+      // Remove a trailing '/commits' or '/diffs'
       new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines)(\.html)?\/?$/, '');
+      // Append the new action if we're on a tab other than 'notes'
       if (action !== 'notes') {
         new_state += "/" + action;
       }
+      // Ensure parameters and hash come along for the ride
       new_state += this._location.search + this._location.hash;
       history.replaceState({
         turbolinks: true,
         url: new_state
+      // Replace the current history state with the new one without breaking
+      // Turbolinks' history.
+      //
+      // See https://github.com/rails/turbolinks/issues/363
       }, document.title, new_state);
       return new_state;
     };
@@ -159,10 +232,10 @@
       $('.hll').removeClass('hll');
       locationHash = window.location.hash;
       if (locationHash !== '') {
-        hashClassString = "." + (locationHash.replace('#', ''));
+        dataLineString = '[data-line-code="' + locationHash.replace('#', '') + '"]';
         $diffLine = $(locationHash + ":not(.match)", $('#diffs'));
         if (!$diffLine.is('tr')) {
-          $diffLine = $('#diffs').find("td" + locationHash + ", td" + hashClassString);
+          $diffLine = $('#diffs').find("td" + locationHash + ", td" + dataLineString);
         } else {
           $diffLine = $diffLine.find('td');
         }
@@ -206,6 +279,9 @@
       });
     };
 
+    // Show or hide the loading spinner
+    //
+    // status - Boolean, true to show, false to hide
     MergeRequestTabs.prototype.toggleLoading = function(status) {
       return $('.mr-loading-status .loading').toggle(status);
     };
@@ -232,6 +308,7 @@
 
     MergeRequestTabs.prototype.diffViewType = function() {
       return $('.inline-parallel-buttons a.active').data('view-type');
+    // Returns diff view type
     };
 
     MergeRequestTabs.prototype.expandViewContainer = function() {
@@ -245,6 +322,8 @@
         if ($gutterIcon.is('.fa-angle-double-right')) {
           return $gutterIcon.closest('a').trigger('click', [true]);
         }
+      // Wait until listeners are set
+      // Only when sidebar is expanded
       }, 0);
     };
 
@@ -259,6 +338,9 @@
           return $gutterIcon.closest('a').trigger('click', [true]);
         }
       }, 0);
+    // Expand the issuable sidebar unless the user explicitly collapsed it
+    // Wait until listeners are set
+    // Only when sidebar is collapsed
     };
 
     return MergeRequestTabs;
diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js
index bd35b6f679d18db622510b9a3c354b261e6b1c6d..7bbcdf5983880534e8362abdf65ec7e327b6fcd2 100644
--- a/app/assets/javascripts/merge_request_widget.js
+++ b/app/assets/javascripts/merge_request_widget.js
@@ -3,6 +3,12 @@
 
   this.MergeRequestWidget = (function() {
     function MergeRequestWidget(opts) {
+      // Initialize MergeRequestWidget behavior
+      //
+      //   check_enable           - Boolean, whether to check automerge status
+      //   merge_check_url - String, URL to use to check automerge status
+      //   ci_status_url        - String, URL to use to check CI status
+      //
       this.opts = opts;
       $('#modal_merge_info').modal({
         show: false
@@ -118,6 +124,8 @@
             if (data.coverage) {
               _this.showCICoverage(data.coverage);
             }
+            // The first check should only update the UI, a notification
+            // should only be displayed on status changes
             if (showNotification && !_this.firstCICheck) {
               status = _this.ciLabelForStatus(data.status);
               if (status === "preparing") {
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index e8d51da7d5829ae07bf60c1a0f824df5d0e50b3b..bc1a99057d9820c8e7c0c89bff2409cc8afd329a 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -110,6 +110,7 @@
         },
         update: function(event, ui) {
           var data;
+          // Prevents sorting from container which element has been removed.
           if ($(this).find(ui.item).length > 0) {
             data = $(this).sortable("serialize");
             return Milestone.sortIssues(data);
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index e897ebdf6304bcdc337fae6d2a60c4d6f3ff779d..c8031174dd21733aa9dc3e78bbf6778a02b121e5 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -92,6 +92,7 @@
           },
           hidden: function() {
             $selectbox.hide();
+            // display:block overrides the hide-collapse rule
             return $value.css('display', '');
           },
           clicked: function(selected, $el, e) {
diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch-graph.js
index c0fec1f860773b4cb42a9019307b31a6cbfe4963..91132af273a32bd04d096bfc29cb2226ef15e2f0 100644
--- a/app/assets/javascripts/network/branch-graph.js
+++ b/app/assets/javascripts/network/branch-graph.js
@@ -90,6 +90,7 @@
       results = [];
       while (k < this.mspace) {
         this.colors.push(Raphael.getColor(.8));
+        // Skipping a few colors in the spectrum to get more contrast between colors
         Raphael.getColor();
         Raphael.getColor();
         results.push(k++);
@@ -112,6 +113,7 @@
       for (mm = j = 0, len = ref.length; j < len; mm = ++j) {
         day = ref[mm];
         if (cuday !== day[0] || cumonth !== day[1]) {
+          // Dates
           r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
             font: "12px Monaco, monospace",
             fill: "#BBB"
@@ -119,6 +121,7 @@
           cuday = day[0];
         }
         if (cumonth !== day[1]) {
+          // Months
           r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
             font: "12px Monaco, monospace",
             fill: "#EEE"
@@ -207,6 +210,7 @@
       }
       r = this.r;
       shortrefs = commit.refs;
+      // Truncate if longer than 15 chars
       if (shortrefs.length > 17) {
         shortrefs = shortrefs.substr(0, 15) + "…";
       }
@@ -217,6 +221,7 @@
         title: commit.refs
       });
       textbox = text.getBBox();
+      // Create rectangle based on the size of the textbox
       rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
         fill: "#000",
         "fill-opacity": .5,
@@ -229,6 +234,7 @@
       });
       label = r.set(rect, text);
       label.transform(["t", -rect.getBBox().width - 15, 0]);
+      // Set text to front
       return text.toFront();
     };
 
@@ -283,11 +289,13 @@
         parentY = this.offsetY + this.unitTime * parentCommit.time;
         parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
         parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
+        // Set line color
         if (parentCommit.space <= commit.space) {
           color = this.colors[commit.space];
         } else {
           color = this.colors[parentCommit.space];
         }
+        // Build line shape
         if (parent[1] === commit.space) {
           offset = [0, 5];
           arrow = "l-2,5,4,0,-2,-5,0,5";
@@ -298,13 +306,17 @@
           offset = [-3, 3];
           arrow = "l-5,0,2,4,3,-4,-4,2";
         }
+        // Start point
         route = ["M", x + offset[0], y + offset[1]];
+        // Add arrow if not first parent
         if (i > 0) {
           route.push(arrow);
         }
+        // Circumvent if overlap
         if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
           route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5);
         }
+        // End point
         route.push("L", parentX1, parentY);
         results.push(r.path(route).attr({
           stroke: color,
@@ -325,6 +337,7 @@
           "fill-opacity": .5,
           stroke: "none"
         });
+        // Displayed in the center
         return this.element.scrollTop(y - this.graphHeight / 2);
       }
     };
diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js
index 6a7422a77553bf88e625cc29249aa4b9d2cf0afc..67c3e6453647011080c2c5212f1fd8d3155b5cef 100644
--- a/app/assets/javascripts/network/network_bundle.js
+++ b/app/assets/javascripts/network/network_bundle.js
@@ -1,4 +1,9 @@
-
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
 /*= require_tree . */
 
 (function() {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index d0d5cad813a24eb9064560c019e2d20964b9e14b..c6854f703fbd182a3398653adf9f3f06a226fb2f 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,22 +1,10 @@
 
 /*= require autosave */
-
-
 /*= require autosize */
-
-
 /*= require dropzone */
-
-
 /*= require dropzone_input */
-
-
 /*= require gfm_auto_complete */
-
-
 /*= require jquery.atwho */
-
-
 /*= require task_list */
 
 (function() {
@@ -60,26 +48,43 @@
     }
 
     Notes.prototype.addBinding = function() {
+      // add note to UI after creation
       $(document).on("ajax:success", ".js-main-target-form", this.addNote);
       $(document).on("ajax:success", ".js-discussion-note-form", this.addDiscussionNote);
+      // catch note ajax errors
       $(document).on("ajax:error", ".js-main-target-form", this.addNoteError);
+      // change note in UI after update
       $(document).on("ajax:success", "form.edit-note", this.updateNote);
+      // Edit note link
       $(document).on("click", ".js-note-edit", this.showEditForm);
       $(document).on("click", ".note-edit-cancel", this.cancelEdit);
+      // Reopen and close actions for Issue/MR combined with note form submit
       $(document).on("click", ".js-comment-button", this.updateCloseButton);
       $(document).on("keyup input", ".js-note-text", this.updateTargetButtons);
+      // resolve a discussion
       $(document).on('click', '.js-comment-resolve-button', this.resolveDiscussion);
+      // remove a note (in general)
       $(document).on("click", ".js-note-delete", this.removeNote);
+      // delete note attachment
       $(document).on("click", ".js-note-attachment-delete", this.removeAttachment);
+      // reset main target form after submit
       $(document).on("ajax:complete", ".js-main-target-form", this.reenableTargetFormSubmitButton);
       $(document).on("ajax:success", ".js-main-target-form", this.resetMainTargetForm);
+      // reset main target form when clicking discard
       $(document).on("click", ".js-note-discard", this.resetMainTargetForm);
+      // update the file name when an attachment is selected
       $(document).on("change", ".js-note-attachment-input", this.updateFormAttachment);
+      // reply to diff/discussion notes
       $(document).on("click", ".js-discussion-reply-button", this.replyToDiscussionNote);
+      // add diff note
       $(document).on("click", ".js-add-diff-note-button", this.addDiffNote);
+      // hide diff note form
       $(document).on("click", ".js-close-discussion-note-form", this.cancelDiscussionForm);
+      // fetch notes when tab becomes visible
       $(document).on("visibilitychange", this.visibilityChange);
+      // when issue status changes, we need to refresh data
       $(document).on("issuable:change", this.refresh);
+      // when a key is clicked on the notes
       return $(document).on("keydown", ".js-note-text", this.keydownNoteText);
     };
 
@@ -112,6 +117,7 @@
         return;
       }
       $textarea = $(e.target);
+      // Edit previous note when UP arrow is hit
       switch (e.which) {
         case 38:
           if ($textarea.val() !== '') {
@@ -123,6 +129,7 @@
             return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
           }
           break;
+        // Cancel creating diff note or editing any note when ESCAPE is hit
         case 27:
           discussionNoteForm = $textarea.closest('.js-discussion-note-form');
           if (discussionNoteForm.length) {
@@ -247,10 +254,13 @@
         votesBlock = $('.js-awards-block').eq(0);
         gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.name);
         return gl.awardsHandler.scrollToAwards();
+      // render note if it not present in loaded list
+      // or skip if rendered
       } else if (this.isNewNote(note)) {
         this.note_ids.push(note.id);
         $notesList = $('ul.main-notes-list');
         $notesList.append(note.html).syntaxHighlight();
+        // Update datetime format on the recent note
         gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
         this.initTaskList();
         this.refresh();
@@ -291,19 +301,26 @@
       row = form.closest("tr");
       note_html = $(note.html);
       note_html.syntaxHighlight();
+      // is this the first note of discussion?
       discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
       if ((note.original_discussion_id != null) && discussionContainer.length === 0) {
         discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']");
       }
       if (discussionContainer.length === 0) {
+        // insert the note and the reply button after the temp row
         row.after(note.diff_discussion_html);
+        // remove the note (will be added again below)
         row.next().find(".note").remove();
+        // Before that, the container didn't exist
         discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
+        // Add note to 'Changes' page discussions
         discussionContainer.append(note_html);
+        // Init discussion on 'Discussion' page if it is merge request page
         if ($('body').attr('data-page').indexOf('projects:merge_request') === 0) {
           $('ul.main-notes-list').append(note.discussion_html).syntaxHighlight();
         }
       } else {
+        // append new note to all matching discussions
         discussionContainer.append(note_html);
       }
 
@@ -327,11 +344,18 @@
     Notes.prototype.resetMainTargetForm = function(e) {
       var form;
       form = $(".js-main-target-form");
+      // remove validation errors
       form.find(".js-errors").remove();
+      // reset text and preview
       form.find(".js-md-write-button").click();
       form.find(".js-note-text").val("").trigger("input");
       form.find(".js-note-text").data("autosave").reset();
-      return this.updateTargetButtons(e);
+
+      var event = document.createEvent('Event');
+      event.initEvent('autosize:update', true, false);
+      form.find('.js-autosize')[0].dispatchEvent(event);
+
+      this.updateTargetButtons(e);
     };
 
     Notes.prototype.reenableTargetFormSubmitButton = function() {
@@ -349,9 +373,13 @@
 
     Notes.prototype.setupMainTargetNoteForm = function() {
       var form;
+      // find the form
       form = $(".js-new-note-form");
+      // Set a global clone of the form for later cloning
       this.formClone = form.clone();
+      // show the form
       this.setupNoteForm(form);
+      // fix classes
       form.removeClass("js-new-note-form");
       form.addClass("js-main-target-form");
       form.find("#note_line_code").remove();
@@ -416,6 +444,7 @@
       }
 
       this.renderDiscussionNote(note);
+      // cleanup after successfully creating a diff/discussion note
       this.removeDiscussionNoteForm($form);
     };
 
@@ -428,10 +457,12 @@
 
     Notes.prototype.updateNote = function(_xhr, note, _status) {
       var $html, $note_li;
+      // Convert returned HTML to a jQuery object so we can modify it further
       $html = $(note.html);
       gl.utils.localTimeAgo($('.js-timeago', $html));
       $html.syntaxHighlight();
       $html.find('.js-task-list-container').taskList('enable');
+      // Find the note's `li` element by ID and replace it with the updated HTML
       $note_li = $('.note-row-' + note.id);
 
       $note_li.replaceWith($html);
@@ -456,15 +487,20 @@
       note.addClass("is-editting");
       form = note.find(".note-edit-form");
       form.addClass('current-note-edit-form');
+      // Show the attachment delete link
       note.find(".js-note-attachment-delete").show();
       done = function($noteText) {
         var noteTextVal;
+        // Neat little trick to put the cursor at the end
         noteTextVal = $noteText.val();
+        // Store the original note text in a data attribute to retrieve if a user cancels edit.
         form.find('form.edit-note').data('original-note', noteTextVal);
         return $noteText.val('').val(noteTextVal);
       };
       new GLForm(form);
       if ((scrollTo != null) && (myLastNote != null)) {
+        // scroll to the bottom
+        // so the open of the last element doesn't make a jump
         $('html, body').scrollTop($(document).height());
         return $('html, body').animate({
           scrollTop: myLastNote.offset().top - 150
@@ -500,6 +536,7 @@
       form = note.find(".current-note-edit-form");
       note.removeClass("is-editting");
       form.removeClass("current-note-edit-form");
+      // Replace markdown textarea text with original note text.
       return form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'));
     };
 
@@ -515,6 +552,9 @@
       var noteId;
       noteId = $(e.currentTarget).closest(".note").attr("id");
       $(".note[id='" + noteId + "']").each((function(_this) {
+        // A same note appears in the "Discussion" and in the "Changes" tab, we have
+        // to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
+        // where $("#noteId") would return only one.
         return function(i, el) {
           var note, notes;
           note = $(el);
@@ -528,13 +568,17 @@
             }
           }
 
+          // check if this is the last note for this line
           if (notes.find(".note").length === 1) {
+            // "Discussions" tab
             notes.closest(".timeline-entry").remove();
+            // "Changes" tab / commit view
             notes.closest("tr").remove();
           }
           return note.remove();
         };
       })(this));
+      // Decrement the "Discussions" counter only once
       return this.updateNotesCount(-1);
     };
 
@@ -566,10 +610,12 @@
       var form, replyLink;
       form = this.formClone.clone();
       replyLink = $(e.target).closest(".js-discussion-reply-button");
+      // insert the form after the button
       replyLink
         .closest('.discussion-reply-holder')
         .hide()
         .after(form);
+      // show the form
       return this.setupDiscussionNoteForm(replyLink, form);
     };
 
@@ -584,6 +630,7 @@
      */
 
     Notes.prototype.setupDiscussionNoteForm = function(dataHolder, form) {
+      // setup note target
       form.attr('id', "new-discussion-note-form-" + (dataHolder.data("discussionId")));
       form.attr("data-line-code", dataHolder.data("lineCode"));
       form.find("#note_type").val(dataHolder.data("noteType"));
@@ -631,6 +678,7 @@
       addForm = false;
       notesContentSelector = ".notes_content";
       rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"><div class=\"content\"></div></td></tr>";
+      // In parallel view, look inside the correct left/right pane
       if (this.isParallelView()) {
         lineType = $link.data("lineType");
         notesContentSelector += "." + lineType;
@@ -647,6 +695,7 @@
             e.target = replyButton[0];
             $.proxy(this.replyToDiscussionNote, replyButton[0], e).call();
           } else {
+            // In parallel view, the form may not be present in one of the panes
             noteForm = notesContent.find(".js-discussion-note-form");
             if (noteForm.length === 0) {
               addForm = true;
@@ -654,6 +703,7 @@
           }
         }
       } else {
+        // add a notes row and insert the form
         row.after(rowCssToAdd);
         nextRow = row.next();
         notesContent = nextRow.find(notesContentSelector);
@@ -662,6 +712,7 @@
       if (addForm) {
         newForm = this.formClone.clone();
         newForm.appendTo(notesContent);
+        // show the form
         return this.setupDiscussionNoteForm($link, newForm);
       }
     };
@@ -680,12 +731,15 @@
       glForm = form.data('gl-form');
       glForm.destroy();
       form.find(".js-note-text").data("autosave").reset();
+      // show the reply button (will only work for replies)
       form
         .prev('.discussion-reply-holder')
         .show();
       if (row.is(".js-temp-notes-holder")) {
+        // remove temporary row for diff lines
         return row.remove();
       } else {
+        // only remove the form
         return form.remove();
       }
     };
@@ -707,6 +761,7 @@
     Notes.prototype.updateFormAttachment = function() {
       var filename, form;
       form = $(this).closest("form");
+      // get only the basename
       filename = $(this).val().replace(/^.*[\\\/]/, "");
       return form.find(".js-attachment-filename").text(filename);
     };
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 5fd7579964024c842915af9f237863d7335394f0..5200487814f736f869e2c3634a6f42d8eee73e05 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -1,9 +1,15 @@
+// MarkdownPreview
+//
+// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
+// and showing a warning when more than `x` users are referenced.
+//
 (function() {
   var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector;
 
   this.MarkdownPreview = (function() {
     function MarkdownPreview() {}
 
+    // Minimum number of users referenced before triggering a warning
     MarkdownPreview.prototype.referenceThreshold = 10;
 
     MarkdownPreview.prototype.ajaxCache = {};
@@ -101,8 +107,10 @@
       return;
     }
     lastTextareaPreviewed = $form.find('textarea.markdown-area');
+    // toggle tabs
     $form.find(writeButtonSelector).parent().removeClass('active');
     $form.find(previewButtonSelector).parent().addClass('active');
+    // toggle content
     $form.find('.md-write-holder').hide();
     $form.find('.md-preview-holder').show();
     return markdownPreview.showPreview($form);
@@ -113,8 +121,10 @@
       return;
     }
     lastTextareaPreviewed = null;
+    // toggle tabs
     $form.find(writeButtonSelector).parent().addClass('active');
     $form.find(previewButtonSelector).parent().removeClass('active');
+    // toggle content
     $form.find('.md-write-holder').show();
     $form.find('textarea.markdown-area').focus();
     return $form.find('.md-preview-holder').hide();
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index a3eea316f67c7aca652b9446ff597734c5243d35..30cd6f6e470c14b6228ea1b3f528a9fe92619764 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -5,6 +5,7 @@
   GitLabCrop = (function() {
     var FILENAMEREGEX;
 
+    // Matches everything but the file name
     FILENAMEREGEX = /^.*[\\\/]/;
 
     function GitLabCrop(input, opts) {
@@ -17,11 +18,18 @@
       this.onModalShow = bind(this.onModalShow, this);
       this.onPickImageClick = bind(this.onPickImageClick, this);
       this.fileInput = $(input);
+      // We should rename to avoid spec to fail
+      // Form will submit the proper input filed with a file using FormData
       this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger");
+      // Set defaults
       this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickImageEl, this.uploadImageBtn = opts.uploadImageBtn, this.modalCropImg = opts.modalCropImg;
+      // Required params
+      // Ensure needed elements are jquery objects
+      // If selector is provided we will convert them to a jQuery Object
       this.filename = this.getElement(this.filename);
       this.previewImage = this.getElement(this.previewImage);
       this.pickImageEl = this.getElement(this.pickImageEl);
+      // Modal elements usually are outside the @form element
       this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop;
       this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn;
       this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg;
@@ -93,8 +101,8 @@
       return this.modalCropImg.attr('src', '').cropper('destroy');
     };
 
-    GitLabCrop.prototype.onUploadImageBtnClick = function(e) {
-      e.preventDefault();
+    GitLabCrop.prototype.onUploadImageBtnClick = function(e) { // Remove attached image
+      e.preventDefault(); // Destroy cropper instance
       this.setBlob();
       this.setPreview();
       this.modalCrop.modal('hide');
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index ed1d87abafed50b7f42e28d07cbf227b456c8e39..60f9fba577785dc6e8bc9ef0fef2d1b8f8058680 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -11,9 +11,11 @@
       this.form = (ref = opts.form) != null ? ref : $('.edit-user');
       $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() {
         return $(this).parents('form').submit();
+      // Automatically submit the Preferences form when any of its radio buttons change
       });
       $('#user_notification_email').on('change', function() {
         return $(this).parents('form').submit();
+      // Automatically submit email form when it changes
       });
       $('.update-username').on('ajax:before', function() {
         $('.loading-username').show();
@@ -76,6 +78,7 @@
         },
         complete: function() {
           window.scrollTo(0, 0);
+          // Enable submit button after requests ends
           return self.form.find(':input[disabled]').enable();
         }
       });
@@ -93,6 +96,7 @@
       if (comment && comment.length > 1 && $title.val() === '') {
         return $title.val(comment[1]).change();
       }
+    // Extract the SSH Key title from its comment
     });
     if (gl.utils.getPagePath() === 'profiles') {
       return new Profile();
diff --git a/app/assets/javascripts/profile/profile_bundle.js b/app/assets/javascripts/profile/profile_bundle.js
index b95faadc8e72f17e7cdb90eab9203622916bee02..d6e4d9f7ad82e841360ab4ab8d46a8738dcdcc51 100644
--- a/app/assets/javascripts/profile/profile_bundle.js
+++ b/app/assets/javascripts/profile/profile_bundle.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 66e097c0a289d529c8cc47455c609bca573e5924..a6c015299a0db796a48753ebb6884cfb04424ae2 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -11,7 +11,13 @@
         url = $("#project_clone").val();
         $('#project_clone').val(url);
         return $('.clone').text(url);
+      // Git protocol switcher
+      // Remove the active class for all buttons (ssh, http, kerberos if shown)
+      // Add the active class for the clicked button
+      // Update the input field
+      // Update the command line instructions
       });
+      // Ref switcher
       this.initRefSwitcher();
       $('.project-refs-select').on('change', function() {
         return $(this).parents('form').submit();
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 4925f0519f069117b0bc44c28e042c003d926a26..5bf900f3e1d5c4cda2575002394551d1cde138dc 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -13,8 +13,11 @@
       this.selectRowUp = bind(this.selectRowUp, this);
       this.filePaths = {};
       this.inputElement = this.element.find(".file-finder-input");
+      // init event
       this.initEvent();
+      // focus text input box
       this.inputElement.focus();
+      // load file list
       this.load(this.options.url);
     }
 
@@ -42,6 +45,7 @@
           }
         }
       });
+    // init event
     };
 
     ProjectFindFile.prototype.findFile = function() {
@@ -49,8 +53,10 @@
       searchText = this.inputElement.val();
       result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
       return this.renderList(result, searchText);
+    // find file
     };
 
+    // files pathes load
     ProjectFindFile.prototype.load = function(url) {
       return $.ajax({
         url: url,
@@ -67,6 +73,7 @@
       });
     };
 
+    // render result
     ProjectFindFile.prototype.renderList = function(filePaths, searchText) {
       var blobItemUrl, filePath, html, i, j, len, matches, results;
       this.element.find(".tree-table > tbody").empty();
@@ -86,6 +93,7 @@
       return results;
     };
 
+    // highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
     highlighter = function(element, text, matches) {
       var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
       lastIndex = 0;
@@ -110,6 +118,7 @@
       return element.append(document.createTextNode(text.substring(lastIndex)));
     };
 
+    // make tbody row html
     ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
       var $tr;
       $tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>");
diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js
index 8ca4c4279120525b19e0744df248551bee67629f..c8cfc9a9ba896073707006c6238a55d87579ef2d 100644
--- a/app/assets/javascripts/project_show.js
+++ b/app/assets/javascripts/project_show.js
@@ -7,3 +7,5 @@
   })();
 
 }).call(this);
+
+// I kept class for future
diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js
index 4f415b05dbc4793f29410c8b53a627c43a9cd122..04fb49552e826f727bf8190568372075fcc6d367 100644
--- a/app/assets/javascripts/projects_list.js
+++ b/app/assets/javascripts/projects_list.js
@@ -33,6 +33,7 @@
           $('.projects-list-holder').replaceWith(data.html);
           return history.replaceState({
             page: project_filter_url
+          // Change url so if user reload a page - search results are saved
           }, document.title, project_filter_url);
         },
         dataType: "json"
diff --git a/app/assets/javascripts/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branch_dropdown.js.es6
index 6738dc8862dff3e10683a7ea926337d16a088524..983322cbeccb83d48d53982dd83d64c771aa727b 100644
--- a/app/assets/javascripts/protected_branch_dropdown.js.es6
+++ b/app/assets/javascripts/protected_branch_dropdown.js.es6
@@ -45,6 +45,7 @@ class ProtectedBranchDropdown {
   }
 
   onClickCreateWildcard() {
+    // Refresh the dropdown's data, which ends up calling `getProtectedBranches`
     this.$dropdown.data('glDropdown').remote.execute();
     this.$dropdown.data('glDropdown').selectRowAtIndex(0);
   }
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 227e8c696b4ffce37f8fd682c3da4e2871c1ebaa..8abb09c626fd2fdf44ee2dc128897eec0fd21c3b 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -24,6 +24,7 @@
       this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this);
       this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this);
       this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || '';
+      // Dropdown Element
       this.dropdown = this.wrap.find('.dropdown');
       this.dropdownContent = this.dropdown.find('.dropdown-content');
       this.locationBadgeEl = this.getElement('.location-badge');
@@ -35,6 +36,7 @@
       this.repositoryInputEl = this.getElement('#repository_ref');
       this.clearInput = this.getElement('.js-clear-input');
       this.saveOriginalState();
+      // Only when user is logged in
       if (gon.current_user_id) {
         this.createAutocomplete();
       }
@@ -43,6 +45,7 @@
       this.bindEvents();
     }
 
+    // Finds an element inside wrapper element
     SearchAutocomplete.prototype.getElement = function(selector) {
       return this.wrap.find(selector);
     };
@@ -82,6 +85,7 @@
         }
         return;
       }
+      // Prevent multiple ajax calls
       if (this.loadingSuggestions) {
         return;
       }
@@ -92,14 +96,17 @@
         term: term
       }, function(response) {
         var data, firstCategory, i, lastCategory, len, suggestion;
+        // Hide dropdown menu if no suggestions returns
         if (!response.length) {
           _this.disableAutocomplete();
           return;
         }
         data = [];
+        // List results
         firstCategory = true;
         for (i = 0, len = response.length; i < len; i++) {
           suggestion = response[i];
+          // Add group header before list each group
           if (lastCategory !== suggestion.category) {
             if (!firstCategory) {
               data.push('separator');
@@ -119,6 +126,7 @@
             url: suggestion.url
           });
         }
+        // Add option to proceed with the search
         if (data.length) {
           data.push('separator');
           data.push({
@@ -169,11 +177,13 @@
 
     SearchAutocomplete.prototype.serializeState = function() {
       return {
+        // Search Criteria
         search_project_id: this.projectInputEl.val(),
         group_id: this.groupInputEl.val(),
         search_code: this.searchCodeInputEl.val(),
         repository_ref: this.repositoryInputEl.val(),
         scope: this.scopeInputEl.val(),
+        // Location badge
         _location: this.locationBadgeEl.text()
       };
     };
@@ -194,6 +204,7 @@
 
     SearchAutocomplete.prototype.enableAutocomplete = function() {
       var _this;
+      // No need to enable anything if user is not logged in
       if (!gon.current_user_id) {
         return;
       }
@@ -206,18 +217,22 @@
     };
 
     SearchAutocomplete.prototype.onSearchInputKeyDown = function() {
+      // Saves last length of the entered text
       return this.saveTextLength();
     };
 
     SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) {
       switch (e.keyCode) {
         case KEYCODE.BACKSPACE:
+          // when trying to remove the location badge
           if (this.lastTextLength === 0 && this.badgePresent()) {
             this.removeLocationBadge();
           }
+          // When removing the last character and no badge is present
           if (this.lastTextLength === 1) {
             this.disableAutocomplete();
           }
+          // When removing any character from existin value
           if (this.lastTextLength > 1) {
             this.enableAutocomplete();
           }
@@ -232,9 +247,12 @@
         case KEYCODE.DOWN:
           return;
         default:
+          // Handle the case when deleting the input value other than backspace
+          // e.g. Pressing ctrl + backspace or ctrl + x
           if (this.searchInput.val() === '') {
             this.disableAutocomplete();
           } else {
+            // We should display the menu only when input is not empty
             if (e.keyCode !== KEYCODE.ENTER) {
               this.enableAutocomplete();
             }
@@ -243,7 +261,9 @@
       this.wrap.toggleClass('has-value', !!e.target.value);
     };
 
+    // Avoid falsy value to be returned
     SearchAutocomplete.prototype.onSearchInputClick = function(e) {
+      // Prevents closing the dropdown menu
       return e.stopImmediatePropagation();
     };
 
@@ -267,6 +287,7 @@
     SearchAutocomplete.prototype.onSearchInputBlur = function(e) {
       this.isFocused = false;
       this.wrap.removeClass('search-active');
+      // If input is blank then restore state
       if (this.searchInput.val() === '') {
         return this.restoreOriginalState();
       }
@@ -311,6 +332,7 @@
       results = [];
       for (i = 0, len = inputs.length; i < len; i++) {
         input = inputs[i];
+        // _location isnt a input
         if (input === '_location') {
           break;
         }
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index 3b28332854a0ad0b2651e730c6bac236bab59314..3aa8536d40a5e01a7624de4ab5f7d9649763c321 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -86,6 +86,7 @@
     var defaultStopCallback;
     defaultStopCallback = Mousetrap.stopCallback;
     return function(e, element, combo) {
+      // allowed shortcuts if textarea, input, contenteditable are focused
       if (['ctrl+shift+p', 'command+shift+p'].indexOf(combo) !== -1) {
         return false;
       } else {
diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js
index 6c78914d3386dd56159fafa8232cf89d10f77d0b..92ce31969e3023eeeac7b02210e9c0bce858a579 100644
--- a/app/assets/javascripts/shortcuts_find_file.js
+++ b/app/assets/javascripts/shortcuts_find_file.js
@@ -14,8 +14,10 @@
       ShortcutsFindFile.__super__.constructor.call(this);
       _oldStopCallback = Mousetrap.stopCallback;
       Mousetrap.stopCallback = (function(_this) {
+        // override to fire shortcuts action when focus in textbox
         return function(event, element, combo) {
           if (element === _this.projectFindFile.inputElement[0] && (combo === 'up' || combo === 'down' || combo === 'esc' || combo === 'enter')) {
+            // when press up/down key in textbox, cusor prevent to move to home/end
             event.preventDefault();
             return false;
           }
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 3f3a8a9dfd9cbf70573499436c6aa135c5f2bc67..235bf4f95ec7206d7e521b77a27f80ae2d636261 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -1,7 +1,5 @@
 
 /*= require mousetrap */
-
-
 /*= require shortcuts_navigation */
 
 (function() {
@@ -43,16 +41,20 @@
         if (selected.trim() === "") {
           return;
         }
+        // Put a '>' character before each non-empty line in the selection
         quote = _.map(selected.split("\n"), function(val) {
           if (val.trim() !== '') {
             return "> " + val + "\n";
           }
         });
+        // If replyField already has some content, add a newline before our quote
         separator = replyField.val().trim() !== "" && "\n" || '';
         replyField.val(function(_, current) {
           return current + separator + quote.join('') + "\n";
         });
+        // Trigger autosave for the added text
         replyField.trigger('input');
+        // Focus the input field
         return replyField.focus();
       }
     };
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js
index 469e25482bbcbcd84d15eadf8d38b29216a89d7c..b04159420d1fa3afbd2a6a851b55958c5fa2df74 100644
--- a/app/assets/javascripts/shortcuts_navigation.js
+++ b/app/assets/javascripts/shortcuts_navigation.js
@@ -34,6 +34,9 @@
       Mousetrap.bind('g i', function() {
         return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
       });
+      Mousetrap.bind('g l', function() {
+        ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards');
+      });
       Mousetrap.bind('g m', function() {
         return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
       });
diff --git a/app/assets/javascripts/sidebar.js b/app/assets/javascripts/sidebar.js
deleted file mode 100644
index bd0c1194b361ea5f0e3c177d6d48ebf96ecda335..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/sidebar.js
+++ /dev/null
@@ -1,41 +0,0 @@
-(function() {
-  var collapsed, expanded, toggleSidebar;
-
-  collapsed = 'page-sidebar-collapsed';
-
-  expanded = 'page-sidebar-expanded';
-
-  toggleSidebar = function() {
-    $('.page-with-sidebar').toggleClass(collapsed + " " + expanded);
-    $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded");
-    if ($.cookie('pin_nav') === 'true') {
-      $('.navbar-fixed-top').toggleClass('header-pinned-nav');
-      $('.page-with-sidebar').toggleClass('page-sidebar-pinned');
-    }
-    return setTimeout((function() {
-      var niceScrollBars;
-      niceScrollBars = $('.nav-sidebar').niceScroll();
-      return niceScrollBars.updateScrollBar();
-    }), 300);
-  };
-
-  $(document).off('click', 'body').on('click', 'body', function(e) {
-    var $nav, $target, $toggle, pageExpanded;
-    if ($.cookie('pin_nav') !== 'true') {
-      $target = $(e.target);
-      $nav = $target.closest('.sidebar-wrapper');
-      pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded');
-      $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle');
-      if ($nav.length === 0 && pageExpanded && $toggle.length === 0) {
-        $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
-        return $('.navbar-fixed-top').toggleClass('header-collapsed header-expanded');
-      }
-    }
-  });
-
-  $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', function(e) {
-    e.preventDefault();
-    return toggleSidebar();
-  });
-
-}).call(this);
diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..755fac8107b3bc6c173f5cff65dd59f9c6e62f26
--- /dev/null
+++ b/app/assets/javascripts/sidebar.js.es6
@@ -0,0 +1,93 @@
+((global) => {
+  let singleton;
+
+  const pinnedStateCookie = 'pin_nav';
+  const sidebarBreakpoint = 1024;
+
+  const pageSelector = '.page-with-sidebar';
+  const navbarSelector = '.navbar-fixed-top';
+  const sidebarWrapperSelector = '.sidebar-wrapper';
+  const sidebarContentSelector = '.nav-sidebar';
+
+  const pinnedToggleSelector = '.js-nav-pin';
+  const sidebarToggleSelector = '.toggle-nav-collapse, .side-nav-toggle';
+
+  const pinnedPageClass = 'page-sidebar-pinned';
+  const expandedPageClass = 'page-sidebar-expanded';
+
+  const pinnedNavbarClass = 'header-sidebar-pinned';
+  const expandedNavbarClass = 'header-sidebar-expanded';
+
+  class Sidebar {
+    constructor() {
+      if (!singleton) {
+        singleton = this;
+        singleton.init();
+      }
+      return singleton;
+    }
+
+    init() {
+      this.isPinned = $.cookie(pinnedStateCookie) === 'true';
+      this.isExpanded = (
+        window.innerWidth >= sidebarBreakpoint &&
+        $(pageSelector).hasClass(expandedPageClass)
+      );
+      $(document)
+        .on('click', sidebarToggleSelector, () => this.toggleSidebar())
+        .on('click', pinnedToggleSelector, () => this.togglePinnedState())
+        .on('click', 'html, body', (e) => this.handleClickEvent(e))
+        .on('page:change', () => this.renderState());
+      this.renderState();
+    }
+
+    handleClickEvent(e) {
+      if (this.isExpanded && (!this.isPinned || window.innerWidth < sidebarBreakpoint)) {
+        const $target = $(e.target);
+        const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0;
+        const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0;
+        if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) {
+          this.toggleSidebar();
+        }
+      }
+    }
+
+    toggleSidebar() {
+      this.isExpanded = !this.isExpanded;
+      this.renderState();
+    }
+
+    togglePinnedState() {
+      this.isPinned = !this.isPinned;
+      if (!this.isPinned) {
+        this.isExpanded = false;
+      }
+      $.cookie(pinnedStateCookie, this.isPinned ? 'true' : 'false', {
+        path: gon.relative_url_root || '/',
+        expires: 3650
+      });
+      this.renderState();
+    }
+
+    renderState() {
+      $(pageSelector)
+        .toggleClass(pinnedPageClass, this.isPinned && this.isExpanded)
+        .toggleClass(expandedPageClass, this.isExpanded);
+      $(navbarSelector)
+        .toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded)
+        .toggleClass(expandedNavbarClass, this.isExpanded);
+
+      const $pinnedToggle = $(pinnedToggleSelector);
+      const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation';
+      const tooltipState = $pinnedToggle.attr('aria-describedby') && this.isExpanded ? 'show' : 'hide';
+      $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState);
+
+      if (this.isExpanded) {
+        setTimeout(() => $(sidebarContentSelector).niceScroll().updateScrollBar(), 200);
+      }
+    }
+  }
+
+  global.Sidebar = Sidebar;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index dba62638c78143bb71f79eb7fd3a468ae4a219fb..2ae7bf5fc156f032aa38ff609a84d7914d0a7d79 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -1,9 +1,20 @@
+// Syntax Highlighter
+//
+// Applies a syntax highlighting color scheme CSS class to any element with the
+// `js-syntax-highlight` class
+//
+// ### Example Markup
+//
+//   <div class="js-syntax-highlight"></div>
+//
 (function() {
   $.fn.syntaxHighlight = function() {
     var $children;
     if ($(this).hasClass('js-syntax-highlight')) {
+      // Given the element itself, apply highlighting
       return $(this).addClass(gon.user_color_scheme);
     } else {
+      // Given a parent element, recurse to any of its applicable children
       $children = $(this).find('.js-syntax-highlight');
       if ($children.length) {
         return $children.syntaxHighlight();
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index 23eda7d44ca5ecb2b96d9baa20ed7f14a1fc5566..93421649ac7b247103f600725bc239c7c47050e4 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -129,16 +129,21 @@
       var currPage, currPages, newPages, pageParams, url;
       currPages = this.getTotalPages();
       currPage = this.getCurrentPage();
+      // Refresh if no remaining Todos
       if (!total) {
         location.reload();
         return;
       }
+      // Do nothing if no pagination
       if (!currPages) {
         return;
       }
       newPages = Math.ceil(total / this.getTodosPerPage());
+      // Includes query strings
       url = location.href;
+      // If new total of pages is different than we have now
       if (newPages !== currPages) {
+        // Redirect to previous page if there's one available
         if (currPages > 1 && currPage === currPages) {
           pageParams = {
             page: currPages - 1
@@ -155,6 +160,7 @@
       if (!todoLink) {
         return;
       }
+      // Allow Meta-Click or Mouse3-click to open in a new tab
       if (e.metaKey || e.which === 2) {
         e.preventDefault();
         return window.open(todoLink, '_blank');
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 78e159a7ed97c3c397a31a224e23f4da57ccd975..9b7be17c4fe9bc34c5685c7361126c65cc9178cd 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -2,6 +2,8 @@
   this.TreeView = (function() {
     function TreeView() {
       this.initKeyNav();
+      // Code browser tree slider
+      // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
       $(".tree-content-holder .tree-item").on('click', function(e) {
         var $clickedEl, path;
         $clickedEl = $(e.target);
@@ -15,6 +17,7 @@
           }
         }
       });
+      // Show the "Loading commit data" for only the first element
       $('span.log_loading:first').removeClass('hide');
     }
 
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
index 9ba847fb0c2c782199845235ed4477d88049ec85..ce2930c7fc727fd7ce6c870479f12002e827d273 100644
--- a/app/assets/javascripts/u2f/authenticate.js
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -1,3 +1,7 @@
+// Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
+//
+// State Flow #1: setup -> in_progress -> authenticated -> POST to server
+// State Flow #2: setup -> in_progress -> error -> setup
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
@@ -15,6 +19,17 @@
       this.appId = u2fParams.app_id;
       this.challenge = u2fParams.challenge;
       this.signRequests = u2fParams.sign_requests.map(function(request) {
+        // The U2F Javascript API v1.1 requires a single challenge, with
+        // _no challenges per-request_. The U2F Javascript API v1.0 requires a
+        // challenge per-request, which is done by copying the single challenge
+        // into every request.
+        //
+        // In either case, we don't need the per-request challenges that the server
+        // has generated, so we can remove them.
+        //
+        // Note: The server library fixes this behaviour in (unreleased) version 1.0.0.
+        // This can be removed once we upgrade.
+        // https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4
         return _(request).omit('challenge');
       });
     }
@@ -41,6 +56,7 @@
       })(this), 10);
     };
 
+    // Rendering #
     U2FAuthenticate.prototype.templates = {
       "notSupported": "#js-authenticate-u2f-not-supported",
       "setup": '#js-authenticate-u2f-setup',
@@ -75,6 +91,8 @@
 
     U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) {
       this.renderTemplate('authenticated');
+      // Prefer to do this instead of interpolating using Underscore templates
+      // because of JSON escaping issues.
       return this.container.find("#js-device-response").val(deviceResponse);
     };
 
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
index c87e0840df33feaab53ddf79faf4e2f419f62d64..926912fa9881e11619f2d8eb705f90043c46f5e7 100644
--- a/app/assets/javascripts/u2f/register.js
+++ b/app/assets/javascripts/u2f/register.js
@@ -1,3 +1,7 @@
+// Register U2F (universal 2nd factor) devices for users to authenticate with.
+//
+// State Flow #1: setup -> in_progress -> registered -> POST to server
+// State Flow #2: setup -> in_progress -> error -> setup
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
@@ -39,6 +43,7 @@
       })(this), 10);
     };
 
+    // Rendering #
     U2FRegister.prototype.templates = {
       "notSupported": "#js-register-u2f-not-supported",
       "setup": '#js-register-u2f-setup',
@@ -73,6 +78,8 @@
 
     U2FRegister.prototype.renderRegistered = function(deviceResponse) {
       this.renderTemplate('registered');
+      // Prefer to do this instead of interpolating using Underscore templates
+      // because of JSON escaping issues.
       return this.container.find("#js-device-response").val(deviceResponse);
     };
 
diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js
index e5e75701feecf9b663649dac086ae9470a64d85b..8a657780eb6590bd875af29441b3f18a24a46310 100644
--- a/app/assets/javascripts/user_tabs.js
+++ b/app/assets/javascripts/user_tabs.js
@@ -1,3 +1,61 @@
+// UserTabs
+//
+// Handles persisting and restoring the current tab selection and lazily-loading
+// content on the Users#show page.
+//
+// ### Example Markup
+//
+//   <ul class="nav-links">
+//     <li class="activity-tab active">
+//       <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
+//         Activity
+//       </a>
+//     </li>
+//     <li class="groups-tab">
+//       <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
+//         Groups
+//       </a>
+//     </li>
+//     <li class="contributed-tab">
+//       <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
+//         Contributed projects
+//       </a>
+//     </li>
+//     <li class="projects-tab">
+//       <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
+//         Personal projects
+//       </a>
+//     </li>
+//    <li class="snippets-tab">
+//       <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
+//       </a>
+//     </li>
+//   </ul>
+//
+//   <div class="tab-content">
+//     <div class="tab-pane" id="activity">
+//       Activity Content
+//     </div>
+//     <div class="tab-pane" id="groups">
+//       Groups Content
+//     </div>
+//     <div class="tab-pane" id="contributed">
+//       Contributed projects content
+//     </div>
+//     <div class="tab-pane" id="projects">
+//       Projects content
+//     </div>
+//     <div class="tab-pane" id="snippets">
+//       Snippets content
+//     </div>
+//   </div>
+//
+//   <div class="loading-status">
+//     <div class="loading">
+//       Loading Animation
+//     </div>
+//   </div>
+//
 (function() {
   var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
 
@@ -6,18 +64,23 @@
       this.tabShown = bind(this.tabShown, this);
       var i, item, len, ref, ref1, ref2, ref3;
       this.action = (ref = opts.action) != null ? ref : 'activity', this.defaultAction = (ref1 = opts.defaultAction) != null ? ref1 : 'activity', this.parentEl = (ref2 = opts.parentEl) != null ? ref2 : $(document);
+      // Make jQuery object if selector is provided
       if (typeof this.parentEl === 'string') {
         this.parentEl = $(this.parentEl);
       }
+      // Store the `location` object, allowing for easier stubbing in tests
       this._location = location;
+      // Set tab states
       this.loaded = {};
       ref3 = this.parentEl.find('.nav-links a');
       for (i = 0, len = ref3.length; i < len; i++) {
         item = ref3[i];
         this.loaded[$(item).attr('data-action')] = false;
       }
+      // Actions
       this.actions = Object.keys(this.loaded);
       this.bindEvents();
+      // Set active tab
       if (this.action === 'show') {
         this.action = this.defaultAction;
       }
@@ -25,6 +88,7 @@
     }
 
     UserTabs.prototype.bindEvents = function() {
+      // Toggle event listeners
       return this.parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]').on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', this.tabShown);
     };
 
@@ -74,6 +138,7 @@
             tabSelector = 'div#' + action;
             _this.parentEl.find(tabSelector).html(data.html);
             _this.loaded[action] = true;
+            // Fix tooltips
             return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
           };
         })(this)
@@ -97,13 +162,17 @@
 
     UserTabs.prototype.setCurrentAction = function(action) {
       var new_state, regExp;
+      // Remove possible actions from URL
       regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$');
       new_state = this._location.pathname;
+      // remove trailing slashes
       new_state = new_state.replace(/\/+$/, "");
       new_state = new_state.replace(regExp, '');
+      // Append the new action if we're on a tab other than 'activity'
       if (action !== this.defaultAction) {
         new_state += "/" + action;
       }
+      // Ensure parameters and hash come along for the ride
       new_state += this._location.search + this._location.hash;
       history.replaceState({
         turbolinks: true,
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
index 74ecf4f4cf90112908c64c0ecb559001e5bf7e51..3bd4c3c066f405e056816c8c8707454c39f8a17d 100644
--- a/app/assets/javascripts/users/calendar.js
+++ b/app/assets/javascripts/users/calendar.js
@@ -11,6 +11,8 @@
       this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
       this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
       this.months = [];
+      // Loop through the timestamps to create a group of objects
+      // The group of objects will be grouped based on the day of the week they are
       this.timestampsTmp = [];
       var group = 0;
 
@@ -27,14 +29,17 @@
         date.setDate(date.getDate() + i);
 
         var day = date.getDay();
-        var count = timestamps[date.getTime() * 0.001];
+        var count = timestamps[dateFormat(date, 'yyyy-mm-dd')];
 
+        // Create a new group array if this is the first day of the week
+        // or if is first object
         if ((day === 0 && i !== 0) || i === 0) {
           this.timestampsTmp.push([]);
           group++;
         }
 
         var innerArray = this.timestampsTmp[group - 1];
+        // Push to the inner array the values that will be used to render map
         innerArray.push({
           count: count || 0,
           date: date,
@@ -42,8 +47,10 @@
         });
       }
 
+      // Init color functions
       this.colorKey = this.initColorKey();
       this.color = this.initColor();
+      // Init the svg element
       this.renderSvg(group);
       this.renderDays();
       this.renderMonths();
@@ -52,8 +59,22 @@
       this.initTooltips();
     }
 
+    // Add extra padding for the last month label if it is also the last column
+    Calendar.prototype.getExtraWidthPadding = function(group) {
+      var extraWidthPadding = 0;
+      var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
+      var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
+
+      if (lastColMonth != secondLastColMonth) {
+        extraWidthPadding = 3;
+      }
+
+      return extraWidthPadding;
+    }
+
     Calendar.prototype.renderSvg = function(group) {
-      return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', (group + 1) * this.daySizeWithSpace).attr('height', 167).attr('class', 'contrib-calendar');
+      var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
+      return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
     };
 
     Calendar.prototype.renderDays = function() {
diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js
index b95faadc8e72f17e7cdb90eab9203622916bee02..d6e4d9f7ad82e841360ab4ab8d46a8738dcdcc51 100644
--- a/app/assets/javascripts/users/users_bundle.js
+++ b/app/assets/javascripts/users/users_bundle.js
@@ -3,5 +3,4 @@
 
 (function() {
 
-
 }).call(this);
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index bad82868ab045dbdcd18e83ee6610336757c917a..9c277998db4ac466b3e9f42e3c577356eeac16f9 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -81,6 +81,7 @@
                 if (term.length === 0) {
                   showDivider = 0;
                   if (firstUser) {
+                    // Move current user to the front of the list
                     for (index = j = 0, len = users.length; j < len; index = ++j) {
                       obj = users[index];
                       if (obj.username === firstUser) {
@@ -115,6 +116,7 @@
                 if (showDivider) {
                   users.splice(showDivider, 0, "divider");
                 }
+                // Send the data back
                 return callback(users);
               });
             },
@@ -139,6 +141,7 @@
             inputId: 'issue_assignee_id',
             hidden: function(e) {
               $selectbox.hide();
+              // display:block overrides the hide-collapse rule
               return $value.css('display', '');
             },
             clicked: function(user, $el, e) {
@@ -177,6 +180,7 @@
                   img = "<img src='" + avatar + "' class='avatar avatar-inline' width='30' />";
                 }
               }
+              // split into three parts so we can remove the username section if nessesary
               listWithName = "<li> <a href='#' class='dropdown-menu-user-link " + selected + "'> " + img + " <strong class='dropdown-menu-user-full-name'> " + user.name + " </strong>";
               listWithUserName = "<span class='dropdown-menu-user-username'> " + username + " </span>";
               listClosingTags = "</a> </li>";
@@ -215,6 +219,7 @@
                 };
                 if (query.term.length === 0) {
                   if (firstUser) {
+                    // Move current user to the front of the list
                     ref = data.results;
                     for (index = j = 0, len = ref.length; j < len; index = ++j) {
                       obj = ref[index];
@@ -271,6 +276,7 @@
               return _this.formatSelection.apply(_this, args);
             },
             dropdownCssClass: "ajax-users-dropdown",
+            // we do not want to escape markup since we are displaying html in results
             escapeMarkup: function(m) {
               return m;
             }
@@ -318,6 +324,8 @@
       });
     };
 
+    // Return users list. Filtered by query
+    // Only active users retrieved
     UsersSelect.prototype.users = function(query, options, callback) {
       var url;
       url = this.buildUrl(this.usersPath);
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index 71236c6a27d1c552a83d1b0f6b34d94d6f186bfd..777b32b41c9bb19d90f00b7a63ba90cf1397db26 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -1,21 +1,34 @@
-
+// Zen Mode (full screen) textarea
+//
 /*= provides zen_mode:enter */
-
-
 /*= provides zen_mode:leave */
-
-
+//
 /*= require jquery.scrollTo */
-
-
 /*= require dropzone */
-
-
 /*= require mousetrap */
-
-
 /*= require mousetrap/pause */
 
+//
+// ### Events
+//
+// `zen_mode:enter`
+//
+// Fired when the "Edit in fullscreen" link is clicked.
+//
+// **Synchronicity** Sync
+// **Bubbles** Yes
+// **Cancelable** No
+// **Target** a.js-zen-enter
+//
+// `zen_mode:leave`
+//
+// Fired when the "Leave Fullscreen" link is clicked.
+//
+// **Synchronicity** Sync
+// **Bubbles** Yes
+// **Cancelable** No
+// **Target** a.js-zen-leave
+//
 (function() {
   this.ZenMode = (function() {
     function ZenMode() {
@@ -40,6 +53,7 @@
         };
       })(this));
       $(document).on('keydown', function(e) {
+        // Esc
         if (e.keyCode === 27) {
           e.preventDefault();
           return $(document).trigger('zen_mode:leave');
@@ -52,6 +66,7 @@
       this.active_backdrop = $(backdrop);
       this.active_backdrop.addClass('fullscreen');
       this.active_textarea = this.active_backdrop.find('textarea');
+      // Prevent a user-resized textarea from persisting to fullscreen
       this.active_textarea.removeAttr('style');
       return this.active_textarea.focus();
     };
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 1fec61bdba1ecaef6dce3eb6090c2c6530456ac5..1e9a45c19b886e709bd8bc3d3fc09a812ac08d96 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -8,65 +8,44 @@
 // Copyright (c) 2016 Daniel Eden
 
 .animated {
-  -webkit-animation-duration: 1s;
-  animation-duration: 1s;
-  -webkit-animation-fill-mode: both;
-  animation-fill-mode: both;
-}
-
-.animated.infinite {
-  -webkit-animation-iteration-count: infinite;
-  animation-iteration-count: infinite;
-}
+  @include webkit-prefix(animation-duration, 1s);
+  @include webkit-prefix(animation-fill-mode, both);
 
-.animated.hinge {
-  -webkit-animation-duration: 2s;
-  animation-duration: 2s;
-}
+  &.infinite {
+    @include webkit-prefix(animation-iteration-count, infinite);
+  }
 
-.animated.flipOutX,
-.animated.flipOutY,
-.animated.bounceIn,
-.animated.bounceOut {
-  -webkit-animation-duration: .75s;
-  animation-duration: .75s;
-}
+  &.once {
+    @include webkit-prefix(animation-iteration-count, 1);
+  }
 
-@-webkit-keyframes pulse {
-  from {
-    -webkit-transform: scale3d(1, 1, 1);
-    transform: scale3d(1, 1, 1);
+  &.hinge {
+    @include webkit-prefix(animation-duration, 2s);
   }
 
-  50% {
-    -webkit-transform: scale3d(1.05, 1.05, 1.05);
-    transform: scale3d(1.05, 1.05, 1.05);
+  &.flipOutX,
+  &.flipOutY,
+  &.bounceIn,
+  &.bounceOut {
+    @include webkit-prefix(animation-duration, .75s);
   }
 
-  to {
-    -webkit-transform: scale3d(1, 1, 1);
-    transform: scale3d(1, 1, 1);
+  &.short {
+    @include webkit-prefix(animation-duration, 321ms);
+    @include webkit-prefix(animation-fill-mode, none);
   }
 }
 
-@keyframes pulse {
-  from {
-    -webkit-transform: scale3d(1, 1, 1);
-    transform: scale3d(1, 1, 1);
+@include keyframes(pulse) {
+  from, to {
+    @include webkit-prefix(transform, scale3d(1, 1, 1));
   }
 
   50% {
-    -webkit-transform: scale3d(1.05, 1.05, 1.05);
-    transform: scale3d(1.05, 1.05, 1.05);
-  }
-
-  to {
-    -webkit-transform: scale3d(1, 1, 1);
-    transform: scale3d(1, 1, 1);
+    @include webkit-prefix(transform, scale3d(1.05, 1.05, 1.05));
   }
 }
 
 .pulse {
-  -webkit-animation-name: pulse;
-  animation-name: pulse;
+  @include webkit-prefix(animation-name, pulse);
 }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 7ce203d2ec7bc0e6d3ea50b2dbf7683480baa011..f5223207f3a8bdd8d31a99751bc96764f6e4b325 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -249,6 +249,10 @@
   > .controls {
     float: right;
   }
+
+  .new-branch {
+    margin-top: 3px;
+  }
 }
 
 .content-block-small {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index e3be45ba1dca12457c3aa2eec81834c88f46b48a..76a3c08369734c4e3afcf3bcfc21f27ce62669ca 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -63,7 +63,7 @@
     &.image_file {
       background: #eee;
       text-align: center;
-      
+
       img {
         padding: 20px;
         max-width: 80%;
@@ -94,7 +94,6 @@
     &.blame {
       table {
         border: none;
-        box-shadow: none;
         margin: 0;
       }
       tr {
@@ -108,19 +107,10 @@
           border-right: none;
         }
       }
-      img.avatar {
-        border: 0 none;
-        float: none;
-        margin: 0;
-        padding: 0;
-      }
       td.blame-commit {
+        padding: 0 10px;
+        min-width: 400px;
         background: $gray-light;
-        min-width: 350px;
-
-        .commit-author-link {
-          color: #888;
-        }
       }
       td.line-numbers {
         float: none;
@@ -133,12 +123,6 @@
       }
       td.lines {
         padding: 0;
-        code {
-          font-family: $monospace_font;
-        }
-        pre {
-          margin: 0;
-        }
       }
     }
 
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 9209347f9bc8fab57347638fc1bc136d5c477808..19827943385a10ca75db2362a9d2f14eb46cb6e2 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -1,6 +1,10 @@
 .filter-item {
   margin-right: 6px;
   vertical-align: top;
+
+  &.reset-filters {
+    padding: 7px;
+  }
 }
 
 @media (min-width: $screen-sm-min) {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 1036219172e33df0c2c463ada7a16673e9501b93..d4a030f7f7ae1c76462b3663504e7e1c6ae4829c 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -77,10 +77,6 @@ header {
       }
     }
 
-    &.header-collapsed {
-      padding: 0 16px;
-    }
-
     .side-nav-toggle {
       position: absolute;
       left: -10px;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 965fcc06518a350e0ee4a6836911757e5ce16274..46af18580d5b451474d0ecda5a97a49cb1ac07cb 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -162,6 +162,10 @@ ul.content-list {
           margin-right: 0;
         }
       }
+
+      .no-comments {
+        opacity: 0.5;
+      }
     }
 
     // When dragging a list item
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 00f92cef9a4a455d93ba70797e0a7d77c53d8cca..1ec08cdef231f7a50376add63cb76dd711186245 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -85,3 +85,13 @@
   #{'-webkit-' + $property}: $value;
   #{$property}: $value;
 }
+
+@mixin keyframes($animation-name) {
+  @-webkit-keyframes #{$animation-name} {
+    @content;
+  }
+
+  @keyframes #{$animation-name} {
+    @content;
+  }
+}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 367c7d019441b4c5500ec106914ab05c9070cb60..76b93b23b957d3118f4a611e2d4b5d27404d0cfa 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -79,10 +79,6 @@
     padding-left: 15px !important;
   }
 
-  .issue-info, .merge-request-info {
-    display: none;
-  }
-
   .nav-links, .nav-links {
     li a {
       font-size: 14px;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 015fe3debf9b0faf6ec0dac10c1b9196ed14c83e..3b7de4b57bb5277f29a620f8efe7fff2d4b88a96 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,6 +1,5 @@
 .page-with-sidebar {
-  padding-top: $header-height;
-  padding-bottom: 25px;
+  padding: $header-height 0 25px;
   transition: padding $sidebar-transition-duration;
 
   &.page-sidebar-pinned {
@@ -15,6 +14,7 @@
     bottom: 0;
     left: 0;
     height: 100%;
+    width: 0;
     overflow: hidden;
     transition: width $sidebar-transition-duration;
     @include box-shadow(2px 0 16px 0 $black-transparent);
@@ -128,10 +128,8 @@
 
     .fa {
       transition: transform .15s;
-    }
 
-    &.is-active {
-      .fa {
+      .page-sidebar-pinned & {
         transform: rotate(90deg);
       }
     }
@@ -152,14 +150,6 @@
   }
 }
 
-.page-sidebar-collapsed {
-  padding-left: 0;
-
-  .sidebar-wrapper {
-    width: 0;
-  }
-}
-
 .page-sidebar-expanded {
   .sidebar-wrapper {
     width: $sidebar_width;
@@ -175,7 +165,7 @@
   }
 }
 
-header.header-pinned-nav {
+header.header-sidebar-pinned {
   @media (min-width: $sidebar-breakpoint) {
     padding-left: ($sidebar_width + $gl-padding);
 
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 3f8433a0e7f67a602ffa9ec93614f86ce3cec0b3..2582cde5a71bb2ccd02c756b5542b018ee160155 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -164,7 +164,7 @@
       text-decoration: none;
 
       &:after {
-        content: url('icon_anchor.svg');
+        content: image-url('icon_anchor.svg');
         visibility: hidden;
       }
     }
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 5faedfedd660fe8bcafab778184a3dd81b260a30..9282e0ae03becbce23e1dc9044580f9cb0df648a 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -93,11 +93,8 @@
 }
 
 .award-control {
-  margin-right: 5px;
-  margin-bottom: 5px;
-  padding-left: 5px;
-  padding-right: 5px;
-  line-height: 20px;
+  margin: 3px 5px 3px 0;
+  padding: 6px 5px;
   outline: 0;
 
   &:hover,
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 037278bb083a38064b6aa602d66df98216c8fff7..9c84dceed0574b8860cae5f11e0621df7ceaba1f 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -1,3 +1,4 @@
+lex
 [v-cloak] {
   display: none;
 }
@@ -18,6 +19,10 @@
   }
 }
 
+.is-ghost {
+  opacity: 0.3;
+}
+
 .dropdown-menu-issues-board-new {
   width: 320px;
 
@@ -34,47 +39,13 @@
   > p {
     margin: 0;
     font-size: 14px;
-    color: #9c9c9c;
   }
 }
 
 .issue-boards-page {
-  .content-wrapper {
-    display: -webkit-flex;
-    display: flex;
-    -webkit-flex-direction: column;
-    flex-direction: column;
-  }
-
-  .sub-nav,
-  .issues-filters {
-    -webkit-flex: none;
-    flex: none;
-  }
-
   .page-with-sidebar {
-    display: -webkit-flex;
-    display: flex;
-    min-height: 100vh;
-    max-height: 100vh;
     padding-bottom: 0;
   }
-
-  .issue-boards-content {
-    display: -webkit-flex;
-    display: flex;
-    -webkit-flex: 1;
-    flex: 1;
-    width: 100%;
-
-    .content {
-      display: -webkit-flex;
-      display: flex;
-      -webkit-flex-direction: column;
-      flex-direction: column;
-      width: 100%;
-    }
-  }
 }
 
 .boards-app-loading {
@@ -83,46 +54,38 @@
 }
 
 .boards-list {
-  display: -webkit-flex;
-  display: flex;
-  -webkit-flex: 1;
-  flex: 1;
-  -webkit-flex-basis: 0;
-  flex-basis: 0;
-  min-height: calc(100vh - 152px);
-  max-height: calc(100vh - 152px);
+  height: calc(100vh - 152px);
+  width: 100%;
   padding-top: 25px;
+  padding-bottom: 25px;
   padding-right: ($gl-padding / 2);
   padding-left: ($gl-padding / 2);
   overflow-x: scroll;
+  white-space: nowrap;
 
   @media (min-width: $screen-sm-min) {
+    height: 475px; // Needed for PhantomJS
+    height: calc(100vh - 220px);
     min-height: 475px;
-    max-height: none;
   }
 }
 
 .board {
-  display: -webkit-flex;
-  display: flex;
-  min-width: calc(85vw - 15px);
-  max-width: calc(85vw - 15px);
-  margin-bottom: 25px;
+  display: inline-block;
+  width: calc(85vw - 15px);
+  height: 100%;
   padding-right: ($gl-padding / 2);
   padding-left: ($gl-padding / 2);
+  white-space: normal;
+  vertical-align: top;
 
   @media (min-width: $screen-sm-min) {
-    min-width: 400px;
-    max-width: 400px;
+    width: 400px;
   }
 }
 
 .board-inner {
-  display: -webkit-flex;
-  display: flex;
-  -webkit-flex-direction: column;
-  flex-direction: column;
-  width: 100%;
+  height: 100%;
   font-size: $issue-boards-font-size;
   background: $background-color;
   border: 1px solid $border-color;
@@ -193,45 +156,31 @@
 }
 
 .board-list {
-  -webkit-flex: 1;
-  flex: 1;
-  height: 400px;
+  height: calc(100% - 49px);
   margin-bottom: 0;
   padding: 5px;
+  list-style: none;
   overflow-y: scroll;
   overflow-x: hidden;
 }
 
 .board-list-loading {
   margin-top: 10px;
-  font-size: 26px;
-}
-
-.is-ghost {
-  opacity: 0.3;
+  font-size: (26px / $issue-boards-font-size) * 1em;
 }
 
 .card {
   position: relative;
-  width: 100%;
   padding: 10px $gl-padding;
   background: #fff;
   border-radius: $border-radius-default;
   box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5);
   list-style: none;
 
-  &.user-can-drag {
-    padding-left: $gl-padding;
-  }
-
   &:not(:last-child) {
     margin-bottom: 5px;
   }
 
-  a {
-    cursor: pointer;
-  }
-
   .label {
     border: 0;
     outline: 0;
@@ -256,14 +205,13 @@
   line-height: 25px;
 
   .label {
-    margin-right: 4px;
+    margin-right: 5px;
     font-size: (14px / $issue-boards-font-size) * 1em;
   }
 }
 
 .card-number {
-  margin-right: 8px;
-  font-weight: 500;
+  margin-right: 5px;
 }
 
 .issue-boards-search {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 614405aa5c190d658e93dfdaa018937c6bab5dcb..c879074c7fee48ffebff65531eb8540d16b9501d 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -48,12 +48,6 @@
       margin-bottom: 10px;
     }
   }
-
-  .page-sidebar-collapsed {
-    .scroll-controls {
-      left: 70px;
-    }
-  }
 }
 
 .build-header {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 21cee2e3a70cced31cea9364f02e9ac5278b8f24..b8ef76cc74e2d7b1343585b84f4d68b3da7fbe6e 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -68,6 +68,11 @@
       border-collapse: separate;
       margin: 0;
       padding: 0;
+      table-layout: fixed;
+
+      .diff-line-num {
+        width: 50px;
+      }
 
       .line_holder td {
         line-height: $code_line_height;
@@ -98,10 +103,6 @@
     }
 
     tr.line_holder.parallel {
-      .old_line, .new_line {
-        min-width: 50px;
-      }
-
       td.line_content.parallel {
         width: 46%;
       }
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 46c4a11aa2eb07ad642e3d68bdedd413bfe170e0..41079b6eeb55b988a9dba874b82d96320eff6f6d 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -206,7 +206,7 @@
     padding-top: 0;
 
     .block {
-      width: $sidebar_collapsed_width - 1px;
+      width: $sidebar_collapsed_width - 2px;
       margin-left: -19px;
       padding: 15px 0 0;
       border-bottom: none;
@@ -404,3 +404,18 @@
     margin-bottom: $gl-padding;
   }
 }
+
+.issuable-list {
+  li {
+    .issue-check {
+      float: left;
+      padding-right: $gl-padding;
+      margin-bottom: 10px;
+      min-width: 15px;
+
+      .selected_issue {
+        vertical-align: text-top;
+      }
+    }
+  }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index d14224ed00f6cce19d3d0eb6597282aca37bf2a7..3ac34cbc829f1a3bf64435626973dd0fc9054ed9 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -7,24 +7,9 @@
       margin-bottom: 2px;
     }
 
-    .issue-check {
-      float: left;
-      padding-right: 16px;
-      margin-bottom: 10px;
-      min-width: 15px;
-
-      .selected_issue {
-        vertical-align: text-top;
-      }
-    }
-
     .issue-labels {
       display: inline-block;
     }
-
-    .issue-no-comments {
-      opacity: 0.5;
-    }
   }
 }
 
@@ -48,6 +33,15 @@ form.edit-issue {
   margin: 0;
 }
 
+ul.related-merge-requests > li {
+  display: -ms-flexbox;
+  display: -webkit-flex;
+  display: flex;
+  .merge-request-id {
+    flex-shrink: 0;
+  }
+}
+
 .merge-requests-title, .related-branches-title {
   font-size: 16px;
   font-weight: 600;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 7fdd79fa8b902ef5c2da13685ea0466ae6dfac94..96c0608686771945a5f32ab72c0019422b18ce89 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -231,10 +231,6 @@
   .merge-request-labels {
     display: inline-block;
   }
-
-  .merge-request-no-comments {
-    opacity: 0.5;
-  }
 }
 
 .merge-request-angle {
@@ -375,7 +371,7 @@
   }
 }
 
-.mr-version-switch {
+.mr-version-controls {
   background: $background-color;
   padding: $gl-btn-padding;
   color: $gl-placeholder-color;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 2d66ab25da6a7a91fd854568ab82fad5fc64f54f..1b4d12d3053fbe37feea65361e0d5c791630bb08 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -147,14 +147,37 @@
   }
 
   .stage-cell {
-    text-align: center;
+    font-size: 0;
 
     svg {
       height: 18px;
       width: 18px;
+      position: relative;
+      z-index: 2;
       vertical-align: middle;
       overflow: visible;
     }
+
+    .stage-container {
+      display: inline-block;
+      position: relative;
+      margin-right: 6px;
+
+      .tooltip {
+        white-space: nowrap;
+      }
+
+      &:not(:last-child) {
+        &::after {
+          content: '';
+          width: 8px;
+          position: absolute;;
+          right: -7px;
+          bottom: 8px;
+          border-bottom: 2px solid $border-color;
+        }
+      }
+    }
   }
 
   .duration,
@@ -318,9 +341,17 @@
 
     .build-content {
       width: 130px;
-      white-space: nowrap;
-      overflow: hidden;
-      text-overflow: ellipsis;
+
+      .ci-status-text {
+        width: 110px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        vertical-align: middle;
+        display: inline-block;
+        position: relative;
+        top: -1px;
+      }
 
       a {
         color: $layout-link-gray;
@@ -331,13 +362,74 @@
             text-decoration: underline;
           }
         }
+      }
+
+      .dropdown-menu-toggle {
+        border: none;
+        width: auto;
+        padding: 0;
+        color: $layout-link-gray;
+
+        .ci-status-text {
+          width: 80px;
+        }
+      }
+
+      .grouped-pipeline-dropdown {
+        padding: 8px 0;
+        width: 200px;
+        left: auto;
+        right: -214px;
+        top: -9px;
+
+        a:hover {
+          .ci-status-text {
+            text-decoration: none;
+          }
+        }
 
+        .ci-status-text {
+          width: 145px;
+        }
+
+        .arrow {
+          &:before,
+          &:after {
+            content: '';
+            display: inline-block;
+            position: absolute;
+            width: 0;
+            height: 0;
+            border-color: transparent;
+            border-style: solid;
+            top: 18px;
+          }
+
+          &:before {
+            left: -5px;
+            margin-top: -6px;
+            border-width: 7px 5px 7px 0;
+            border-right-color: $border-color;
+          }
+
+          &:after {
+            left: -4px;
+            margin-top: -9px;
+            border-width: 10px 7px 10px 0;
+            border-right-color: $white-light;
+          }
+        }
+      }
+
+      .badge {
+        background-color: $gray-dark;
+        color: $layout-link-gray;
+        font-weight: normal;
       }
     }
 
     svg {
-      position: relative;
-      top: 2px;
+      vertical-align: middle;
       margin-right: 5px;
     }
 
@@ -442,7 +534,7 @@
       width: 21px;
       height: 25px;
       position: absolute;
-      top: -28.5px;
+      top: -29px;
       border-top: 2px solid $border-color;
     }
 
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index f2db373da52736bf60a3b6a88850414d100c5d55..db46d8072cee3769fb223fb2dbf042be58b3d76e 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -334,6 +334,10 @@ a.deploy-project-label {
   a {
     color: $gl-dark-link-color;
   }
+
+  .dropdown-menu {
+    width: 240px;
+  }
 }
 
 .last-push-widget {
@@ -723,9 +727,15 @@ pre.light-well {
   }
 }
 
-.project-refs-form {
-  .dropdown-menu {
-    width: 300px;
+.project-refs-form .dropdown-menu, .dropdown-menu-projects {
+  width: 300px;
+
+  @media (min-width: $screen-sm-min) {
+    width: 500px;
+  }
+
+  a {
+    white-space: normal;
   }
 }
 
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 2aa939b7dc389197b6b17e747ad9ac68617b82e3..5270aea4e797f3f4212b5ead3e6518008773b9e9 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -2,20 +2,6 @@
   padding: 2px;
 }
 
-.snippet-holder {
-  margin-bottom: -$gl-padding;
-
-  .file-holder {
-    border-top: 0;
-  }
-
-  .file-actions {
-    .btn-clipboard {
-      @extend .btn;
-    }
-  }
-}
-
 .markdown-snippet-copy {
   position: fixed;
   top: -10px;
@@ -24,29 +10,18 @@
   max-width: 0;
 }
 
-.file-holder.snippet-file-content {
-  padding-bottom: $gl-padding;
-  border-bottom: 1px solid $border-color;
-
-  .file-title {
-    padding-top: $gl-padding;
-    padding-bottom: $gl-padding;
-  }
-
-  .file-actions {
-    top: 12px;
-  }
-
-  .file-content {
-    border-left: 1px solid $border-color;
-    border-right: 1px solid $border-color;
-    border-bottom: 1px solid $border-color;
+.snippet-file-content {
+  border-radius: 3px;
+  .btn-clipboard {
+    @extend .btn;
   }
 }
 
 .snippet-title {
   font-size: 24px;
-  font-weight: normal;
+  font-weight: 600;
+  padding: $gl-padding;
+  padding-left: 0;
 }
 
 .snippet-actions {
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index cdfa8d91a2880d3a65d8a88a8e15a7b19a50f4d2..aed77d0358a7b90cbd8335afe12e80046d0bc1e8 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -60,6 +60,14 @@ class Admin::GroupsController < Admin::ApplicationController
   end
 
   def group_params
-    params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level, :request_access_enabled)
+    params.require(:group).permit(
+      :avatar,
+      :description,
+      :lfs_enabled,
+      :name,
+      :path,
+      :request_access_enabled,
+      :visibility_level
+    )
   end
 end
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index a7af3cb83450b1e6d5fc23559345d7f4accee53f..e06d12cfce16ce5fe0784fb7566045a28341630e 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -7,19 +7,14 @@ module Ci
 
     def create
       @content = params[:content]
+      @error = Ci::GitlabCiYamlProcessor.validation_message(@content)
+      @status = @error.blank?
 
-      if @content.blank?
-        @status = false
-        @error = "Please provide content of .gitlab-ci.yml"
-      else
+      if @error.blank?
         @config_processor = Ci::GitlabCiYamlProcessor.new(@content)
         @stages = @config_processor.stages
         @builds = @config_processor.builds
-        @status = true
       end
-    rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
-      @error = e.message
-      @status = false
     rescue
       @error = 'Undefined error'
       @status = false
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index ba07cea569c870111ae7b623aa157732c5d87eb3..d5a8a962662ad0a6c4ffca0d5c643d2f37580ed1 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -62,6 +62,7 @@ module AuthenticatesWithTwoFactor
       session.delete(:otp_user_id)
       session.delete(:challenges)
 
+      remember_me(user) if user_params[:remember_me] == '1'
       sign_in(user)
     else
       flash.now[:alert] = 'Authentication via U2F device failed.'
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index f2b8f297bc27886a536478ba77aeb82304cfaa56..dacb5679dd300fde4a034ba762960ee5a41610e7 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -7,8 +7,7 @@ module CreatesCommit
     commit_params = @commit_params.merge(
       source_project: @project,
       source_branch: @ref,
-      target_branch: @target_branch,
-      previous_path: @previous_path
+      target_branch: @target_branch
     )
 
     result = service.new(@tree_edit_project, current_user, commit_params).execute
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index f40b62446e5ecbd223e5c7b282e25a4d9c64d31e..bb32bc502e65b249c3062d31a935491e3dd6aea3 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -3,21 +3,54 @@ module IssuableActions
 
   included do
     before_action :authorize_destroy_issuable!, only: :destroy
+    before_action :authorize_admin_issuable!, only: :bulk_update
   end
 
   def destroy
     issuable.destroy
+    destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
+    TodoService.new.public_send(destroy_method, issuable, current_user)
 
     name = issuable.class.name.titleize.downcase
     flash[:notice] = "The #{name} was successfully deleted."
     redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
   end
 
+  def bulk_update
+    result = Issuable::BulkUpdateService.new(project, current_user, bulk_update_params).execute(resource_name)
+    quantity = result[:count]
+
+    render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
+  end
+
   private
 
   def authorize_destroy_issuable!
-    unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable)
+    unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
       return access_denied!
     end
   end
+
+  def authorize_admin_issuable!
+    unless can?(current_user, :"admin_#{resource_name}", @project)
+      return access_denied!
+    end
+  end
+
+  def bulk_update_params
+    params.require(:update).permit(
+      :issuable_ids,
+      :assignee_id,
+      :milestone_id,
+      :state_event,
+      :subscription_event,
+      label_ids: [],
+      add_label_ids: [],
+      remove_label_ids: []
+    )
+  end
+
+  def resource_name
+    @resource_name ||= controller_name.singularize
+  end
 end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index cb82d62616c863835f5b77eac7851abef4054bf7..b83c3a872cf07bb9caaf583ae2fec7f83640fd94 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -121,7 +121,17 @@ class GroupsController < Groups::ApplicationController
   end
 
   def group_params
-    params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock, :request_access_enabled)
+    params.require(:group).permit(
+      :avatar,
+      :description,
+      :lfs_enabled,
+      :name,
+      :path,
+      :public,
+      :request_access_enabled,
+      :share_with_group_lock,
+      :visibility_level
+    )
   end
 
   def load_events
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 66ebdcc37a79d32cae70621f86040934088acd46..06d967747545e30b43ffb3232c8c8c7d8c2a1dd3 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -11,7 +11,10 @@ class JwtController < ApplicationController
     service = SERVICES[params[:service]]
     return head :not_found unless service
 
-    result = service.new(@project, @user, auth_params).execute
+    @authentication_result ||= Gitlab::Auth::Result.new
+
+    result = service.new(@authentication_result.project, @authentication_result.actor, auth_params).
+      execute(authentication_abilities: @authentication_result.authentication_abilities)
 
     render json: result, status: result[:http_status]
   end
@@ -20,30 +23,23 @@ class JwtController < ApplicationController
 
   def authenticate_project_or_user
     authenticate_with_http_basic do |login, password|
-      # if it's possible we first try to authenticate project with login and password
-      @project = authenticate_project(login, password)
-      return if @project
-
-      @user = authenticate_user(login, password)
-      return if @user
+      @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
 
-      render_403
+      render_403 unless @authentication_result.success? &&
+        (@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
     end
+  rescue Gitlab::Auth::MissingPersonalTokenError
+    render_missing_personal_token
   end
 
-  def auth_params
-    params.permit(:service, :scope, :account, :client_id)
+  def render_missing_personal_token
+    render plain: "HTTP Basic: Access denied\n" \
+                  "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
+                  "You can generate one at #{profile_personal_access_tokens_url}",
+           status: 401
   end
 
-  def authenticate_project(login, password)
-    if login == 'gitlab-ci-token'
-      Project.with_builds_enabled.find_by(runners_token: password)
-    end
-  end
-
-  def authenticate_user(login, password)
-    user = Gitlab::Auth.find_with_user_password(login, password)
-    Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
-    user
+  def auth_params
+    params.permit(:service, :scope, :account, :client_id)
   end
 end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index cdf9a04bacfcfb80d50e15bf590508db4e1c2988..b78cc6585ba1b250f3e851e1e198526c663ff086 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -38,12 +38,7 @@ class Projects::BlobController < Projects::ApplicationController
   end
 
   def update
-    if params[:file_path].present?
-      @previous_path = @path
-      @path = params[:file_path]
-      @commit_params[:file_path] = @path
-    end
-
+    @path = params[:file_path] if params[:file_path].present?
     after_edit_path =
       if from_merge_request && @target_branch == @ref
         diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
@@ -143,6 +138,8 @@ class Projects::BlobController < Projects::ApplicationController
           params[:file_name] = params[:file].original_filename
         end
         File.join(@path, params[:file_name])
+      elsif params[:file_path].present?
+        params[:file_path]
       else
         @path
       end
@@ -155,6 +152,7 @@ class Projects::BlobController < Projects::ApplicationController
     @commit_params = {
       file_path: @file_path,
       commit_message: params[:commit_message],
+      previous_path: @path,
       file_content: params[:content],
       file_content_encoding: params[:encoding],
       last_commit_sha: params[:last_commit_sha]
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 77934ff9962865813007c1323847910d64c19953..3b2e35a7a0545eb89fd1ad6605b365d9cb069cff 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -35,7 +35,11 @@ class Projects::BuildsController < Projects::ApplicationController
     respond_to do |format|
       format.html
       format.json do
-        render json: @build.to_json(methods: :trace_html)
+        render json: {
+          id: @build.id,
+          status: @build.status,
+          trace_html: @build.trace_html
+        }
       end
     end
   end
@@ -74,7 +78,7 @@ class Projects::BuildsController < Projects::ApplicationController
   def erase
     @build.erase(erased_by: current_user)
     redirect_to namespace_project_build_path(project.namespace, project, @build),
-                notice: "Build has been sucessfully erased!"
+                notice: "Build has been successfully erased!"
   end
 
   def raw
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index a5b4031c30f8e2e09551570881cff71acd9d282a..d1a2c52d80a89749fbe2d4526f1461ea19470bc7 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -4,7 +4,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   include ActionController::HttpAuthentication::Basic
   include KerberosSpnegoHelper
 
-  attr_reader :user
+  attr_reader :authentication_result
+
+  delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
+
+  alias_method :user, :actor
 
   # Git clients will not know what authenticity token to send along
   skip_before_action :verify_authenticity_token
@@ -15,32 +19,25 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   private
 
   def authenticate_user
+    @authentication_result = Gitlab::Auth::Result.new
+
     if project && project.public? && download_request?
       return # Allow access
     end
 
     if allow_basic_auth? && basic_auth_provided?
       login, password = user_name_and_password(request)
-      auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
-
-      if auth_result.type == :ci && download_request?
-        @ci = true
-      elsif auth_result.type == :oauth && !download_request?
-        # Not allowed
-      elsif auth_result.type == :missing_personal_token
-        render_missing_personal_token
-        return # Render above denied access, nothing left to do
-      else
-        @user = auth_result.user
-      end
 
-      if ci? || user
+      if handle_basic_authentication(login, password)
         return # Allow access
       end
     elsif allow_kerberos_spnego_auth? && spnego_provided?
-      @user = find_kerberos_user
+      user = find_kerberos_user
 
       if user
+        @authentication_result = Gitlab::Auth::Result.new(
+          user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities)
+
         send_final_spnego_response
         return # Allow access
       end
@@ -48,6 +45,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
 
     send_challenges
     render plain: "HTTP Basic: Access denied\n", status: 401
+  rescue Gitlab::Auth::MissingPersonalTokenError
+    render_missing_personal_token
   end
 
   def basic_auth_provided?
@@ -114,7 +113,42 @@ class Projects::GitHttpClientController < Projects::ApplicationController
     render plain: 'Not Found', status: :not_found
   end
 
+  def handle_basic_authentication(login, password)
+    @authentication_result = Gitlab::Auth.find_for_git_client(
+      login, password, project: project, ip: request.ip)
+
+    return false unless @authentication_result.success?
+
+    if download_request?
+      authentication_has_download_access?
+    else
+      authentication_has_upload_access?
+    end
+  end
+
   def ci?
-    @ci.present?
+    authentication_result.ci? &&
+      authentication_project &&
+      authentication_project == project
+  end
+
+  def authentication_has_download_access?
+    has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code)
+  end
+
+  def authentication_has_upload_access?
+    has_authentication_ability?(:push_code)
+  end
+
+  def has_authentication_ability?(capability)
+    (authentication_abilities || []).include?(capability)
+  end
+
+  def authentication_project
+    authentication_result.project
+  end
+
+  def verify_workhorse_api!
+    Gitlab::Workhorse.verify_api_request!(request.headers)
   end
 end
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index b4373ef89efa6575ddadb01311c627d3762346e8..662d38b10a5867f49c1fe3c76e6425df8ad4ede5 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -1,6 +1,8 @@
 # This file should be identical in GitLab Community Edition and Enterprise Edition
 
 class Projects::GitHttpController < Projects::GitHttpClientController
+  before_action :verify_workhorse_api!
+
   # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
   # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
   def info_refs
@@ -56,6 +58,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
   end
 
   def render_ok
+    set_workhorse_internal_api_content_type
     render json: Gitlab::Workhorse.git_http_ok(repository, user)
   end
 
@@ -83,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
   end
 
   def access
-    @access ||= Gitlab::GitAccess.new(user, project, 'http')
+    @access ||= Gitlab::GitAccess.new(user, project, 'http', authentication_abilities: authentication_abilities)
   end
 
   def access_check
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 72d2d3618784313af1eda1092fff9cbb0c2a7a5c..de02e28e384243f4483f959fb504e7f5ec73344f 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -20,9 +20,6 @@ class Projects::IssuesController < Projects::ApplicationController
   # Allow modify issue
   before_action :authorize_update_issue!, only: [:edit, :update]
 
-  # Allow issues bulk update
-  before_action :authorize_admin_issues!, only: [:bulk_update]
-
   respond_to :html
 
   def index
@@ -168,16 +165,6 @@ class Projects::IssuesController < Projects::ApplicationController
     end
   end
 
-  def bulk_update
-    result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
-
-    respond_to do |format|
-      format.json do
-        render json: { notice: "#{result[:count]} issues updated" }
-      end
-    end
-  end
-
   protected
 
   def issue
@@ -237,17 +224,4 @@ class Projects::IssuesController < Projects::ApplicationController
       :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: []
     )
   end
-
-  def bulk_update_params
-    params.require(:update).permit(
-      :issues_ids,
-      :assignee_id,
-      :milestone_id,
-      :state_event,
-      :subscription_event,
-      label_ids: [],
-      add_label_ids: [],
-      remove_label_ids: []
-    )
-  end
 end
diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb
index 69066cb40e671286810267fb4650699bf66aea58..9005b104e901f2cda7a6f9397f23194e641a5b83 100644
--- a/app/controllers/projects/lfs_storage_controller.rb
+++ b/app/controllers/projects/lfs_storage_controller.rb
@@ -3,6 +3,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
 
   before_action :require_lfs_enabled!
   before_action :lfs_check_access!
+  before_action :verify_workhorse_api!, only: [:upload_authorize]
 
   def download
     lfs_object = LfsObject.find_by_oid(oid)
@@ -15,14 +16,8 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
   end
 
   def upload_authorize
-    render(
-      json: {
-        StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload",
-        LfsOid: oid,
-        LfsSize: size,
-      },
-      content_type: 'application/json; charset=utf-8'
-    )
+    set_workhorse_internal_api_content_type
+    render json: Gitlab::Workhorse.lfs_upload_ok(oid, size)
   end
 
   def upload_finalize
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 4f9ca0097a1478e3783132ce24345c78c1e1b2b5..0288ee8771711035c84fef09368fb0a6006dcacd 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -90,16 +90,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController
         @merge_request.merge_request_diff
       end
 
+    @merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff
+    @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
+
+    if params[:start_sha].present?
+      @start_sha = params[:start_sha]
+      @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
+
+      unless @start_version
+        render_404
+      end
+    end
+
     respond_to do |format|
       format.html { define_discussion_vars }
       format.json do
-        unless @merge_request_diff.latest?
-          # Disable comments if browsing older version of the diff
-          @diff_notes_disabled = true
+        if @start_sha
+          compared_diff_version
+        else
+          original_diff_version
         end
 
-        @diffs = @merge_request_diff.diffs(diff_options)
-
         render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
       end
     end
@@ -417,17 +428,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   end
 
   def validates_merge_request
-    # If source project was removed (Ex. mr from fork to origin)
-    return invalid_mr unless @merge_request.source_project
+    # If source project was removed and merge request for some reason
+    # wasn't close (Ex. mr from fork to origin)
+    return invalid_mr if !@merge_request.source_project && @merge_request.open?
 
     # Show git not found page
     # if there is no saved commits between source & target branch
     if @merge_request.commits.blank?
       # and if target branch doesn't exist
       return invalid_mr unless @merge_request.target_branch_exists?
-
-      # or if source branch doesn't exist
-      return invalid_mr unless @merge_request.source_branch_exists?
     end
   end
 
@@ -529,4 +538,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
     @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
   end
+
+  def compared_diff_version
+    @diff_notes_disabled = true
+    @diffs = @merge_request_diff.compare_with(@start_sha).diffs(diff_options)
+  end
+
+  def original_diff_version
+    @diff_notes_disabled = !@merge_request_diff.latest?
+    @diffs = @merge_request_diff.diffs(diff_options)
+  end
 end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index b0c72cfe4b4fd6da6e4cb569eccb0b99ccecb359..371cc3787fba35c98a064d2e06dd79e034e161fc 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -7,11 +7,10 @@ class Projects::PipelinesController < Projects::ApplicationController
 
   def index
     @scope = params[:scope]
-    all_pipelines = project.pipelines
-    @pipelines_count = all_pipelines.count
-    @running_or_pending_count = all_pipelines.running_or_pending.count
-    @pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope)
-    @pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30)
+    @pipelines = PipelinesFinder.new(project).execute(scope: @scope).page(params[:page]).per(30)
+
+    @running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count
+    @pipelines_count = PipelinesFinder.new(project).execute.count
   end
 
   def new
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index a99632454d94f354a92b0b607c127b9734ba9158..a4bedb3bfe6f71b51c336a25c7fb2db16745b9e9 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -73,7 +73,7 @@ class UsersController < ApplicationController
 
   def calendar
     calendar = contributions_calendar
-    @timestamps = calendar.timestamps
+    @activity_dates = calendar.activity_dates
 
     render 'calendar', layout: false
   end
diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb
index 641fbf838f143d89cf68ed2ce4b875c91aabc5da..32aea75486deee72a465d8f5959a772d1787df79 100644
--- a/app/finders/pipelines_finder.rb
+++ b/app/finders/pipelines_finder.rb
@@ -1,30 +1,34 @@
 class PipelinesFinder
-  attr_reader :project
+  attr_reader :project, :pipelines
 
   def initialize(project)
     @project = project
+    @pipelines = project.pipelines
   end
 
-  def execute(pipelines, scope)
-    case scope
-    when 'running'
-      pipelines.running_or_pending
-    when 'branches'
-      from_ids(pipelines, ids_for_ref(pipelines, branches))
-    when 'tags'
-      from_ids(pipelines, ids_for_ref(pipelines, tags))
-    else
-      pipelines
-    end
+  def execute(scope: nil)
+    scoped_pipelines =
+      case scope
+      when 'running'
+        pipelines.running_or_pending
+      when 'branches'
+        from_ids(ids_for_ref(branches))
+      when 'tags'
+        from_ids(ids_for_ref(tags))
+      else
+        pipelines
+      end
+
+    scoped_pipelines.order(id: :desc)
   end
 
   private
 
-  def ids_for_ref(pipelines, refs)
+  def ids_for_ref(refs)
     pipelines.where(ref: refs).group(:ref).select('max(id)')
   end
 
-  def from_ids(pipelines, ids)
+  def from_ids(ids)
     pipelines.unscoped.where(id: ids)
   end
 
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index aa8acbe7567585ae346b2d98ab509e91bc499ec8..df41473543b5314acfc5886d63c08cf45c3cfc29 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -14,7 +14,8 @@ module AvatarsHelper
       avatar_icon(options[:user] || options[:user_email], avatar_size),
       class: "avatar has-tooltip hidden-xs s#{avatar_size}",
       alt: "#{user_name}'s avatar",
-      title: user_name
+      title: user_name,
+      data: { container: 'body' }
     )
 
     if options[:user]
diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb
index 096849552336c2495ab4d2f9fbe08c547f4737e0..8ab394384f30081ee9830a3e84cbc65e6144455e 100644
--- a/app/helpers/git_helper.rb
+++ b/app/helpers/git_helper.rb
@@ -2,4 +2,8 @@ module GitHelper
   def strip_gpg_signature(text)
     text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
   end
+
+  def short_sha(text)
+    Commit.truncate_sha(text)
+  end
 end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index b9211e884733f693e0905a3fbcc2c748564a244f..ab880ed6de0874ab773b016da8eb31c70bb80300 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -23,4 +23,29 @@ module GroupsHelper
       full_title
     end
   end
+
+  def projects_lfs_status(group)
+    lfs_status =
+      if group.lfs_enabled?
+        group.projects.select(&:lfs_enabled?).size
+      else
+        group.projects.reject(&:lfs_enabled?).size
+      end
+
+    size = group.projects.size
+
+    if lfs_status == size
+      'for all projects'
+    else
+      "for #{lfs_status} out of #{pluralize(size, 'project')}"
+    end
+  end
+
+  def group_lfs_status(group)
+    status = group.lfs_enabled? ? 'enabled' : 'disabled'
+
+    content_tag(:span, class: "lfs-#{status}") do
+      "#{status.humanize} #{projects_lfs_status(group)}"
+    end
+  end
 end
diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb
index 5d82abfca79078df050e04a9132fef69774d4048..8e82766468183feb9ac7dc81c049ba54728bf37c 100644
--- a/app/helpers/lfs_helper.rb
+++ b/app/helpers/lfs_helper.rb
@@ -25,13 +25,21 @@ module LfsHelper
   def lfs_download_access?
     return false unless project.lfs_enabled?
 
-    project.public? || ci? || (user && user.can?(:download_code, project))
+    project.public? || ci? || user_can_download_code? || build_can_download_code?
+  end
+
+  def user_can_download_code?
+    has_authentication_ability?(:download_code) && can?(user, :download_code, project)
+  end
+
+  def build_can_download_code?
+    has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project)
   end
 
   def lfs_upload_access?
     return false unless project.lfs_enabled?
 
-    user && user.can?(:push_code, project)
+    has_authentication_ability?(:push_code) && can?(user, :push_code, project)
   end
 
   def render_lfs_forbidden
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index a9e175c3f5ce542dff5f6efae876e28a092a74cf..8abe7865fed21f4b0b56acb17e4c1175e8e17930 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -100,4 +100,14 @@ module MergeRequestsHelper
   def merge_request_button_visibility(merge_request, closed)
     return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
   end
+
+  def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil)
+    diffs_namespace_project_merge_request_path(
+      project.namespace, project, merge_request,
+      diff_id: merge_request_diff.id, start_sha: start_sha)
+  end
+
+  def version_index(merge_request_diff)
+    @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff)
+  end
 end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 2b0ff6c0d00bc721d15e46c72fea0fae86033393..df87fac132def9e5305c7e6ed9ba134dcf5496f7 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,21 +1,7 @@
 module NavHelper
-  def nav_menu_collapsed?
-    cookies[:collapsed_nav] == 'true'
-  end
-
-  def nav_sidebar_class
-    if nav_menu_collapsed?
-      "sidebar-collapsed"
-    else
-      "sidebar-expanded"
-    end
-  end
-
   def page_sidebar_class
     if pinned_nav?
       "page-sidebar-expanded page-sidebar-pinned"
-    else
-      "page-sidebar-collapsed"
     end
   end
 
@@ -26,7 +12,6 @@ module NavHelper
       current_path?('merge_requests#builds') ||
       current_path?('merge_requests#conflicts') ||
       current_path?('merge_requests#pipelines') ||
-      
       current_path?('issues#show')
       if cookies[:collapsed_gutter] == 'true'
         "page-gutter right-sidebar-collapsed"
@@ -43,9 +28,7 @@ module NavHelper
     class_name << " with-horizontal-nav" if defined?(nav) && nav
 
     if pinned_nav?
-      class_name << " header-expanded header-pinned-nav"
-    else
-      class_name << " header-collapsed"
+      class_name << " header-sidebar-expanded header-sidebar-pinned"
     end
 
     class_name
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 4c685b97c03d6369fab2514b57940dfaa0b033ed..56477733ea2ca9be765d027f079e27815bb451df 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -27,7 +27,7 @@ module ProjectsHelper
     author_html =  ""
 
     # Build avatar image tag
-    author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar]
+    author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]} #{opts[:avatar_class] if opts[:avatar_class]}", alt: '') if opts[:avatar]
 
     # Build name span tag
     if opts[:by_username]
@@ -129,6 +129,19 @@ module ProjectsHelper
     current_user.recent_push(project_ids)
   end
 
+  def project_feature_access_select(field)
+    # Don't show option "everyone with access" if project is private
+    options = project_feature_options
+
+    if @project.private?
+      options.delete('Everyone with access')
+      highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED
+    end
+
+    options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field))
+    content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe
+  end
+
   private
 
   def get_project_nav_tabs(project, current_user)
@@ -422,15 +435,4 @@ module ProjectsHelper
       'Everyone with access' => ProjectFeature::ENABLED
     }
   end
-
-  def project_feature_access_select(field)
-    # Don't show option "everyone with access" if project is private
-    options = project_feature_options
-    level = @project.project_feature.public_send(field)
-
-    options.delete('Everyone with access') if @project.private? && level != ProjectFeature::ENABLED
-
-    options = options_for_select(options, selected: @project.project_feature.public_send(field) || ProjectFeature::ENABLED)
-    content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe
-  end
 end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 4549c2e5bb6b20f390c293a10180f50cf6784659..8a7446b7cc762bfd8e8264729a0af9d240932394 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -7,8 +7,10 @@ module SearchHelper
       projects_autocomplete(term)
     ].flatten
 
+    search_pattern = Regexp.new(Regexp.escape(term), "i")
+
     generic_results = project_autocomplete + default_autocomplete + help_autocomplete
-    generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") }
+    generic_results.select! { |result| result[:label] =~ search_pattern }
 
     [
       resources_results,
@@ -28,6 +30,37 @@ module SearchHelper
     "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
   end
 
+  def parse_search_result(result)
+    ref = nil
+    filename = nil
+    basename = nil
+    startline = 0
+
+    result.each_line.each_with_index do |line, index|
+      if line =~ /^.*:.*:\d+:/
+        ref, filename, startline = line.split(':')
+        startline = startline.to_i - index
+        extname = Regexp.escape(File.extname(filename))
+        basename = filename.sub(/#{extname}$/, '')
+        break
+      end
+    end
+
+    data = ""
+
+    result.each_line do |line|
+      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
+    end
+
+    OpenStruct.new(
+      filename: filename,
+      basename: basename,
+      ref: ref,
+      startline: startline,
+      data: data
+    )
+  end
+
   private
 
   # Autocomplete results for various settings pages
diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d440edc55ba4660505b8f21d332dec8ffb7ad94d
--- /dev/null
+++ b/app/helpers/sidekiq_helper.rb
@@ -0,0 +1,19 @@
+module SidekiqHelper
+  SIDEKIQ_PS_REGEXP = /\A
+    (?<pid>\d+)\s+
+    (?<cpu>[\d\.,]+)\s+
+    (?<mem>[\d\.,]+)\s+
+    (?<state>[DRSTWXZNLsl\+<]+)\s+
+    (?<start>.+)\s+
+    (?<command>sidekiq.*\])\s+
+    \z/x
+
+  def parse_sidekiq_ps(line)
+    match = line.match(SIDEKIQ_PS_REGEXP)
+    if match
+      match[1..6]
+    else
+      %w[? ? ? ? ? ?]
+    end
+  end
+end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 0a5a8eb5aeec8de8f353858e304c2743d5587756..7e33a5620775641a918eb28c8a3135b5dff82552 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,10 +1,10 @@
 module SnippetsHelper
-  def reliable_snippet_path(snippet)
+  def reliable_snippet_path(snippet, opts = nil)
     if snippet.project_id?
       namespace_project_snippet_path(snippet.project.namespace,
-                                     snippet.project, snippet)
+                                     snippet.project, snippet, opts)
     else
-      snippet_path(snippet)
+      snippet_path(snippet, opts)
     end
   end
 
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index d887cdadc3475a731a3b414528b33ec512a1d1ad..88f374be1e5c1340e6987c535ea8be7486802466 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -34,4 +34,8 @@ module WorkhorseHelper
     headers.store(*Gitlab::Workhorse.send_artifacts_entry(build, entry))
     head :ok
   end
+
+  def set_workhorse_internal_api_content_type
+    headers['Content-Type'] = Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+  end
 end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 246477ffe88e05b2952e21bb610cbef01f55564a..55d2e07de08ff74485efbfb183660d08e3b7fb42 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -146,7 +146,7 @@ class ApplicationSetting < ActiveRecord::Base
       default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
       default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
       domain_whitelist: Settings.gitlab['domain_whitelist'],
-      import_sources: %w[github bitbucket gitlab google_code fogbugz git gitlab_project],
+      import_sources: Gitlab::ImportSources.values,
       shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
       max_artifacts_size: Settings.artifacts['max_size'],
       require_two_factor_authentication: false,
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 12cc5aaafba1bbeb13a361e93d88f8bdce6264cb..ab92e82033544a8db5236490f81199f04c42ea96 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -22,6 +22,18 @@ class Blob < SimpleDelegator
     new(blob)
   end
 
+  # Returns the data of the blob.
+  #
+  # If the blob is a text based blob the content is converted to UTF-8 and any
+  # invalid byte sequences are replaced.
+  def data
+    if binary?
+      super
+    else
+      @data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
+    end
+  end
+
   def no_highlighting?
     size && size > 1.megabyte
   end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 61052437318ba3eac06c3e54fec625894ecf6295..dd984aef3184b3a59dfa0e5d8ee01bfd50f5d384 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -1,5 +1,7 @@
 module Ci
   class Build < CommitStatus
+    include TokenAuthenticatable
+
     belongs_to :runner, class_name: 'Ci::Runner'
     belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
     belongs_to :erased_by, class_name: 'User'
@@ -23,7 +25,10 @@ module Ci
 
     acts_as_taggable
 
+    add_authentication_token_field :token
+
     before_save :update_artifacts_size, if: :artifacts_file_changed?
+    before_save :ensure_token
     before_destroy { project }
 
     after_create :execute_hooks
@@ -38,6 +43,7 @@ module Ci
         new_build.status = 'pending'
         new_build.runner_id = nil
         new_build.trigger_request_id = nil
+        new_build.token = nil
         new_build.save
       end
 
@@ -79,11 +85,14 @@ module Ci
 
       after_transition any => [:success] do |build|
         if build.environment.present?
-          service = CreateDeploymentService.new(build.project, build.user,
-                                                environment: build.environment,
-                                                sha: build.sha,
-                                                ref: build.ref,
-                                                tag: build.tag)
+          service = CreateDeploymentService.new(
+            build.project, build.user,
+            environment: build.environment,
+            sha: build.sha,
+            ref: build.ref,
+            tag: build.tag,
+            options: build.options[:environment],
+            variables: build.variables)
           service.execute(build)
         end
       end
@@ -148,6 +157,7 @@ module Ci
       variables += runner.predefined_variables if runner
       variables += project.container_registry_variables
       variables += yaml_variables
+      variables += user_variables
       variables += project.secret_variables
       variables += trigger_request.user_variables if trigger_request
       variables
@@ -172,7 +182,7 @@ module Ci
     end
 
     def repo_url
-      auth = "gitlab-ci-token:#{token}@"
+      auth = "gitlab-ci-token:#{ensure_token!}@"
       project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
         prefix + auth
       end
@@ -234,12 +244,7 @@ module Ci
     end
 
     def trace
-      trace = raw_trace
-      if project && trace.present? && project.runners_token.present?
-        trace.gsub(project.runners_token, 'xxxxxx')
-      else
-        trace
-      end
+      hide_secrets(raw_trace)
     end
 
     def trace_length
@@ -252,6 +257,7 @@ module Ci
 
     def trace=(trace)
       recreate_trace_dir
+      trace = hide_secrets(trace)
       File.write(path_to_trace, trace)
     end
 
@@ -265,6 +271,8 @@ module Ci
     def append_trace(trace_part, offset)
       recreate_trace_dir
 
+      trace_part = hide_secrets(trace_part)
+
       File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
       File.open(path_to_trace, 'ab') do |f|
         f.write(trace_part)
@@ -340,12 +348,8 @@ module Ci
       )
     end
 
-    def token
-      project.runners_token
-    end
-
     def valid_token?(token)
-      project.valid_runners_token?(token)
+      self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
     end
 
     def has_tags?
@@ -434,6 +438,15 @@ module Ci
       read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || []
     end
 
+    def user_variables
+      return [] if user.blank?
+
+      [
+        { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
+        { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }
+      ]
+    end
+
     private
 
     def update_artifacts_size
@@ -469,6 +482,7 @@ module Ci
       ]
       variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
       variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
+      variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if manual?
       variables
     end
 
@@ -477,5 +491,11 @@ module Ci
 
       pipeline.config_processor.build_attributes(name)
     end
+
+    def hide_secrets(trace)
+      trace = Ci::MaskSecret.mask(trace, project.runners_token) if project
+      trace = Ci::MaskSecret.mask(trace, token)
+      trace
+    end
   end
 end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 66f43456c0630f044d33d1a973fdfc516a3565d7..cbb4f60cd32568f824e9f8619425a331ff20aa0a 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -2,6 +2,7 @@ module Ci
   class Pipeline < ActiveRecord::Base
     extend Ci::Model
     include HasStatus
+    include Importable
 
     self.table_name = 'ci_commits'
 
@@ -12,12 +13,12 @@ module Ci
     has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
     has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
 
-    validates_presence_of :sha
-    validates_presence_of :ref
-    validates_presence_of :status
-    validate :valid_commit_sha
+    validates_presence_of :sha, unless: :importing?
+    validates_presence_of :ref, unless: :importing?
+    validates_presence_of :status, unless: :importing?
+    validate :valid_commit_sha, unless: :importing?
 
-    after_save :keep_around_commits
+    after_save :keep_around_commits, unless: :importing?
 
     delegate :stages, to: :statuses
 
@@ -269,8 +270,17 @@ module Ci
       ]
     end
 
+    def queued_duration
+      return unless started_at
+
+      seconds = (started_at - created_at).to_i
+      seconds unless seconds.zero?
+    end
+
     def update_duration
-      self.duration = calculate_duration
+      return unless started_at
+
+      self.duration = Gitlab::Ci::PipelineDuration.from_pipeline(self)
     end
 
     def execute_hooks
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 49f05f881a25f246f03c5b208238da5c204f75c8..ed5d4b13b7eedd75730c5632cb26f0d212c503b0 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -2,7 +2,7 @@ module Ci
   class Runner < ActiveRecord::Base
     extend Ci::Model
 
-    LAST_CONTACT_TIME = 5.minutes.ago
+    LAST_CONTACT_TIME = 2.hours.ago
     AVAILABLE_SCOPES = %w[specific shared active paused online]
     FORM_EDITABLE = %i[description tag_list active run_untagged locked]
 
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index c9c47ec7419641462301456fe88268bd77ed57ba..6959223aed9e0d89953e93019a6a17ebe46a7537 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -1,7 +1,7 @@
 module Ci
   class Variable < ActiveRecord::Base
     extend Ci::Model
-    
+
     belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
 
     validates_uniqueness_of :key, scope: :gl_project_id
@@ -11,7 +11,9 @@ module Ci
       format: { with: /\A[a-zA-Z0-9_]+\z/,
                 message: "can contain only letters, digits and '_'." }
 
-    attr_encrypted :value, 
+    scope :order_key_asc, -> { reorder(key: :asc) }
+
+    attr_encrypted :value,
        mode: :per_attribute_iv_and_salt,
        insecure_mode: true,
        key: Gitlab::Application.secrets.db_key_base,
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 4a6289244997c61c220fb36513cd2cc722722d46..c85561291c8700544923071052a378d484c7e50a 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base
       commit_status.update_attributes finished_at: Time.now
     end
 
-    # We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed
-    around_transition any => [:success, :failed, :canceled] do |commit_status, block|
-      block.call
-
-      commit_status.pipeline.try(:process!)
-    end
-
     after_transition do |commit_status, transition|
       commit_status.pipeline.try(:build_updated) unless transition.loopback?
     end
 
+    after_transition any => [:success, :failed, :canceled] do |commit_status|
+      commit_status.pipeline.try(:process!)
+      true
+    end
+
     after_transition [:created, :pending, :running] => :success do |commit_status|
       MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
     end
@@ -95,6 +93,10 @@ class CommitStatus < ActiveRecord::Base
     pipeline.before_sha || Gitlab::Git::BLANK_SHA
   end
 
+  def group_name
+    name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
+  end
+
   def self.stages
     # We group by stage name, but order stages by theirs' index
     unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
@@ -113,6 +115,10 @@ class CommitStatus < ActiveRecord::Base
     allow_failure? && (failed? || canceled?)
   end
 
+  def playable?
+    false
+  end
+
   def duration
     calculate_duration
   end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index f7b8352405c6ea665697a6467b1d92f1e7792ad2..0fa4df0fb5617b2161ef11845188105fa4c34612 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -8,8 +8,9 @@ module HasStatus
 
   class_methods do
     def status_sql
-      scope = all.relevant
+      scope = all
       builds = scope.select('count(*)').to_sql
+      created = scope.created.select('count(*)').to_sql
       success = scope.success.select('count(*)').to_sql
       ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
       ignored ||= '0'
@@ -19,12 +20,12 @@ module HasStatus
       skipped = scope.skipped.select('count(*)').to_sql
 
       deduce_status = "(CASE
-        WHEN (#{builds})=0 THEN NULL
+        WHEN (#{builds})=(#{created}) THEN 'created'
         WHEN (#{builds})=(#{skipped}) THEN 'skipped'
         WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
-        WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending'
+        WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending'
         WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
-        WHEN (#{running})+(#{pending})>0 THEN 'running'
+        WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
         ELSE 'failed'
       END)"
 
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 4442cefc7e955c255c921e8ecd02afa7a33e6ab3..559b30759050c58305cedbba8ace0df22e78fa51 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -13,6 +13,11 @@ class DiffNote < Note
   validate :positions_complete
   validate :verify_supported
 
+  # Keep this scope in sync with the logic in `#resolvable?`
+  scope :resolvable, -> { user.where(noteable_type: 'MergeRequest') }
+  scope :resolved, -> { resolvable.where.not(resolved_at: nil) }
+  scope :unresolved, -> { resolvable.where(resolved_at: nil) }
+
   after_initialize :ensure_original_discussion_id
   before_validation :set_original_position, :update_position, on: :create
   before_validation :set_line_code, :set_original_discussion_id
@@ -25,6 +30,16 @@ class DiffNote < Note
     def build_discussion_id(noteable_type, noteable_id, position)
       [super(noteable_type, noteable_id), *position.key].join("-")
     end
+
+    # This method must be kept in sync with `#resolve!`
+    def resolve!(current_user)
+      unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id)
+    end
+
+    # This method must be kept in sync with `#unresolve!`
+    def unresolve!
+      resolved.update_all(resolved_at: nil, resolved_by_id: nil)
+    end
   end
 
   def new_diff_note?
@@ -73,6 +88,7 @@ class DiffNote < Note
     self.position.diff_refs == diff_refs
   end
 
+  # If you update this method remember to also update the scope `resolvable`
   def resolvable?
     !system? && for_merge_request?
   end
@@ -83,6 +99,7 @@ class DiffNote < Note
     self.resolved_at.present?
   end
 
+  # If you update this method remember to also update `.resolve!`
   def resolve!(current_user)
     return unless resolvable?
     return if resolved?
@@ -92,6 +109,7 @@ class DiffNote < Note
     save!
   end
 
+  # If you update this method remember to also update `.unresolve!`
   def unresolve!
     return unless resolvable?
     return unless resolved?
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index 9676bc034708630130e3fe79b8d63f3c47830f74..de06c13481a1899aa80110139bbd6b700f6091b7 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -1,7 +1,7 @@
 class Discussion
   NUMBER_OF_TRUNCATED_DIFF_LINES = 16
 
-  attr_reader :first_note, :last_note, :notes
+  attr_reader :notes
 
   delegate  :created_at,
             :project,
@@ -36,8 +36,6 @@ class Discussion
   end
 
   def initialize(notes)
-    @first_note = notes.first
-    @last_note = notes.last
     @notes = notes
   end
 
@@ -70,17 +68,25 @@ class Discussion
   end
 
   def resolvable?
-    return @resolvable if defined?(@resolvable)
+    return @resolvable if @resolvable.present?
 
     @resolvable = diff_discussion? && notes.any?(&:resolvable?)
   end
 
   def resolved?
-    return @resolved if defined?(@resolved)
+    return @resolved if @resolved.present?
 
     @resolved = resolvable? && notes.none?(&:to_be_resolved?)
   end
 
+  def first_note
+    @first_note ||= @notes.first
+  end
+
+  def last_note
+    @last_note ||= @notes.last
+  end
+
   def resolved_notes
     notes.select(&:resolved?)
   end
@@ -100,17 +106,13 @@ class Discussion
   def resolve!(current_user)
     return unless resolvable?
 
-    notes.each do |note|
-      note.resolve!(current_user) if note.resolvable?
-    end
+    update { |notes| notes.resolve!(current_user) }
   end
 
   def unresolve!
     return unless resolvable?
 
-    notes.each do |note|
-      note.unresolve! if note.resolvable?
-    end
+    update { |notes| notes.unresolve! }
   end
 
   def for_target?(target)
@@ -118,7 +120,7 @@ class Discussion
   end
 
   def active?
-    return @active if defined?(@active)
+    return @active if @active.present?
 
     @active = first_note.active?
   end
@@ -174,4 +176,17 @@ class Discussion
 
     prev_lines
   end
+
+  private
+
+  def update
+    notes_relation = DiffNote.where(id: notes.map(&:id)).fresh
+    yield(notes_relation)
+
+    # Set the notes array to the updated notes
+    @notes = notes_relation.to_a
+
+    # Reset the memoized values
+    @last_resolved_note = @resolvable = @resolved = @first_note = @last_note = nil
+  end
 end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 75e6f869786e5bbc513ce1a677b0889fe97a6bcc..33c9abf382a1666a2a16ef5645d0ff52fed08519 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -4,6 +4,7 @@ class Environment < ActiveRecord::Base
   has_many :deployments
 
   before_validation :nullify_external_url
+  before_save :set_environment_type
 
   validates :name,
             presence: true,
@@ -26,6 +27,17 @@ class Environment < ActiveRecord::Base
     self.external_url = nil if self.external_url.blank?
   end
 
+  def set_environment_type
+    names = name.split('/')
+
+    self.environment_type =
+      if names.many?
+        names.first
+      else
+        nil
+      end
+  end
+
   def includes_commit?(commit)
     return false unless last_deployment
 
diff --git a/app/models/event.rb b/app/models/event.rb
index a0b7b0dc2b597016dfc0854abfc350ce20b7f634..55a76e26f3cacc61bd5cfd0093d0d095bb5e8af8 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -13,6 +13,8 @@ class Event < ActiveRecord::Base
   LEFT      = 9 # User left project
   DESTROYED = 10
 
+  RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
+
   delegate :name, :email, to: :author, prefix: true, allow_nil: true
   delegate :title, to: :issue, prefix: true, allow_nil: true
   delegate :title, to: :merge_request, prefix: true, allow_nil: true
@@ -324,8 +326,27 @@ class Event < ActiveRecord::Base
   end
 
   def reset_project_activity
-    if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain
-      project.update_column(:last_activity_at, self.created_at)
-    end
+    return unless project
+
+    # Don't even bother obtaining a lock if the last update happened less than
+    # 60 minutes ago.
+    return if recent_update?
+
+    return unless try_obtain_lease
+
+    project.update_column(:last_activity_at, created_at)
+  end
+
+  private
+
+  def recent_update?
+    project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
+  end
+
+  def try_obtain_lease
+    Gitlab::ExclusiveLease.
+      new("project:update_last_activity_at:#{project.id}",
+          timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i).
+      try_obtain
   end
 end
diff --git a/app/models/group.rb b/app/models/group.rb
index c48869ae465ea904f7b18b6af8ff120bef03ea12..aefb94b2adac86bdf413463413da8e372a0205e0 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -95,6 +95,13 @@ class Group < Namespace
     end
   end
 
+  def lfs_enabled?
+    return false unless Gitlab.config.lfs.enabled
+    return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?
+
+    self[:lfs_enabled]
+  end
+
   def add_users(user_ids, access_level, current_user: nil, expires_at: nil)
     user_ids.each do |user_id|
       Member.add_user(
diff --git a/app/models/member.rb b/app/models/member.rb
index 64e0d33fb208adca2e7c3677adafa93a1347c1de..694063799484e295ef1a2c1c0d7c51114865ba08 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -28,17 +28,34 @@ class Member < ActiveRecord::Base
       allow_nil: true
     }
 
+  # This scope encapsulates (most of) the conditions a row in the member table
+  # must satisfy if it is a valid permission. Of particular note:
+  #
+  #   * Access requests must be excluded
+  #   * Blocked users must be excluded
+  #   * Invitations take effect immediately
+  #   * expires_at is not implemented. A background worker purges expired rows
+  scope :active, -> do
+    is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil))
+    user_is_active = User.arel_table[:state].eq(:active)
+
+    includes(:user).references(:users)
+      .where(is_external_invite.or(user_is_active))
+      .where(requested_at: nil)
+  end
+
   scope :invite, -> { where.not(invite_token: nil) }
   scope :non_invite, -> { where(invite_token: nil) }
   scope :request, -> { where.not(requested_at: nil) }
-  scope :has_access, -> { where('access_level > 0') }
-
-  scope :guests, -> { where(access_level: GUEST) }
-  scope :reporters, -> { where(access_level: REPORTER) }
-  scope :developers, -> { where(access_level: DEVELOPER) }
-  scope :masters,  -> { where(access_level: MASTER) }
-  scope :owners,  -> { where(access_level: OWNER) }
-  scope :owners_and_masters,  -> { where(access_level: [OWNER, MASTER]) }
+
+  scope :has_access, -> { active.where('access_level > 0') }
+
+  scope :guests, -> { active.where(access_level: GUEST) }
+  scope :reporters, -> { active.where(access_level: REPORTER) }
+  scope :developers, -> { active.where(access_level: DEVELOPER) }
+  scope :masters,  -> { active.where(access_level: MASTER) }
+  scope :owners,  -> { active.where(access_level: OWNER) }
+  scope :owners_and_masters,  -> { active.where(access_level: [OWNER, MASTER]) }
 
   before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
 
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 6548b84ea7a60828f3d97d00e7c2883223ebc221..e7af37a6c0a93b21f780f4f24a24961c2896da9c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -322,6 +322,10 @@ class MergeRequest < ActiveRecord::Base
     closed? && forked_source_project_missing?
   end
 
+  def closed_without_source_project?
+    closed? && !source_project
+  end
+
   def forked_source_project_missing?
     return false unless for_fork?
     return true unless source_project
@@ -329,6 +333,12 @@ class MergeRequest < ActiveRecord::Base
     !source_project.forked_from?(target_project)
   end
 
+  def reopenable?
+    return false if closed_without_fork? || closed_without_source_project? || merged?
+
+    closed?
+  end
+
   def ensure_merge_request_diff
     merge_request_diff || create_merge_request_diff
   end
@@ -661,7 +671,7 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def environments
-    return unless diff_head_commit
+    return [] unless diff_head_commit
 
     target_project.environments.select do |environment|
       environment.includes_commit?(diff_head_commit)
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 445179a4487b47016525e83b3ed156cc23c09544..18c583add8848174d2e250dddb667dc54f326f18 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -152,6 +152,10 @@ class MergeRequestDiff < ActiveRecord::Base
     self == merge_request.merge_request_diff
   end
 
+  def compare_with(sha)
+    CompareService.new.execute(project, head_commit_sha, project, sha)
+  end
+
   private
 
   def dump_commits(commits)
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 7c29d27ce9784c954960a7c0bf119b3ed79e5f0c..919b3b1f095c5840e88cd803ca276126191cee9b 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -141,6 +141,11 @@ class Namespace < ActiveRecord::Base
     projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id)
   end
 
+  def lfs_enabled?
+    # User namespace will always default to the global setting
+    Gitlab.config.lfs.enabled
+  end
+
   private
 
   def repository_storage_paths
diff --git a/app/models/project.rb b/app/models/project.rb
index a6de2c4807164c1456b39ee240162dc6f2242041..d7f20070be0bc8737982cc481d0be1dcd7dee5f7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -58,7 +58,7 @@ class Project < ActiveRecord::Base
 
   # Relations
   belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
-  belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
+  belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
   belongs_to :namespace
 
   has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
@@ -393,10 +393,9 @@ class Project < ActiveRecord::Base
   end
 
   def lfs_enabled?
-    return false unless Gitlab.config.lfs.enabled
-    return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?
+    return namespace.lfs_enabled? if self[:lfs_enabled].nil?
 
-    self[:lfs_enabled]
+    self[:lfs_enabled] && Gitlab.config.lfs.enabled
   end
 
   def repository_storage_path
@@ -1138,12 +1137,6 @@ class Project < ActiveRecord::Base
     self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
   end
 
-  # TODO (ayufan): For now we use runners_token (backward compatibility)
-  # In 8.4 every build will have its own individual token valid for time of build
-  def valid_build_token?(token)
-    self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
-  end
-
   def build_coverage_enabled?
     build_coverage_regex.present?
   end
@@ -1288,8 +1281,24 @@ class Project < ActiveRecord::Base
     end
   end
 
+  def pushes_since_gc
+    Gitlab::Redis.with { |redis| redis.get(pushes_since_gc_redis_key).to_i }
+  end
+
+  def increment_pushes_since_gc
+    Gitlab::Redis.with { |redis| redis.incr(pushes_since_gc_redis_key) }
+  end
+
+  def reset_pushes_since_gc
+    Gitlab::Redis.with { |redis| redis.del(pushes_since_gc_redis_key) }
+  end
+
   private
 
+  def pushes_since_gc_redis_key
+    "projects/#{id}/pushes_since_gc"
+  end
+
   # Prevents the creation of project_feature record for every project
   def setup_project_feature
     build_project_feature unless project_feature
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index e6c943db2bfc9be8ec584a16e99cae2be190b796..e1b937817f4649d7bfd4adbdd10665da35b023ef 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -1,6 +1,6 @@
 class SlackService < Service
   prop_accessor :webhook, :username, :channel
-  boolean_accessor :notify_only_broken_builds
+  boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
   validates :webhook, presence: true, url: true, if: :activated?
 
   def initialize_properties
@@ -10,6 +10,7 @@ class SlackService < Service
     if properties.nil?
       self.properties = {}
       self.notify_only_broken_builds = true
+      self.notify_only_broken_pipelines = true
     end
   end
 
@@ -38,13 +39,15 @@ class SlackService < Service
         { type: 'text', name: 'username', placeholder: 'username' },
         { type: 'text', name: 'channel', placeholder: "#general" },
         { type: 'checkbox', name: 'notify_only_broken_builds' },
+        { type: 'checkbox', name: 'notify_only_broken_pipelines' },
       ]
 
     default_fields + build_event_channels
   end
 
   def supported_events
-    %w(push issue confidential_issue merge_request note tag_push build wiki_page)
+    %w[push issue confidential_issue merge_request note tag_push
+       build pipeline wiki_page]
   end
 
   def execute(data)
@@ -62,32 +65,22 @@ class SlackService < Service
     # 'close' action. Ignore update events for now to prevent duplicate
     # messages from arriving.
 
-    message = \
-      case object_kind
-      when "push", "tag_push"
-        PushMessage.new(data)
-      when "issue"
-        IssueMessage.new(data) unless is_update?(data)
-      when "merge_request"
-        MergeMessage.new(data) unless is_update?(data)
-      when "note"
-        NoteMessage.new(data)
-      when "build"
-        BuildMessage.new(data) if should_build_be_notified?(data)
-      when "wiki_page"
-        WikiPageMessage.new(data)
-      end
-
-    opt = {}
-
-    event_channel = get_channel_field(object_kind) || channel
-
-    opt[:channel] = event_channel if event_channel
-    opt[:username] = username if username
+    message = get_message(object_kind, data)
 
     if message
+      opt = {}
+
+      event_channel = get_channel_field(object_kind) || channel
+
+      opt[:channel] = event_channel if event_channel
+      opt[:username] = username if username
+
       notifier = Slack::Notifier.new(webhook, opt)
       notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
+
+      true
+    else
+      false
     end
   end
 
@@ -105,6 +98,25 @@ class SlackService < Service
 
   private
 
+  def get_message(object_kind, data)
+    case object_kind
+    when "push", "tag_push"
+      PushMessage.new(data)
+    when "issue"
+      IssueMessage.new(data) unless is_update?(data)
+    when "merge_request"
+      MergeMessage.new(data) unless is_update?(data)
+    when "note"
+      NoteMessage.new(data)
+    when "build"
+      BuildMessage.new(data) if should_build_be_notified?(data)
+    when "pipeline"
+      PipelineMessage.new(data) if should_pipeline_be_notified?(data)
+    when "wiki_page"
+      WikiPageMessage.new(data)
+    end
+  end
+
   def get_channel_field(event)
     field_name = event_channel_name(event)
     self.public_send(field_name)
@@ -142,6 +154,17 @@ class SlackService < Service
       false
     end
   end
+
+  def should_pipeline_be_notified?(data)
+    case data[:object_attributes][:status]
+    when 'success'
+      !notify_only_broken_pipelines?
+    when 'failed'
+      true
+    else
+      false
+    end
+  end
 end
 
 require "slack_service/issue_message"
@@ -149,4 +172,5 @@ require "slack_service/push_message"
 require "slack_service/merge_message"
 require "slack_service/note_message"
 require "slack_service/build_message"
+require "slack_service/pipeline_message"
 require "slack_service/wiki_page_message"
diff --git a/app/models/project_services/slack_service/build_message.rb b/app/models/project_services/slack_service/build_message.rb
index 69c21b3fc387fdf4ecbf98773ebf468ed8812f87..0fca4267bad8eb3f9246c40cf09e24a823526d0a 100644
--- a/app/models/project_services/slack_service/build_message.rb
+++ b/app/models/project_services/slack_service/build_message.rb
@@ -9,7 +9,7 @@ class SlackService
     attr_reader :user_name
     attr_reader :duration
 
-    def initialize(params, commit = true)
+    def initialize(params)
       @sha = params[:sha]
       @ref_type = params[:tag] ? 'tag' : 'branch'
       @ref = params[:ref]
@@ -36,7 +36,7 @@ class SlackService
 
     def message
       "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
-    end   
+    end
 
     def format(string)
       Slack::Notifier::LinkFormatter.format(string)
diff --git a/app/models/project_services/slack_service/pipeline_message.rb b/app/models/project_services/slack_service/pipeline_message.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f06b3562965292b549c0781dc5467c65df282455
--- /dev/null
+++ b/app/models/project_services/slack_service/pipeline_message.rb
@@ -0,0 +1,79 @@
+class SlackService
+  class PipelineMessage < BaseMessage
+    attr_reader :sha, :ref_type, :ref, :status, :project_name, :project_url,
+                :user_name, :duration, :pipeline_id
+
+    def initialize(data)
+      pipeline_attributes = data[:object_attributes]
+      @sha = pipeline_attributes[:sha]
+      @ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
+      @ref = pipeline_attributes[:ref]
+      @status = pipeline_attributes[:status]
+      @duration = pipeline_attributes[:duration]
+      @pipeline_id = pipeline_attributes[:id]
+
+      @project_name = data[:project][:path_with_namespace]
+      @project_url = data[:project][:web_url]
+      @user_name = data[:commit] && data[:commit][:author_name]
+    end
+
+    def pretext
+      ''
+    end
+
+    def fallback
+      format(message)
+    end
+
+    def attachments
+      [{ text: format(message), color: attachment_color }]
+    end
+
+    private
+
+    def message
+      "#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
+    end
+
+    def format(string)
+      Slack::Notifier::LinkFormatter.format(string)
+    end
+
+    def humanized_status
+      case status
+      when 'success'
+        'passed'
+      else
+        status
+      end
+    end
+
+    def attachment_color
+      if status == 'success'
+        'good'
+      else
+        'danger'
+      end
+    end
+
+    def branch_url
+      "#{project_url}/commits/#{ref}"
+    end
+
+    def branch_link
+      "[#{ref}](#{branch_url})"
+    end
+
+    def project_link
+      "[#{project_name}](#{project_url})"
+    end
+
+    def pipeline_url
+      "#{project_url}/pipelines/#{pipeline_id}"
+    end
+
+    def pipeline_link
+      "[#{Commit.truncate_sha(sha)}](#{pipeline_url})"
+    end
+  end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 414b82516bcbdc4dc7caf788540f3d5be9102d30..c69e5a22a694d271d55a6d7a3486ea6e8807f47f 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -757,7 +757,7 @@ class Repository
   end
 
   def commit_dir(user, path, message, branch)
-    commit_with_hooks(user, branch) do |ref|
+    update_branch_with_hooks(user, branch) do |ref|
       committer = user_to_committer(user)
       options = {}
       options[:committer] = committer
@@ -774,7 +774,7 @@ class Repository
   end
 
   def commit_file(user, path, content, message, branch, update)
-    commit_with_hooks(user, branch) do |ref|
+    update_branch_with_hooks(user, branch) do |ref|
       committer = user_to_committer(user)
       options = {}
       options[:committer] = committer
@@ -796,7 +796,7 @@ class Repository
   end
 
   def update_file(user, path, content, branch:, previous_path:, message:)
-    commit_with_hooks(user, branch) do |ref|
+    update_branch_with_hooks(user, branch) do |ref|
       committer = user_to_committer(user)
       options = {}
       options[:committer] = committer
@@ -813,7 +813,7 @@ class Repository
         update: true
       }
 
-      if previous_path
+      if previous_path && previous_path != path
         options[:file][:previous_path] = previous_path
         Gitlab::Git::Blob.rename(raw_repository, options)
       else
@@ -823,7 +823,7 @@ class Repository
   end
 
   def remove_file(user, path, message, branch)
-    commit_with_hooks(user, branch) do |ref|
+    update_branch_with_hooks(user, branch) do |ref|
       committer = user_to_committer(user)
       options = {}
       options[:committer] = committer
@@ -871,7 +871,7 @@ class Repository
     merge_index = rugged.merge_commits(our_commit, their_commit)
     return false if merge_index.conflicts?
 
-    commit_with_hooks(user, merge_request.target_branch) do
+    update_branch_with_hooks(user, merge_request.target_branch) do
       actual_options = options.merge(
         parents: [our_commit, their_commit],
         tree: merge_index.write_tree(rugged),
@@ -889,7 +889,7 @@ class Repository
 
     return false unless revert_tree_id
 
-    commit_with_hooks(user, base_branch) do
+    update_branch_with_hooks(user, base_branch) do
       committer = user_to_committer(user)
       source_sha = Rugged::Commit.create(rugged,
         message: commit.revert_message,
@@ -906,7 +906,7 @@ class Repository
 
     return false unless cherry_pick_tree_id
 
-    commit_with_hooks(user, base_branch) do
+    update_branch_with_hooks(user, base_branch) do
       committer = user_to_committer(user)
       source_sha = Rugged::Commit.create(rugged,
         message: commit.message,
@@ -922,7 +922,7 @@ class Repository
   end
 
   def resolve_conflicts(user, branch, params)
-    commit_with_hooks(user, branch) do
+    update_branch_with_hooks(user, branch) do
       committer = user_to_committer(user)
 
       Rugged::Commit.create(rugged, params.merge(author: committer, committer: committer))
@@ -990,43 +990,12 @@ class Repository
     Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
   end
 
-  def parse_search_result(result)
-    ref = nil
-    filename = nil
-    basename = nil
-    startline = 0
-
-    result.each_line.each_with_index do |line, index|
-      if line =~ /^.*:.*:\d+:/
-        ref, filename, startline = line.split(':')
-        startline = startline.to_i - index
-        extname = Regexp.escape(File.extname(filename))
-        basename = filename.sub(/#{extname}$/, '')
-        break
-      end
-    end
-
-    data = ""
-
-    result.each_line do |line|
-      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
-    end
-
-    OpenStruct.new(
-      filename: filename,
-      basename: basename,
-      ref: ref,
-      startline: startline,
-      data: data
-    )
-  end
-
   def fetch_ref(source_path, source_ref, target_ref)
     args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
     Gitlab::Popen.popen(args, path_to_repo)
   end
 
-  def commit_with_hooks(current_user, branch)
+  def update_branch_with_hooks(current_user, branch)
     update_autocrlf_option
 
     ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
@@ -1040,11 +1009,15 @@ class Repository
       raise CommitError.new('Failed to create commit')
     end
 
-    oldrev = rugged.lookup(newrev).parent_ids.first || Gitlab::Git::BLANK_SHA
+    if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil?
+      oldrev = Gitlab::Git::BLANK_SHA
+    else
+      oldrev = rugged.merge_base(newrev, target_branch.target.sha)
+    end
 
     GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
       update_ref!(ref, newrev, oldrev)
-      
+
       if was_empty || !target_branch
         # If repo was empty expire cache
         after_create if was_empty
diff --git a/app/models/service.rb b/app/models/service.rb
index 198e7247838ba9ba416ae1742ef2e43a81e035fc..80de7175565f38840efeb95e8fcdff0e2470d508 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -12,6 +12,7 @@ class Service < ActiveRecord::Base
   default_value_for :tag_push_events, true
   default_value_for :note_events, true
   default_value_for :build_events, true
+  default_value_for :pipeline_events, true
   default_value_for :wiki_page_events, true
 
   after_initialize :initialize_properties
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 08feb2cbb0e4a73a267a8efae23e3fc3861b7c4b..ae77b2f05068d4e6ed8e34fd8874cb1ae06b7190 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -64,6 +64,12 @@ class ProjectPolicy < BasePolicy
     can! :read_deployment
   end
 
+  # Permissions given when an user is team member of a project
+  def team_member_reporter_access!
+    can! :build_download_code
+    can! :build_read_container_image
+  end
+
   def developer_access!
     can! :admin_merge_request
     can! :update_merge_request
@@ -110,6 +116,8 @@ class ProjectPolicy < BasePolicy
     can! :read_commit_status
     can! :read_pipeline
     can! :read_container_image
+    can! :build_download_code
+    can! :build_read_container_image
   end
 
   def owner_access!
@@ -131,10 +139,11 @@ class ProjectPolicy < BasePolicy
   def team_access!(user)
     access = project.team.max_member_access(user.id)
 
-    guest_access!     if access >= Gitlab::Access::GUEST
-    reporter_access!  if access >= Gitlab::Access::REPORTER
-    developer_access! if access >= Gitlab::Access::DEVELOPER
-    master_access!    if access >= Gitlab::Access::MASTER
+    guest_access!                if access >= Gitlab::Access::GUEST
+    reporter_access!             if access >= Gitlab::Access::REPORTER
+    team_member_reporter_access! if access >= Gitlab::Access::REPORTER
+    developer_access!            if access >= Gitlab::Access::DEVELOPER
+    master_access!               if access >= Gitlab::Access::MASTER
   end
 
   def archived_access!
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 6072123b851e3ffb1d9c6dab9322e9f407aafd4e..98da65639475438a96ca575f7a6360bd7fd7927d 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -4,7 +4,9 @@ module Auth
 
     AUDIENCE = 'container_registry'
 
-    def execute
+    def execute(authentication_abilities:)
+      @authentication_abilities = authentication_abilities || []
+
       return error('not found', 404) unless registry.enabled
 
       unless current_user || project
@@ -74,9 +76,9 @@ module Auth
 
       case requested_action
       when 'pull'
-        requested_project == project || can?(current_user, :read_container_image, requested_project)
+        requested_project.public? || build_can_pull?(requested_project) || user_can_pull?(requested_project)
       when 'push'
-        requested_project == project || can?(current_user, :create_container_image, requested_project)
+        build_can_push?(requested_project) || user_can_push?(requested_project)
       else
         false
       end
@@ -85,5 +87,29 @@ module Auth
     def registry
       Gitlab.config.registry
     end
+
+    def build_can_pull?(requested_project)
+      # Build can:
+      # 1. pull from its own project (for ex. a build)
+      # 2. read images from dependent projects if creator of build is a team member
+      @authentication_abilities.include?(:build_read_container_image) &&
+        (requested_project == project || can?(current_user, :build_read_container_image, requested_project))
+    end
+
+    def user_can_pull?(requested_project)
+      @authentication_abilities.include?(:read_container_image) &&
+        can?(current_user, :read_container_image, requested_project)
+    end
+
+    def build_can_push?(requested_project)
+      # Build can push only to the project from which it originates
+      @authentication_abilities.include?(:build_create_container_image) &&
+        requested_project == project
+    end
+
+    def user_can_push?(requested_project)
+      @authentication_abilities.include?(:create_container_image) &&
+        can?(current_user, :create_container_image, requested_project)
+    end
   end
 end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index de48a50774e2c1d931ee1d6812a1e5a991247021..36c93dddadbde82ae28d3ac96eac9c13d9af259c 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -31,13 +31,13 @@ module Ci
       current_status = status_for_prior_stages(index)
 
       created_builds_in_stage(index).select do |build|
-        process_build(build, current_status)
+        if HasStatus::COMPLETED_STATUSES.include?(current_status)
+          process_build(build, current_status)
+        end
       end
     end
 
     def process_build(build, current_status)
-      return false unless HasStatus::COMPLETED_STATUSES.include?(current_status)
-
       if valid_statuses_for_when(build.when).include?(current_status)
         build.enqueue
         true
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index ed73d8cb8c2a13f911d4d8a86131a9ed62f8b10d..1c82599c5793d3f92db2ba4d4ee502a0e3f3ab58 100644
--- a/app/services/commits/change_service.rb
+++ b/app/services/commits/change_service.rb
@@ -16,11 +16,29 @@ module Commits
       error(ex.message)
     end
 
+    private
+
     def commit
       raise NotImplementedError
     end
 
-    private
+    def commit_change(action)
+      raise NotImplementedError unless repository.respond_to?(action)
+
+      into = @create_merge_request ? @commit.public_send("#{action}_branch_name") : @target_branch
+      tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch)
+
+      if tree_id
+        create_target_branch(into) if @create_merge_request
+
+        repository.public_send(action, current_user, @commit, into, tree_id)
+        success
+      else
+        error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically.
+                     It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content."
+        raise ChangeError, error_msg
+      end
+    end
 
     def check_push_permissions
       allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
diff --git a/app/services/commits/cherry_pick_service.rb b/app/services/commits/cherry_pick_service.rb
index f9a4efa7182ead1398cfbd02fed217e9acc510c6..605cca36f9c39d9d99355d0ba02b67a40a859785 100644
--- a/app/services/commits/cherry_pick_service.rb
+++ b/app/services/commits/cherry_pick_service.rb
@@ -1,19 +1,7 @@
 module Commits
   class CherryPickService < ChangeService
     def commit
-      cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch
-      cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch)
-
-      if cherry_pick_tree_id
-        create_target_branch(cherry_pick_into) if @create_merge_request
-
-        repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id)
-        success
-      else
-        error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically.
-                     It may have already been cherry-picked, or a more recent commit may have updated some of its content."
-        raise ChangeError, error_msg
-      end
+      commit_change(:cherry_pick)
     end
   end
 end
diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb
index c7de9f6f35e47cf0c704a8136abcfb4996666e19..addd55cb32f1c99136cd80056605a85844a77879 100644
--- a/app/services/commits/revert_service.rb
+++ b/app/services/commits/revert_service.rb
@@ -1,19 +1,7 @@
 module Commits
   class RevertService < ChangeService
     def commit
-      revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch
-      revert_tree_id = repository.check_revert_content(@commit, @target_branch)
-
-      if revert_tree_id
-        create_target_branch(revert_into) if @create_merge_request
-
-        repository.revert(current_user, @commit, revert_into, revert_tree_id)
-        success
-      else
-        error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically.
-                     It may have already been reverted, or a more recent commit may have updated some of its content."
-        raise ChangeError, error_msg
-      end
+      commit_change(:revert)
     end
   end
 end
diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb
index c126b2fad3154b29a6e88f98b58604278a250ae0..aad5cc8a15bac8a559c33500568f5726a4267c7d 100644
--- a/app/services/create_deployment_service.rb
+++ b/app/services/create_deployment_service.rb
@@ -2,9 +2,7 @@ require_relative 'base_service'
 
 class CreateDeploymentService < BaseService
   def execute(deployable = nil)
-    environment = project.environments.find_or_create_by(
-      name: params[:environment]
-    )
+    environment = find_or_create_environment
 
     deployment = project.deployments.create(
       environment: environment,
@@ -45,4 +43,38 @@ class CreateDeploymentService < BaseService
       where.not(id: current_deployment.id).
       first
   end
+
+  private
+
+  def find_or_create_environment
+    project.environments.find_or_create_by(name: expanded_name) do |environment|
+      environment.external_url = expanded_url
+    end
+  end
+
+  def expanded_name
+    ExpandVariables.expand(name, variables)
+  end
+
+  def expanded_url
+    return unless url
+
+    @expanded_url ||= ExpandVariables.expand(url, variables)
+  end
+
+  def name
+    params[:environment]
+  end
+
+  def url
+    options[:url]
+  end
+
+  def options
+    params[:options] || {}
+  end
+
+  def variables
+    params[:variables] || []
+  end
 end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 2aab67b4b9d980d6b537e6b9b1e69491271e5456..cde993221ff4cf9f47feca19a66dc8726068c7b1 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -87,7 +87,7 @@ class GitPushService < BaseService
     project.change_head(branch_name)
 
     # Set protection on the default branch if configured
-    if current_application_settings.default_branch_protection != PROTECTION_NONE
+    if current_application_settings.default_branch_protection != PROTECTION_NONE && !@project.protected_branch?(@project.default_branch)
 
       params = {
         name: @project.default_branch,
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..60891cbb255fbdf6732647ecc84c733d2b228cc6
--- /dev/null
+++ b/app/services/issuable/bulk_update_service.rb
@@ -0,0 +1,26 @@
+module Issuable
+  class BulkUpdateService < IssuableBaseService
+    def execute(type)
+      model_class = type.classify.constantize
+      update_class = type.classify.pluralize.constantize::UpdateService
+
+      ids = params.delete(:issuable_ids).split(",")
+      items = model_class.where(id: ids)
+
+      %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key|
+        params.delete(key) unless params[key].present?
+      end
+
+      items.each do |issuable|
+        next unless can?(current_user, :"update_#{type}", issuable)
+
+        update_class.new(issuable.project, current_user, params).execute(issuable)
+      end
+
+      {
+        count:    items.count,
+        success:  !items.count.zero?
+      }
+    end
+  end
+end
diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb
deleted file mode 100644
index 7e19a73f71a22491f18dff6be77ba9698ce03641..0000000000000000000000000000000000000000
--- a/app/services/issues/bulk_update_service.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Issues
-  class BulkUpdateService < BaseService
-    def execute
-      issues_ids   = params.delete(:issues_ids).split(",")
-      issue_params = params
-
-      %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key|
-        issue_params.delete(key) unless issue_params[key].present?
-      end
-
-      issues = Issue.where(id: issues_ids)
-
-      issues.each do |issue|
-        next unless can?(current_user, :update_issue, issue)
-
-        Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue)
-      end
-
-      {
-        count:    issues.count,
-        success:  !issues.count.zero?
-      }
-    end
-  end
-end
diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb
index 3b90399af6439816ab5ca3d7e1fa728ebff226fd..b8e08c9f1eb167e97f496f359819b5c013c1aeb5 100644
--- a/app/services/milestones/create_service.rb
+++ b/app/services/milestones/create_service.rb
@@ -3,7 +3,7 @@ module Milestones
     def execute
       milestone = project.milestones.new(params)
 
-      if milestone.save!
+      if milestone.save
         event_service.open_milestone(milestone, current_user)
       end
 
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 8a53f65aec1a82bf4770a1a9d0df637e694113ec..a08c6fcd94b1504cf0be871d804cbd18907ef34c 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -27,6 +27,8 @@ module Projects
       # Git data (e.g. a list of branch names).
       flush_caches(project, wiki_path)
 
+      Projects::UnlinkForkService.new(project, current_user).execute
+
       Project.transaction do
         project.destroy!
 
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index 29b3981f49f76f1f3b824f56bd820869cec8c65b..c3dfc8cfbe8c974f626b22ebc6a022ca4891c1f5 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -30,10 +30,8 @@ module Projects
     end
 
     def increment!
-      if Gitlab::ExclusiveLease.new("project_housekeeping:increment!:#{@project.id}", timeout: 60).try_obtain
-        Gitlab::Metrics.measure(:increment_pushes_since_gc) do
-          update_pushes_since_gc(@project.pushes_since_gc + 1)
-        end
+      Gitlab::Metrics.measure(:increment_pushes_since_gc) do
+        @project.increment_pushes_since_gc
       end
     end
 
@@ -43,14 +41,10 @@ module Projects
       GitGarbageCollectWorker.perform_async(@project.id)
     ensure
       Gitlab::Metrics.measure(:reset_pushes_since_gc) do
-        update_pushes_since_gc(0)
+        @project.reset_pushes_since_gc
       end
     end
 
-    def update_pushes_since_gc(new_value)
-      @project.update_column(:pushes_since_gc, new_value)
-    end
-
     def try_obtain_lease
       Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
         lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 2aab8c736d6a56036de37df5a109620804cc9f73..776530ac0a54e9a0233e050877962b9c40f618f1 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -31,6 +31,14 @@ class TodoService
     mark_pending_todos_as_done(issue, current_user)
   end
 
+  # When we destroy an issue we should:
+  #
+  #  * refresh the todos count cache for the current user
+  #
+  def destroy_issue(issue, current_user)
+    destroy_issuable(issue, current_user)
+  end
+
   # When we reassign an issue we should:
   #
   #  * create a pending todo for new assignee if issue is assigned
@@ -64,6 +72,14 @@ class TodoService
     mark_pending_todos_as_done(merge_request, current_user)
   end
 
+  # When we destroy a merge request we should:
+  #
+  #  * refresh the todos count cache for the current user
+  #
+  def destroy_merge_request(merge_request, current_user)
+    destroy_issuable(merge_request, current_user)
+  end
+
   # When we reassign a merge request we should:
   #
   #  * creates a pending todo for new assignee if merge request is assigned
@@ -187,6 +203,10 @@ class TodoService
     create_mention_todos(issuable.project, issuable, author)
   end
 
+  def destroy_issuable(issuable, user)
+    user.update_todos_count_cache
+  end
+
   def toggling_tasks?(issuable)
     issuable.previous_changes.include?('description') &&
       issuable.tasks? && issuable.updated_tasks.any?
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 4f680b507c493983d66295c120aebebd6610f200..05855db963a44004d8fe5cc0e855e15ad772d5b6 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -28,14 +28,10 @@
               %th COMMAND
             %tbody
               - @sidekiq_processes.each do |process|
-                - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
-                - data = process.strip.split(' ')
                 %tr
                   %td= gitlab_config.user
-                  - 5.times do
-                    %td= data.shift
-                  %td= data.join(' ')
-
+                  - parse_sidekiq_ps(process).each do |value|
+                    %td= value
         .clearfix
           %p
             %i.fa.fa-exclamation-circle
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
deleted file mode 100644
index f29d9c94441a96ce91f27cb17830e52f3fc2ef9f..0000000000000000000000000000000000000000
--- a/app/views/admin/builds/_build.html.haml
+++ /dev/null
@@ -1,77 +0,0 @@
-- project = build.project
-%tr.build.commit
-  %td.status
-    = ci_status_with_icon(build.status)
-
-  %td
-    .branch-commit
-      - if can?(current_user, :read_build, build.project)
-        = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
-          %span.build-link ##{build.id}
-      - else
-        %span.build-link ##{build.id}
-
-      - if build.ref
-        .icon-container
-          = build.tag? ? icon('tag') : icon('code-fork')
-        = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
-      - else
-        .light none
-      .icon-container
-        = custom_icon("icon_commit")
-
-      = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id"
-      - if build.stuck?
-        %i.fa.fa-warning.text-warning
-
-      .label-container
-        - if build.tags.any?
-          - build.tags.each do |tag|
-            %span.label.label-primary
-              = tag
-        - if build.try(:trigger_request)
-          %span.label.label-info triggered
-        - if build.try(:allow_failure)
-          %span.label.label-danger allowed to fail
-
-  %td
-    - if project
-      = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project)
-
-  %td
-    - if build.try(:runner)
-      = runner_link(build.runner)
-    - else
-      .light none
-
-  %td
-    #{build.stage} / #{build.name}
-
-  %td
-    - if build.duration
-      %p.duration
-        = custom_icon("icon_timer")
-        = duration_in_numbers(build.duration)
-
-    - if build.finished_at
-      %p.finished-at
-        = icon("calendar")
-        %span #{time_ago_with_tooltip(build.finished_at)}
-
-  - if defined?(coverage) && coverage
-    %td.coverage
-      - if build.try(:coverage)
-        #{build.coverage}%
-
-  %td
-    .pull-right
-      - if can?(current_user, :read_build, project) && build.artifacts?
-        = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do
-          %i.fa.fa-download
-      - if can?(current_user, :update_build, build.project)
-        - if build.active?
-          = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
-            %i.fa.fa-remove.cred
-        - elsif defined?(allow_retry) && allow_retry && build.retryable?
-          = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
-            %i.fa.fa-refresh
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index 3d77634d8fafe6b9ce72261c28845377a5599795..26a8846b609f69cbaeac928268af82a82ca5d2f6 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/builds/index.html.haml
@@ -4,26 +4,8 @@
 %div{ class: container_class }
 
   .top-area
-    %ul.nav-links
-      %li{class: ('active' if @scope.nil?)}
-        = link_to admin_builds_path do
-          All
-          %span.badge.js-totalbuilds-count= @all_builds.count(:id)
-
-      %li{class: ('active' if @scope == 'pending')}
-        = link_to admin_builds_path(scope: :pending) do
-          Pending
-          %span.badge= number_with_delimiter(@all_builds.pending.count(:id))
-
-      %li{class: ('active' if @scope == 'running')}
-        = link_to admin_builds_path(scope: :running) do
-          Running
-          %span.badge= number_with_delimiter(@all_builds.running.count(:id))
-
-      %li{class: ('active' if @scope == 'finished')}
-        = link_to admin_builds_path(scope: :finished) do
-          Finished
-          %span.badge= number_with_delimiter(@all_builds.finished.count(:id))
+    - build_path_proc = ->(scope) { admin_builds_path(scope: scope) }
+    = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
 
     .nav-controls
       - if @all_builds.running_or_pending.any?
@@ -33,23 +15,4 @@
     #{(@scope || 'all').capitalize} builds
 
   %ul.content-list.builds-content-list
-    - if @builds.blank?
-      %li
-        .nothing-here-block No builds to show
-    - else
-      .table-holder
-        %table.table.builds
-          %thead
-            %tr
-              %th Status
-              %th Commit
-              %th Project
-              %th Runner
-              %th Name
-              %th
-              %th
-
-          - @builds.each do |build|
-            = render "admin/builds/build", build: build
-
-      = paginate @builds, theme: 'gitlab'
+    = render "projects/builds/table", builds: @builds, admin: true
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 5f7fdfdb011801cf71ded2c5fd22ade8f7c4717b..817910f7ddf0cc44d3398731301bcfe466bda153 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -13,6 +13,8 @@
     .col-sm-offset-2.col-sm-10
       = render 'shared/allow_request_access', form: f
 
+  = render 'groups/group_lfs_settings', f: f
+
   - if @group.new_record?
     .form-group
       .col-sm-offset-2.col-sm-10
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index bb37469440076243a655866968de2f41a1a81c01..0188ed448ce7c33bbab736254c7bde81f9c55317 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -37,6 +37,12 @@
           %strong
             = @group.created_at.to_s(:medium)
 
+        %li
+          %span.light Group Git LFS status:
+          %strong
+            = group_lfs_status(@group)
+            = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+
     .panel.panel-default
       .panel-heading
         %h3.panel-title
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 4debd3d608f822dcaecd7c8cea1dabd0bf54511a..e623f7cff889f99216ad8fd42d39f66967f67021 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -18,6 +18,5 @@
             = f.submit "Verify code", class: "btn btn-save"
 
       - if @user.two_factor_u2f_enabled?
-
         %hr
-        = render "u2f/authenticate"
+        = render "u2f/authenticate", locals: { params: params, resource: resource, resource_name: resource_name }
diff --git a/app/views/groups/_group_lfs_settings.html.haml b/app/views/groups/_group_lfs_settings.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..af57065f0fc59a2c6366b29759603468e3950dc8
--- /dev/null
+++ b/app/views/groups/_group_lfs_settings.html.haml
@@ -0,0 +1,11 @@
+- if current_user.admin?
+  .form-group
+    .col-sm-offset-2.col-sm-10
+      .checkbox
+        = f.label :lfs_enabled do
+          = f.check_box :lfs_enabled, checked: @group.lfs_enabled?
+          %strong
+            Allow projects within this group to use Git LFS
+            = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+          %br/
+          %span.descr This setting can be overridden in each project.
\ No newline at end of file
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index decb89b2fd60936c02cc5ab27eaa97be0ba355bc..c766370d5a0991526eace080c1cfb8a8c7a1be75 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -25,6 +25,8 @@
         .col-sm-offset-2.col-sm-10
           = render 'shared/allow_request_access', form: f
 
+      = render 'group_lfs_settings', f: f
+
       .form-group
         %hr
         = f.label :share_with_group_lock, class: 'control-label' do
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index ce4536ebdc630fb09eeb0a100bdaa8c38e9fe073..65842a0479b8cdb00853d2d7d8d51dff96028086 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -7,277 +7,284 @@
           Keyboard Shortcuts
           %small
             = link_to '(Show all)', '#', class: 'js-more-help-button'
-      .modal-body.shortcuts-cheatsheet
-        .col-lg-4
-          %table.shortcut-mappings
-            %tbody
-              %tr
-                %th
-                %th Global Shortcuts
-              %tr
-                %td.shortcut
-                  .key s
-                %td Focus Search
-              %tr
-                %td.shortcut
-                  .key f
-                %td Focus Filter
-              %tr
-                %td.shortcut
-                  .key ?
-                %td Show/hide this dialog
-              %tr
-                %td.shortcut
-                  - if browser.platform.mac?
-                    .key &#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
-                %th Project Files browsing
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-up
-                %td Move selection up
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-down
-                %td Move selection down
-              %tr
-                %td.shortcut
-                  .key enter
-                %td Open Selection
-            %tbody
-              %tr
-                %th
-                %th Finding Project File
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-up
-                %td Move selection up
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-down
-                %td Move selection down
-              %tr
-                %td.shortcut
-                  .key enter
-                %td Open Selection
-              %tr
-                %td.shortcut
-                  .key esc
-                %td Go back
+      .modal-body
+        .row
+          .col-lg-4
+            %table.shortcut-mappings
+              %tbody
+                %tr
+                  %th
+                  %th Global Shortcuts
+                %tr
+                  %td.shortcut
+                    .key s
+                  %td Focus Search
+                %tr
+                  %td.shortcut
+                    .key f
+                  %td Focus Filter
+                %tr
+                  %td.shortcut
+                    .key ?
+                  %td Show/hide this dialog
+                %tr
+                  %td.shortcut
+                    - if browser.platform.mac?
+                      .key &#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
+                  %th Project Files browsing
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-up
+                  %td Move selection up
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-down
+                  %td Move selection down
+                %tr
+                  %td.shortcut
+                    .key enter
+                  %td Open Selection
+              %tbody
+                %tr
+                  %th
+                  %th Finding Project File
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-up
+                  %td Move selection up
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-down
+                  %td Move selection down
+                %tr
+                  %td.shortcut
+                    .key enter
+                  %td Open Selection
+                %tr
+                  %td.shortcut
+                    .key esc
+                  %td Go back
 
-        .col-lg-4
-          %table.shortcut-mappings
-            %tbody{ class: 'hidden-shortcut project', style: 'display:none' }
-              %tr
-                %th
-                %th Global Dashboard
-              %tr
-                %td.shortcut
-                  .key g
-                  .key a
-                %td
-                  Go to the activity feed
-              %tr
-                %td.shortcut
-                  .key g
-                  .key p
-                %td
-                  Go to projects
-              %tr
-                %td.shortcut
-                  .key g
-                  .key i
-                %td
-                  Go to issues
-              %tr
-                %td.shortcut
-                  .key g
-                  .key m
-                %td
-                  Go to merge requests
-            %tbody
-              %tr
-                %th
-                %th Project
-              %tr
-                %td.shortcut
-                  .key g
-                  .key p
-                %td
-                  Go to the project's home page
-              %tr
-                %td.shortcut
-                  .key g
-                  .key e
-                %td
-                  Go to the project's activity feed
-              %tr
-                %td.shortcut
-                  .key g
-                  .key f
-                %td
-                  Go to files
-              %tr
-                %td.shortcut
-                  .key g
-                  .key c
-                %td
-                  Go to commits
-              %tr
-                %td.shortcut
-                  .key g
-                  .key b
-                %td
-                  Go to builds
-              %tr
-                %td.shortcut
-                  .key g
-                  .key n
-                %td
-                  Go to network graph
-              %tr
-                %td.shortcut
-                  .key g
-                  .key g
-                %td
-                  Go to graphs
-              %tr
-                %td.shortcut
-                  .key g
-                  .key i
-                %td
-                  Go to issues
-              %tr
-                %td.shortcut
-                  .key g
-                  .key m
-                %td
-                  Go to merge requests
-              %tr
-                %td.shortcut
-                  .key g
-                  .key s
-                %td
-                  Go to snippets
-              %tr
-                %td.shortcut
-                  .key t
-                %td Go to finding file
-              %tr
-                %td.shortcut
-                  .key i
-                %td New issue
-        .col-lg-4
-          %table.shortcut-mappings
-            %tbody{ class: 'hidden-shortcut network', style: 'display:none' }
-              %tr
-                %th
-                %th Network Graph
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-left
-                  \/
-                  .key h
-                %td Scroll left
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-right
-                  \/
-                  .key l
-                %td Scroll right
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-up
-                  \/
-                  .key k
-                %td Scroll up
-              %tr
-                %td.shortcut
-                  .key
-                    %i.fa.fa-arrow-down
-                  \/
-                  .key j
-                %td Scroll down
-              %tr
-                %td.shortcut
-                  .key
-                    shift
-                    %i.fa.fa-arrow-up
-                  \/
-                  .key
-                    shift k
-                %td Scroll to top
-              %tr
-                %td.shortcut
-                  .key
-                    shift
-                    %i.fa.fa-arrow-down
-                  \/
-                  .key
-                    shift j
-                %td Scroll to bottom
-            %tbody{ class: 'hidden-shortcut issues', style: 'display:none' }
-              %tr
-                %th
-                %th Issues
-              %tr
-                %td.shortcut
-                  .key a
-                %td Change assignee
-              %tr
-                %td.shortcut
-                  .key m
-                %td Change milestone
-              %tr
-                %td.shortcut
-                  .key r
-                %td Reply (quoting selected text)
-              %tr
-                %td.shortcut
-                  .key e
-                %td Edit issue
-              %tr
-                %td.shortcut
-                  .key l
-                %td Change Label
-            %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
-              %tr
-                %th
-                %th Merge Requests
-              %tr
-                %td.shortcut
-                  .key a
-                %td Change assignee
-              %tr
-                %td.shortcut
-                  .key m
-                %td Change milestone
-              %tr
-                %td.shortcut
-                  .key r
-                %td Reply (quoting selected text)
-              %tr
-                %td.shortcut
-                  .key e
-                %td Edit merge request
-              %tr
-                %td.shortcut
-                  .key l
-                %td Change Label
+          .col-lg-4
+            %table.shortcut-mappings
+              %tbody{ class: 'hidden-shortcut project', style: 'display:none' }
+                %tr
+                  %th
+                  %th Global Dashboard
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key a
+                  %td
+                    Go to the activity feed
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key p
+                  %td
+                    Go to projects
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key i
+                  %td
+                    Go to issues
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key m
+                  %td
+                    Go to merge requests
+              %tbody
+                %tr
+                  %th
+                  %th Project
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key p
+                  %td
+                    Go to the project's home page
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key e
+                  %td
+                    Go to the project's activity feed
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key f
+                  %td
+                    Go to files
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key c
+                  %td
+                    Go to commits
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key b
+                  %td
+                    Go to builds
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key n
+                  %td
+                    Go to network graph
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key g
+                  %td
+                    Go to graphs
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key i
+                  %td
+                    Go to issues
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key l
+                  %td
+                    Go to issue boards
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key m
+                  %td
+                    Go to merge requests
+                %tr
+                  %td.shortcut
+                    .key g
+                    .key s
+                  %td
+                    Go to snippets
+                %tr
+                  %td.shortcut
+                    .key t
+                  %td Go to finding file
+                %tr
+                  %td.shortcut
+                    .key i
+                  %td New issue
+          .col-lg-4
+            %table.shortcut-mappings
+              %tbody{ class: 'hidden-shortcut network', style: 'display:none' }
+                %tr
+                  %th
+                  %th Network Graph
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-left
+                    \/
+                    .key h
+                  %td Scroll left
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-right
+                    \/
+                    .key l
+                  %td Scroll right
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-up
+                    \/
+                    .key k
+                  %td Scroll up
+                %tr
+                  %td.shortcut
+                    .key
+                      %i.fa.fa-arrow-down
+                    \/
+                    .key j
+                  %td Scroll down
+                %tr
+                  %td.shortcut
+                    .key
+                      shift
+                      %i.fa.fa-arrow-up
+                    \/
+                    .key
+                      shift k
+                  %td Scroll to top
+                %tr
+                  %td.shortcut
+                    .key
+                      shift
+                      %i.fa.fa-arrow-down
+                    \/
+                    .key
+                      shift j
+                  %td Scroll to bottom
+              %tbody{ class: 'hidden-shortcut issues', style: 'display:none' }
+                %tr
+                  %th
+                  %th Issues
+                %tr
+                  %td.shortcut
+                    .key a
+                  %td Change assignee
+                %tr
+                  %td.shortcut
+                    .key m
+                  %td Change milestone
+                %tr
+                  %td.shortcut
+                    .key r
+                  %td Reply (quoting selected text)
+                %tr
+                  %td.shortcut
+                    .key e
+                  %td Edit issue
+                %tr
+                  %td.shortcut
+                    .key l
+                  %td Change Label
+              %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
+                %tr
+                  %th
+                  %th Merge Requests
+                %tr
+                  %td.shortcut
+                    .key a
+                  %td Change assignee
+                %tr
+                  %td.shortcut
+                    .key m
+                  %td Change milestone
+                %tr
+                  %td.shortcut
+                    .key r
+                  %td Reply (quoting selected text)
+                %tr
+                  %td.shortcut
+                    .key e
+                  %td Edit merge request
+                %tr
+                  %td.shortcut
+                    .key l
+                  %td Change Label
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index bf50633af244025eaf404f88f3537bbd46370655..4f7839a881f0ef0f29a172ea9c62bb0c5b5cdd01 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,5 +1,5 @@
 .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
-  .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
+  .sidebar-wrapper.nicescroll
     .sidebar-action-buttons
       = link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do
         %span.sr-only Toggle navigation
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 1fb34841d87a45ac16e77428b92f976afbc99747..e44a2bfed9d7694ca2aaf72c9afbb60a5d077c0c 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -113,3 +113,7 @@
       %li.hidden
         = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
           Commits
+
+    -# Shortcut to issue boards
+    %li.hidden
+      = link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index a42b3b8eb3830cea042ab454a3d0e0c06ad826d7..931878735014e0e341c9f8448c641d5accd60cd8 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,4 +1,5 @@
 - page_title "SSH Keys"
+= render 'profiles/head'
 
 .row.prepend-top-default
   .col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml
index 249680bcab6634f7dfd9d7806058053a1a91c9c9..de1337a2a24e093ff7a4a26244c0c8d7c28a53f7 100644
--- a/app/views/profiles/update_username.js.haml
+++ b/app/views/profiles/update_username.js.haml
@@ -1,6 +1,6 @@
 - if @user.valid?
   :plain
-    new Flash("Username sucessfully changed", "notice")
+    new Flash("Username successfully changed", "notice")
 - else
   :plain
     new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert")
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 377665b096f5885d620f2450d3089000db12e101..5a98e258b2204e73259ba72f9dc8d89d75b0fb98 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -11,7 +11,7 @@
       %small= number_to_human_size @blob.size
       .file-actions
         = render "projects/blob/actions"
-    .file-content.blame.code.js-syntax-highlight
+    .table-responsive.file-content.blame.code.js-syntax-highlight
       %table
         - current_line = 1
         - @blame_groups.each do |blame_group|
@@ -19,6 +19,7 @@
             %td.blame-commit
               .commit
                 - commit = blame_group[:commit]
+                = author_avatar(commit, size: 36)
                 .commit-row-title
                   %strong
                     = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 808e6b957462e6e7fcfda794340703d8b74b3cad..5217b8bf028654c49323fbdbf7d91947fe811066 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -3,6 +3,7 @@
 - diverging_commit_counts = @repository.diverging_commit_counts(branch)
 - number_commits_behind = diverging_commit_counts[:behind]
 - number_commits_ahead = diverging_commit_counts[:ahead]
+- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
 %li(class="js-branch-#{branch.name}")
   %div
     = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated' do
@@ -19,12 +20,12 @@
         %i.fa.fa-lock
         protected
     .controls.hidden-xs
-      - if create_mr_button?(@repository.root_ref, branch.name)
+      - if merge_project && create_mr_button?(@repository.root_ref, branch.name)
         = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do
           Merge Request
 
       - if branch.name != @repository.root_ref
-        = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do
+        = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
           Compare
 
       = render 'projects/buttons/download', project: @project, ref: branch.name
diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..61eff73da2633a017d4bfcfabd5fee10b70cdec9
--- /dev/null
+++ b/app/views/projects/builds/_table.html.haml
@@ -0,0 +1,24 @@
+- admin = local_assigns.fetch(:admin, false)
+
+- if builds.blank?
+  %li
+    .nothing-here-block No builds to show
+- else
+  .table-holder
+    %table.table.builds
+      %thead
+        %tr
+          %th Status
+          %th Commit
+          - if admin
+            %th Project
+            %th Runner
+          %th Stage
+          %th Name
+          %th
+          %th Coverage
+          %th
+
+      = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: admin || project.build_coverage_enabled?, admin: admin }
+
+  = paginate builds, theme: 'gitlab'
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 2af625f69cd378e0b66786baf7f1d9d5012459c0..5c60b7a73643b78e60592488764e0c5f2ee3021e 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -4,30 +4,8 @@
 
 %div{ class: container_class }
   .top-area
-    %ul.nav-links
-      %li{class: ('active' if @scope.nil?)}
-        = link_to project_builds_path(@project) do
-          All
-          %span.badge.js-totalbuilds-count
-            = number_with_delimiter(@all_builds.count(:id))
-
-      %li{class: ('active' if @scope == 'pending')}
-        = link_to project_builds_path(@project, scope: :pending) do
-          Pending
-          %span.badge
-            = number_with_delimiter(@all_builds.pending.count(:id))
-
-      %li{class: ('active' if @scope == 'running')}
-        = link_to project_builds_path(@project, scope: :running) do
-          Running
-          %span.badge
-            = number_with_delimiter(@all_builds.running.count(:id))
-
-      %li{class: ('active' if @scope == 'finished')}
-        = link_to project_builds_path(@project, scope: :finished) do
-          Finished
-          %span.badge
-            = number_with_delimiter(@all_builds.finished.count(:id))
+    - build_path_proc = ->(scope) { project_builds_path(@project, scope: scope) }
+    = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
 
     .nav-controls
       - if can?(current_user, :update_build, @project)
@@ -42,23 +20,4 @@
           %span CI Lint
 
   %ul.content-list.builds-content-list
-    - if @builds.blank?
-      %li
-        .nothing-here-block No builds to show
-    - else
-      .table-holder
-        %table.table.builds
-          %thead
-            %tr
-              %th Status
-              %th Commit
-              %th Stage
-              %th Name
-              %th
-              - if @project.build_coverage_enabled?
-                %th Coverage
-              %th
-
-          = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
-
-      = paginate @builds, theme: 'gitlab'
+    = render "table", builds: @builds, project: @project
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 5f5e071eb40e318cd085ae005103ecae2f25d414..24de020917a9c2abe5f65026cadb8f4cb09086de 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -37,6 +37,6 @@
               %li.dropdown-header Previous Artifacts
             - artifacts.each do |job|
               %li
-                = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do
+                = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do
                   %i.fa.fa-download
                   %span Download '#{job.name}'
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 73de8abe55b906275ad669f60ccf7925aa937b78..75192c4818832f49294639997cc69c3cf0e4166a 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -1,3 +1,11 @@
+- admin = local_assigns.fetch(:admin, false)
+- ref = local_assigns.fetch(:ref, nil)
+- commit_sha = local_assigns.fetch(:commit_sha, nil)
+- retried = local_assigns.fetch(:retried, false)
+- stage = local_assigns.fetch(:stage, false)
+- coverage = local_assigns.fetch(:coverage, false)
+- allow_retry = local_assigns.fetch(:allow_retry, false)
+
 %tr.build.commit
   %td.status
     - if can?(current_user, :read_build, build)
@@ -9,11 +17,11 @@
     .branch-commit
       - if can?(current_user, :read_build, build)
         = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
-          %span ##{build.id}
+          %span.build-link ##{build.id}
       - else
-        %span ##{build.id}
+        %span.build-link ##{build.id}
 
-      - if defined?(ref) && ref
+      - if ref
         - if build.ref
           .icon-container
             = build.tag? ? icon('tag') : icon('code-fork')
@@ -23,12 +31,12 @@
         .icon-container
           = custom_icon("icon_commit")
 
-      - if defined?(commit_sha) && commit_sha
+      - if commit_sha
         = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
 
       - if build.stuck?
         = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
-      - if defined?(retried) && retried
+      - if retried
         = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
 
       .label-container
@@ -40,19 +48,24 @@
           %span.label.label-info triggered
         - if build.try(:allow_failure)
           %span.label.label-danger allowed to fail
-        - if defined?(retried) && retried
+        - if retried
           %span.label.label-warning retried
         - if build.manual?
           %span.label.label-info manual
 
-  - if defined?(runner) && runner
+  - if admin
+    %td
+      - if build.project
+        = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project)
+
+  - if admin
     %td
       - if build.try(:runner)
         = runner_link(build.runner)
       - else
         .light none
 
-  - if defined?(stage) && stage
+  - if stage
     %td
       = build.stage
 
@@ -64,13 +77,14 @@
       %p.duration
         = custom_icon("icon_timer")
         = duration_in_numbers(build.duration)
+
     - if build.finished_at
       %p.finished-at
         = icon("calendar")
         %span #{time_ago_with_tooltip(build.finished_at)}
 
-  - if defined?(coverage) && coverage
-    %td.coverage
+  %td.coverage
+    - if coverage
       - if build.try(:coverage)
         #{build.coverage}%
 
@@ -83,10 +97,10 @@
         - if build.active?
           = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
             = icon('remove', class: 'cred')
-        - elsif defined?(allow_retry) && allow_retry
+        - elsif allow_retry
           - if build.retryable?
             = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
               = icon('repeat')
-          - elsif build.playable?
+          - elsif build.playable? && !admin
             = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
               = custom_icon('icon_play')
diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml
index 36fb0300aebc7a554d938d0170795957ea72726b..547bc0c9c1971a4dd5d31827c56092e9c352386a 100644
--- a/app/views/projects/ci/builds/_build_pipeline.html.haml
+++ b/app/views/projects/ci/builds/_build_pipeline.html.haml
@@ -1,15 +1,12 @@
 - is_playable = subject.playable? && can?(current_user, :update_build, @project)
-%li.build{class: ("playable" if is_playable)}
-  .curve
-  .build-content
-    - if is_playable
-      = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
-        = render_status_with_link('build', 'play')
-        %span.ci-status-text= subject.name
-    - elsif can?(current_user, :read_build, @project)
-      = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
-        = render_status_with_link('build', subject.status)
-        %span.ci-status-text= subject.name
-    - else
-      = render_status_with_link('build', subject.status)
-      = ci_icon_for_status(subject.status)
+- if is_playable
+  = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
+    = render_status_with_link('build', 'play')
+    .ci-status-text= subject.name
+- elsif can?(current_user, :read_build, @project)
+  = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
+    = render_status_with_link('build', subject.status)
+    .ci-status-text= subject.name
+- else
+  = render_status_with_link('build', subject.status)
+  = ci_icon_for_status(subject.status)
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index bb9493f51583cf8169e36212249345022fb3dbfe..6391c67021b1183763d2b98c4b34bde045cd8dd7 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -36,16 +36,14 @@
 
 
     - stages_status = pipeline.statuses.relevant.latest.stages_status
-    - stages.each do |stage|
-      %td.stage-cell
+    %td.stage-cell
+      - stages.each do |stage|
         - status = stages_status[stage]
         - tooltip = "#{stage.titleize}: #{status || 'not found'}"
         - if status
-          = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
-            = ci_icon_for_status(status)
-        - else
-          .light.has-tooltip{ title: tooltip }
-            \-
+          .stage-container
+            = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
+              = ci_icon_for_status(status)
 
   %td
     - if pipeline.duration
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 20a85148ab5cdf06b20bf641867a6f3fc5c68fef..9258f4b3c25cf9bc401d93d68b555a6f8c666b7a 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -39,8 +39,7 @@
               = stage.titleize
           .builds-container
             %ul
-              - statuses.each do |status|
-                = render "projects/#{status.to_partial_path}_pipeline", subject: status
+              = render "projects/commit/pipeline_stage", statuses: statuses
 
 
 - if pipeline.yaml_errors.present?
diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..23c5c51fbc270b9f1b834d6457be0afcc6e2c4a8
--- /dev/null
+++ b/app/views/projects/commit/_pipeline_stage.html.haml
@@ -0,0 +1,14 @@
+- status_groups = statuses.group_by(&:group_name)
+- status_groups.each do |group_name, grouped_statuses|
+  - if grouped_statuses.one?
+    - status = grouped_statuses.first
+    - is_playable = status.playable? && can?(current_user, :update_build, @project)
+    %li.build{ class: ("playable" if is_playable) }
+      .curve
+      .build-content
+        = render "projects/#{status.to_partial_path}_pipeline", subject: status
+  - else
+    %li.build
+      .curve
+      .build-content
+        = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..4e7a6f1af081b83b8da5c831e93c9b85ffad9c5d
--- /dev/null
+++ b/app/views/projects/commit/_pipeline_status_group.html.haml
@@ -0,0 +1,11 @@
+- group_status = CommitStatus.where(id: subject).status
+= render_status_with_link('build', group_status)
+.dropdown.inline
+  %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
+    %span.ci-status-text
+      = name
+    %span.badge= subject.size
+  %ul.dropdown-menu.grouped-pipeline-dropdown
+    .arrow
+    - subject.each do |status|
+      = render "projects/#{status.to_partial_path}_pipeline", subject: status
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f6d751a343e735a8a76d0af37e6d59c263f510f0..a04d53e02bf2de3367c6a7aa04262c42c2bac129 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -84,15 +84,14 @@
                     = project_feature_access_select(:snippets_access_level)
 
             - if Gitlab.config.lfs.enabled && current_user.admin?
-              .form-group
-                .checkbox
-                  = f.label :lfs_enabled do
-                    = f.check_box :lfs_enabled, checked: @project.lfs_enabled?
-                    %strong LFS
-                    %br
-                    %span.descr
-                      Git Large File Storage
-                      = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+              .row
+                .col-md-9
+                  = f.label :lfs_enabled, 'LFS', class: 'label-light'
+                  %span.help-block
+                    Git Large File Storage
+                    = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+                .col-md-3
+                  = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control'
 
           - if Gitlab.config.registry.enabled
             .form-group
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
index 576d0bec51bb334a744edb024d56468bcea8b2ff..409f4701e4bc317d4020edd9c11499e67e0670c0 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
@@ -1,10 +1,7 @@
-%li.build
-  .curve
-  .build-content
-    - if subject.target_url
-      - link_to subject.target_url do
-        = render_status_with_link('commit status', subject.status)
-        %span.ci-status-text= subject.name
-    - else
-      = render_status_with_link('commit status', subject.status)
-      %span.ci-status-text= subject.name
+- if subject.target_url
+  = link_to subject.target_url do
+    = render_status_with_link('commit status', subject.status)
+    %span.ci-status-text= subject.name
+- else
+  = render_status_with_link('commit status', subject.status)
+  %span.ci-status-text= subject.name
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 79b1481986579cf5545e4de8fdb5b8ac88b5e1da..8b1a8a8a2d948da9e47dc0cf6e9989da011322c7 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,7 +1,7 @@
 %li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
-  - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
+  - if @bulk_edit
     .issue-check
-      = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
+      = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
 
   .issue-title.title
     %span.issue-title-text
@@ -29,7 +29,7 @@
 
       - note_count = issue.notes.user.count
       %li
-        = link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do
+        = link_to issue_path(issue, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
           = icon('comments')
           = note_count
 
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index f34f3c0573743d4cb746e3e51399c6d1054907bb..a2c31c0b4c5003778badce476ad50291c594055d 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,4 +1,4 @@
-%ul.content-list.issues-list
+%ul.content-list.issues-list.issuable-list
   = render @issues
   - if @issues.blank?
     %li
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index d80753718533e3738c05817d844417607c8ebfd2..31d3ec2327611d50c0ed01c6252ad8b118417bf5 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -1,7 +1,7 @@
 - if @merge_requests.any?
   %h2.merge-requests-title
     = pluralize(@merge_requests.count, 'Related Merge Request')
-  %ul.unstyled-list
+  %ul.unstyled-list.related-merge-requests
     - has_any_ci = @merge_requests.any?(&:pipeline)
     - @merge_requests.each do |merge_request|
       %li
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 33556a1a2b36ec4f465b31098998819cce90118a..c56b6cc11f5e40ba5798356779f7cc39c5ed8f2a 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,6 +1,6 @@
 - if can?(current_user, :push_code, @project)
   .pull-right
-    #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
+    #new-branch.new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
       = link_to '#', class: 'checking btn btn-grouped', disabled: 'disabled' do
         = icon('spinner spin')
         Checking branches
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index a8eeab3e55e416871021e6b8997f00a968a45a6d..44683c8bcdbe542cb58506c90ddf728c21c9ddbd 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -1,7 +1,7 @@
 - if @related_branches.any?
   %h2.related-branches-title
     = pluralize(@related_branches.count, 'Related Branch')
-  %ul.unstyled-list
+  %ul.unstyled-list.related-merge-requests
     - @related_branches.each do |branch|
       %li
         - target = @project.repository.find_branch(branch).target
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 1a87045aa60619900f852f0d54c1699c9127be33..8da9f2100e954afb3832249c0edfe2ed7f40707c 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,4 +1,6 @@
 - @no_container = true
+- @bulk_edit = can?(current_user, :admin_issue, @project)
+
 - page_title "Issues"
 - new_issue_email = @project.new_issue_address(current_user)
 = render "projects/issues/head"
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index d070979bcfe6bd06d59d866f7511c1f6fbbbbc2e..3900b4f6f1736fce2c984eca093a058853425d9a 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -2,7 +2,7 @@
   - if can?(current_user, :update_merge_request, @merge_request)
     - if @merge_request.open?
       = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"}
-    - if @merge_request.closed?
+    - if @merge_request.reopenable?
       = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
   %comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" }
     %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } }
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 5029b365f934e193de54d968f3f1046548d29cc9..68fb7d5a4145af5c7ec4e44da56ddc3be8b9fdae 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,4 +1,8 @@
 %li{ class: mr_css_classes(merge_request) }
+  - if @bulk_edit
+    .issue-check
+      = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
+
   .merge-request-title.title
     %span.merge-request-title-text
       = link_to merge_request.title, merge_request_path(merge_request)
@@ -37,7 +41,7 @@
 
       - note_count = merge_request.mr_and_commit_notes.user.count
       %li
-        = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do
+        = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
           = icon('comments')
           = note_count
 
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index 446887774a4e343f223bfc7533de3bde36fb5dc1..fe82f751f53c52c3664f173d03c4d0e4b4432a48 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -1,4 +1,4 @@
-%ul.content-list.mr-list
+%ul.content-list.mr-list.issuable-list
   = render @merge_requests
   - if @merge_requests.blank?
     %li
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 9d8b4cc56be7403ee4236e7ecea98da0badaec8a..d03ff9ec7e82967d20eeb04142b3677b349e39c0 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -29,17 +29,19 @@
             %ul.dropdown-menu.dropdown-menu-align-right
               %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
               %li= link_to "Plain Diff",    merge_request_path(@merge_request, format: :diff)
-      .normal
-        %span Request to merge
-        %span.label-branch= source_branch_with_namespace(@merge_request)
-        %span into
-        %span.label-branch
-          = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
-        - if @merge_request.open? && @merge_request.diverged_from_target_branch?
-          %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
+      - unless @merge_request.closed_without_fork?
+        .normal
+          %span Request to merge
+          %span.label-branch= source_branch_with_namespace(@merge_request)
+          %span into
+          %span.label-branch
+            = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
+          - if @merge_request.open? && @merge_request.diverged_from_target_branch?
+            %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
 
-    = render "projects/merge_requests/show/how_to_merge"
-    = render "projects/merge_requests/widget/show.html.haml"
+    - unless @merge_request.closed_without_source_project?
+      = render "projects/merge_requests/show/how_to_merge"
+      = render "projects/merge_requests/widget/show.html.haml"
 
     - if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user)
       .light.prepend-top-default.append-bottom-default
@@ -53,10 +55,11 @@
           = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
             Discussion
             %span.badge= @merge_request.mr_and_commit_notes.user.count
-        %li.commits-tab
-          = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
-            Commits
-            %span.badge= @commits_count
+        - unless @merge_request.closed_without_source_project?
+          %li.commits-tab
+            = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
+              Commits
+              %span.badge= @commits_count
         - if @pipeline
           %li.pipelines-tab
             = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
@@ -83,7 +86,7 @@
 
       .tab-content#diff-notes-app
         #notes.notes.tab-pane.voting_notes
-          .content-block.content-block-small.oneline-block
+          .content-block.content-block-small
             = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
 
           .row
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index ace275c689b8770e1ae420c6802ecfca86424a32..144b3a9c8c85d59096be1972348ac93f98455dcf 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,4 +1,6 @@
 - @no_container = true
+- @bulk_edit = can?(current_user, :admin_merge_request, @project)
+
 - page_title "Merge Requests"
 = render "projects/issues/head"
 = render 'projects/last_push'
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index 2da70ce7137adba857a99d4eba2d13266f8260ac..00287f2d245044ee2b0cf1da5e99dbd160191ad3 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -1,31 +1,60 @@
-- merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff
-
-- if merge_request_diffs.size > 1
-  .mr-version-switch
-    Version:
-    %span.dropdown.inline
+- if @merge_request_diffs.size > 1
+  .mr-version-controls
+    Changes between
+    %span.dropdown.inline.mr-version-dropdown
       %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} }
-        %strong.monospace<
+        %strong
           - if @merge_request_diff.latest?
-            #{"latest"}
+            latest version
           - else
-            #{@merge_request_diff.head_commit.short_id}
+            version #{version_index(@merge_request_diff)}
         %span.caret
       %ul.dropdown-menu.dropdown-menu-selectable
-        - merge_request_diffs.each do |merge_request_diff|
+        - @merge_request_diffs.each do |merge_request_diff|
           %li
-            = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do
-              %strong.monospace
-                #{merge_request_diff.head_commit.short_id}
-              %br
+            = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do
+              %strong
+                - if merge_request_diff.latest?
+                  latest version
+                - else
+                  version #{version_index(merge_request_diff)}
+              .monospace #{short_sha(merge_request_diff.head_commit_sha)}
               %small
                 #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)},
                 = time_ago_with_tooltip(merge_request_diff.created_at)
 
-    - unless @merge_request_diff.latest?
-      %span.prepend-left-default
+    - if @merge_request_diff.base_commit_sha
+      and
+      %span.dropdown.inline.mr-version-compare-dropdown
+        %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} }
+          %strong
+            - if @start_sha
+              version #{version_index(@start_version)}
+            - else
+              #{@merge_request.target_branch}
+          %span.caret
+        %ul.dropdown-menu.dropdown-menu-selectable
+          - @comparable_diffs.each do |merge_request_diff|
+            %li
+              = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do
+                %strong
+                  - if merge_request_diff.latest?
+                    latest version
+                  - else
+                    version #{version_index(merge_request_diff)}
+                .monospace #{short_sha(merge_request_diff.head_commit_sha)}
+                %small
+                  = time_ago_with_tooltip(merge_request_diff.created_at)
+          %li
+            = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
+              %strong
+                #{@merge_request.target_branch} (base)
+              .monospace #{short_sha(@merge_request_diff.base_commit_sha)}
+
+    - unless @merge_request_diff.latest? && !@start_sha
+      .prepend-top-10
         = icon('info-circle')
-        This version is not the latest one. Comments are disabled
-    .pull-right
-      %span.monospace
-        #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id}
+        - if @start_sha
+          Comments are disabled because you're comparing two versions of this merge request.
+        - else
+          Comments are disabled because you're viewing an old version of this merge request.
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 7c82177f9ea390ce50173ae7e4f1aaf6ceb8bc41..9ec17cf6e76b512e9f51834afc95aaf9a930efd8 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,6 +1,5 @@
 - return unless note.author
 - return if note.cross_reference_not_visible_for?(current_user)
-- can_resolve = can?(current_user, :resolve_note, note)
 
 - note_editable = note_editable?(note)
 %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
@@ -24,6 +23,8 @@
               %span.note-role.hidden-xs= access
 
             - if note.resolvable?
+              - can_resolve = can?(current_user, :resolve_note, note)
+
               %resolve-btn{ ":namespace-path" => "'#{note.project.namespace.path}'",
                   ":project-path" => "'#{note.project.path}'",
                   ":discussion-id" => "'#{note.discussion_id}'",
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 063e83a407aca98225f8d9a52f2a7cb463305ee4..5800ef7de489be31497b7901649986d7879f5ed6 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -10,6 +10,8 @@
   - if @pipeline.duration
     in
     = time_interval_in_words(@pipeline.duration)
+  - if @pipeline.queued_duration
+    = "(queued for #{time_interval_in_words(@pipeline.queued_duration)})"
 
   .pull-right
     = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 4d957e0d890c4e4ccf5d723f4de7c7c82674952f..faf28db68d1bb7233139020e4c663f2c6ee79610 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -47,13 +47,7 @@
           %tbody
             %th Status
             %th Commit
-            - stages.each do |stage|
-              %th.stage
-                - if stage.titleize.length > 12
-                  %span.has-tooltip{ title: "#{stage.titleize}" }
-                    = stage.titleize
-                - else
-                  = stage.titleize
+            %th Stages
             %th
             %th
           = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index bdbf3e5f4d6b875aed2f0e016935408a538c5168..a5a5619fa128c8b191bd7bd180ebbf032baaae27 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -2,12 +2,12 @@
   - if can?(current_user, :create_project_snippet, @project)
     = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
       New Snippet
-  - if can?(current_user, :update_project_snippet, @snippet)
-    = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
-      Edit
   - if can?(current_user, :update_project_snippet, @snippet)
     = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
       Delete
+  - if can?(current_user, :update_project_snippet, @snippet)
+    = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
+      Edit
 - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
   .visible-xs-block.dropdown
     %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -19,11 +19,11 @@
           %li
             = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
               New Snippet
-        - if can?(current_user, :update_project_snippet, @snippet)
-          %li
-            = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
-              Edit
         - if can?(current_user, :update_project_snippet, @snippet)
           %li
             = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
               Delete
+        - if can?(current_user, :update_project_snippet, @snippet)
+          %li
+            = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
+              Edit
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index bae4d8f349f2c661db9a32d740a5672a60e94017..b70fda88a799d0067f278770bd4970c665d48401 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,15 +1,14 @@
 - page_title @snippet.title, "Snippets"
 
-.snippet-holder
-  = render 'shared/snippets/header'
+= render 'shared/snippets/header'
 
-  %article.file-holder.file-holder-no-border.snippet-file-content
-    .file-title.file-title-clear
-      = blob_icon 0, @snippet.file_name
-      = @snippet.file_name
-      .file-actions.hidden-xs
-        = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
-        = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
-    = render 'shared/snippets/blob'
+%article.file-holder.snippet-file-content
+  .file-title
+    = blob_icon 0, @snippet.file_name
+    = @snippet.file_name
+    .file-actions
+      = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
+      = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
+  = render 'shared/snippets/blob'
 
-  %div#notes= render "projects/notes/notes_with_form"
+%div#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index baaa2caa6de827bee5791c25210feb364e31533e..a1f4e3e8ed6f326a3ea87408986d44ffa23d1041 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,7 +1,7 @@
 %article.file-holder.readme-holder
   .file-title
     = blob_icon readme.mode, readme.name
-    = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, @path, readme.name)) do
+    = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, @path, readme.name)) do
       %strong
         = readme.name
   .file-content.wiki
diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml
index 6c43f822db495716a631aa1103e4872d18195b8e..07cee86ba4ce0c7bbc1eb10b328495ef476d3d91 100644
--- a/app/views/projects/variables/_table.html.haml
+++ b/app/views/projects/variables/_table.html.haml
@@ -9,7 +9,7 @@
       %th Value
       %th
     %tbody
-      - @project.variables.each do |variable|
+      - @project.variables.order_key_asc.each do |variable|
         - if variable.id?
           %tr
             %td= variable.key
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 252c37532e16370d1f4cd06145947ff2627c5772..7fe2bce3e7c35cbff0bfa6d5a62545479f99b759 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -10,12 +10,16 @@
         in group #{link_to @group.name, @group}
 
   .results.prepend-top-10
-    .search-results
-      - if @scope == 'projects'
-        .term
-          = render 'shared/projects/list', projects: @search_objects
-      - else
-        = render partial: "search/results/#{@scope.singularize}", collection: @search_objects
+    - if @scope == 'commits'
+      %ul.list-unstyled
+        = render partial: "search/results/commit", collection: @search_objects
+    - else
+      .search-results
+        - if @scope == 'projects'
+          .term
+            = render 'shared/projects/list', projects: @search_objects
+        - else
+          = render partial: "search/results/#{@scope.singularize}", collection: @search_objects
 
     - if @scope != 'projects'
       = paginate(@search_objects, theme: 'gitlab')
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 290743feb4a57c4fb80a343e67188fad2a1b9d27..6f0a0ea36ec69cf1f469f6ef5646548169549b66 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,4 +1,4 @@
-- blob = @project.repository.parse_search_result(blob)
+- blob = parse_search_result(blob)
 .blob-result
   .file-holder
     .file-title
diff --git a/app/views/search/results/_commit.html.haml b/app/views/search/results/_commit.html.haml
index 4e6c3965dc65dac0a96f427c8466b2091df7b37e..5b2d83d6b92ecda8cf24fa261d7f1e52931aad59 100644
--- a/app/views/search/results/_commit.html.haml
+++ b/app/views/search/results/_commit.html.haml
@@ -1,2 +1 @@
-.search-result-row
-  = render 'projects/commits/commit', project: @project, commit: commit
+= render 'projects/commits/commit', project: @project, commit: commit
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index 235106c4f746553abf0ecf928cbf989516d9d82d..648d0bd76cbf42558392dfd81cb6f436e8b78f57 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,4 +1,4 @@
-- wiki_blob = @project.repository.parse_search_result(wiki_blob)
+- wiki_blob = parse_search_result(wiki_blob)
 .blob-result
   .file-holder
     .file-title
diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index 107ad19177c9e1a314923ef3109db4dc12cf403a..add4536a0a247bc367bdc82c4f43a890446308fa 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -1,7 +1,7 @@
 .form-group.project-visibility-level-holder
   = f.label :visibility_level, class: 'control-label' do
     Visibility Level
-    = link_to "(?)", help_page_path("public_access/public_access")
+    = link_to icon('question-circle'), help_page_path("public_access/public_access")
   .col-sm-10
     - if can_change_visibility_level
       = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..60353aee7f15750eaf4611a954771dc9fda6a6dd
--- /dev/null
+++ b/app/views/shared/builds/_tabs.html.haml
@@ -0,0 +1,24 @@
+%ul.nav-links
+  %li{ class: ('active' if scope.nil?) }
+    = link_to build_path_proc.call(nil) do
+      All
+      %span.badge.js-totalbuilds-count
+        = number_with_delimiter(all_builds.count(:id))
+
+  %li{ class: ('active' if scope == 'pending') }
+    = link_to build_path_proc.call('pending') do
+      Pending
+      %span.badge
+        = number_with_delimiter(all_builds.pending.count(:id))
+
+  %li{ class: ('active' if scope == 'running') }
+    = link_to build_path_proc.call('running') do
+      Running
+      %span.badge
+        = number_with_delimiter(all_builds.running.count(:id))
+
+  %li{ class: ('active' if scope == 'finished') }
+    = link_to build_path_proc.call('finished') do
+      Finished
+      %span.badge
+        = number_with_delimiter(all_builds.finished.count(:id))
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 0f4f744a71fcec5a61aaa27446844ac0ca7cf0db..93c4d5c3d307684f344961ac1dadfc70fe449208 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,9 +1,11 @@
+- boards_page = controller.controller_name == 'boards'
+
 .issues-filters
   .issues-details-filters.row-content-block.second-block
     = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do
       - if params[:issue_search].present?
         = hidden_field_tag :issue_search, params[:issue_search]
-      - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
+      - if @bulk_edit
         .check-all-holder
           = check_box_tag "check_all_issues", nil, false,
             class: "check_all_issues left"
@@ -26,8 +28,11 @@
         .filter-item.inline.labels-filter
           = render "shared/issuable/label_dropdown"
 
+        .filter-item.inline.reset-filters
+          %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search])} Reset filters
+
         .pull-right
-          - if controller.controller_name == 'boards'
+          - if boards_page
             #js-boards-seach.issue-boards-search
               %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" }
               - if can?(current_user, :admin_list, @project)
@@ -42,9 +47,9 @@
           - else
             = render 'shared/sort_dropdown'
 
-    - if controller.controller_name == 'issues'
+    - if @bulk_edit
       .issues_bulk_update.hide
-        = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post, class: 'bulk-update'  do
+        = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update'  do
           .filter-item.inline
             = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
               %ul
@@ -67,10 +72,10 @@
                 %li
                   %a{href: "#", data: {id: "unsubscribe"}} Unsubscribe
 
-          = hidden_field_tag 'update[issues_ids]', []
+          = hidden_field_tag 'update[issuable_ids]', []
           = hidden_field_tag :state_event, params[:state_event]
           .filter-item.inline
-            = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+            = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
 
   - if !@labels.nil?
     .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 3856a4917b4095b8fb2acb905ac894a93ced9c45..04373684ee9d07f6834181342bbf1e2d23cb5788 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -19,7 +19,7 @@
 
         = dropdown_tag(title, options: { toggle_class: 'js-issuable-selector',
           title: title, filter: true, placeholder: 'Filter', footer_content: true,
-          data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: @project.path, namespace_path: @project.namespace.path } } ) do
+          data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do
           %ul.dropdown-footer-list
             %li
               %a.reset-template
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 24a1a616919ac845b78b85aa5385aca1abfbcf41..d34d28f6736a11b63d98c13319e34f115d26f099 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -12,7 +12,7 @@
 - if params[:label_name].present?
   - if params[:label_name].respond_to?('any?')
     - params[:label_name].each do |label|
-      = hidden_field_tag "label_name[]", u(label), id: nil
+      = hidden_field_tag "label_name[]", label, id: nil
 .dropdown
   %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
     %span.dropdown-toggle-text
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index af753496260b1c0e210cee2a3dddb4703c23ec7b..7ae4211ddfdf93f9e51cca976e5d2d413654bae4 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -6,12 +6,13 @@
   %strong.item-title
     Snippet #{@snippet.to_reference}
   %span.creator
-    created by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title")}
+    authored
     = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
     - if @snippet.updated_at != @snippet.created_at
       %span
         = icon('edit', title: 'edited')
         = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
+    by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")}
 
   .snippet-actions
     - if @snippet.project_id?
@@ -19,6 +20,5 @@
     - else
       = render "snippets/actions"
 
-.content-block.second-block
-  %h2.snippet-title.prepend-top-0.append-bottom-0
-    = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author
+%h2.snippet-title.prepend-top-0.append-bottom-0
+  = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index c96dfefe17f5d21e0634714c9cfdf2e35687aab5..ea17bec8677ed48680cbb7c8abbdaebfda7396f5 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -3,19 +3,30 @@
 
   .title
     = link_to reliable_snippet_path(snippet) do
-      = truncate(snippet.title, length: 60)
+      = snippet.title
       - if snippet.private?
-        %span.label.label-gray
+        %span.label.label-gray.hidden-xs
           = icon('lock')
           private
-    %span.monospace.pull-right
+    %span.monospace.pull-right.hidden-xs
       = snippet.file_name
 
-  %small.pull-right.cgray
+    %ul.controls.visible-xs
+      %li
+        - note_count = snippet.notes.user.count
+        = link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
+          = icon('comments')
+          = note_count
+      %li
+        %span.sr-only
+          = visibility_level_label(snippet.visibility_level)
+        = visibility_level_icon(snippet.visibility_level, fw: false)
+
+  %small.pull-right.cgray.hidden-xs
     - if snippet.project_id?
       = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
 
-  .snippet-info
+  .snippet-info.hidden-xs
     = link_to user_snippets_path(snippet.author) do
       = snippet.author_name
     authored #{time_ago_with_tooltip(snippet.created_at)}
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 160c6cd84da168dc74229839b0807489247b2b77..fdaca199218b2e557f9c72a68a57dee5e087850e 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -2,12 +2,12 @@
   - if current_user
     = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
       New Snippet
-  - if can?(current_user, :update_personal_snippet, @snippet)
-    = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
-      Edit
   - if can?(current_user, :admin_personal_snippet, @snippet)
     = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
       Delete
+  - if can?(current_user, :update_personal_snippet, @snippet)
+    = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
+      Edit
 - if current_user
   .visible-xs-block.dropdown
     %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -18,11 +18,11 @@
         %li
           = link_to new_snippet_path, title: "New Snippet" do
             New Snippet
-        - if can?(current_user, :update_personal_snippet, @snippet)
-          %li
-            = link_to edit_snippet_path(@snippet) do
-              Edit
         - if can?(current_user, :admin_personal_snippet, @snippet)
           %li
             = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
               Delete
+        - if can?(current_user, :update_personal_snippet, @snippet)
+          %li
+            = link_to edit_snippet_path(@snippet) do
+              Edit
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index ed3992650d4a0c741f809ab4f80e49e5cde3c29c..fa403da8f796253ad5a6afa3a46d46e170803519 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,13 +1,12 @@
 - page_title @snippet.title, "Snippets"
 
-.snippet-holder
-  = render 'shared/snippets/header'
+= render 'shared/snippets/header'
 
-  %article.file-holder.file-holder-no-border.snippet-file-content
-    .file-title.file-title-clear
-      = blob_icon 0, @snippet.file_name
-      = @snippet.file_name
-      .file-actions.hidden-xs
-        = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
-        = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
-    = render 'shared/snippets/blob'
+%article.file-holder.snippet-file-content
+  .file-title
+    = blob_icon 0, @snippet.file_name
+    = @snippet.file_name
+    .file-actions
+      = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
+      = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
+  = render 'shared/snippets/blob'
diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml
index 75fb0e303ada8cb196d366576b5c574e78960ea4..9657101ace5cba69f9bec900839cb121b3bf7bc8 100644
--- a/app/views/u2f/_authenticate.html.haml
+++ b/app/views/u2f/_authenticate.html.haml
@@ -20,6 +20,8 @@
   %div
     %p We heard back from your U2F device. Click this button to authenticate with the GitLab server.
     = form_tag(new_user_session_path, method: :post) do |f|
+      - resource_params = params[resource_name].presence || params
+      = hidden_field_tag 'user[remember_me]', resource_params.fetch(:remember_me, 0)
       = hidden_field_tag 'user[device_response]', nil, class: 'form-control', required: true, id: "js-device-response"
       = submit_tag "Authenticate via U2F Device", class: "btn btn-success"
 
diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml
index 77f2ddefb1e28bff028e29c1cf807913363b222e..09ff8a76d2711da184e8b7e7b10bf74064607039 100644
--- a/app/views/users/calendar.html.haml
+++ b/app/views/users/calendar.html.haml
@@ -4,6 +4,6 @@
     Summary of issues, merge requests, and push events
 :javascript
   new Calendar(
-    #{@timestamps.to_json},
+    #{@activity_dates.to_json},
     '#{user_calendar_activities_path}'
-  );
+  );
\ No newline at end of file
diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5883cafe1d16db9fdd29b4cd80e7c6dcc7868511
--- /dev/null
+++ b/app/workers/prune_old_events_worker.rb
@@ -0,0 +1,17 @@
+class PruneOldEventsWorker
+  include Sidekiq::Worker
+
+  def perform
+    # Contribution calendar shows maximum 12 months of events.
+    # Double nested query is used because MySQL doesn't allow DELETE subqueries
+    # on the same table.
+    Event.unscoped.where(
+      '(id IN (SELECT id FROM (?) ids_to_remove))',
+      Event.unscoped.where(
+        'created_at < ?',
+        (12.months + 1.day).ago).
+      select(:id).
+      limit(10_000)).
+    delete_all
+  end
+end
diff --git a/changelogs/archive.md b/changelogs/archive.md
new file mode 100644
index 0000000000000000000000000000000000000000..c68ab694d39bba375fac3ca7066186b7ba2c45d6
--- /dev/null
+++ b/changelogs/archive.md
@@ -0,0 +1,1810 @@
+## 7.14.3
+
+- No changes
+
+## 7.14.2
+
+- Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with ISO-encoded files (Stan Hu)
+- Allow configuration of LDAP attributes GitLab will use for the new user account.
+
+## 7.14.1
+
+- Improve abuse reports management from admin area
+- Fix "Reload with full diff" URL button in compare branch view (Stan Hu)
+- Disabled DNS lookups for SSH in docker image (Rowan Wookey)
+- Only include base URL in OmniAuth full_host parameter (Stan Hu)
+- Fix Error 500 in API when accessing a group that has an avatar (Stan Hu)
+- Ability to enable SSL verification for Webhooks
+
+## 7.14.0
+
+- Fix bug where non-project members of the target project could set labels on new merge requests.
+- Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller)
+- Fix redirection after sign in when using auto_sign_in_with_provider
+- Upgrade gitlab_git to 7.2.14 to ignore CRLFs in .gitmodules (Stan Hu)
+- Clear cache to prevent listing deleted branches after MR removes source branch (Stan Hu)
+- Provide more feedback what went wrong if HipChat service failed test (Stan Hu)
+- Fix bug where backslashes in inline diffs could be dropped (Stan Hu)
+- Disable turbolinks when linking to Bitbucket import status (Stan Hu)
+- Fix broken code import and display error messages if something went wrong with creating project (Stan Hu)
+- Fix corrupted binary files when using API files endpoint (Stan Hu)
+- Bump Haml to 4.0.7 to speed up textarea rendering (Stan Hu)
+- Show incompatible projects in Bitbucket import status (Stan Hu)
+- Fix coloring of diffs on MR Discussion-tab (Gert Goet)
+- Fix "Network" and "Graphs" pages for branches with encoded slashes (Stan Hu)
+- Fix errors deleting and creating branches with encoded slashes (Stan Hu)
+- Always add current user to autocomplete controller to support filter by "Me" (Stan Hu)
+- Fix multi-line syntax highlighting (Stan Hu)
+- Fix network graph when branch name has single quotes (Stan Hu)
+- Add "Confirm user" button in user admin page (Stan Hu)
+- Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu)
+- Add support for Unicode filenames in relative links (Hiroyuki Sato)
+- Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki)
+- Fix commit data retrieval when branch name has single quotes (Stan Hu)
+- Check that project was actually created rather than just validated in import:repos task (Stan Hu)
+- Fix full screen mode for snippet comments (Daniel Gerhardt)
+- Fix 404 error in files view after deleting the last file in a repository (Stan Hu)
+- Fix the "Reload with full diff" URL button (Stan Hu)
+- Fix label read access for unauthenticated users (Daniel Gerhardt)
+- Fix access to disabled features for unauthenticated users (Daniel Gerhardt)
+- Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu)
+- Fix file upload dialog for comment editing (Daniel Gerhardt)
+- Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu)
+- Return comments in created order in merge request API (Stan Hu)
+- Disable internal issue tracker controller if external tracker is used (Stan Hu)
+- Expire Rails cache entries after two weeks to prevent endless Redis growth
+- Add support for destroying project milestones (Stan Hu)
+- Allow custom backup archive permissions
+- Add project star and fork count, group avatar URL and user/group web URL attributes to API
+- Show who last edited a comment if it wasn't the original author
+- Send notification to all participants when MR is merged.
+- Add ability to manage user email addresses via the API.
+- Show buttons to add license, changelog and contribution guide if they're missing.
+- Tweak project page buttons.
+- Disabled autocapitalize and autocorrect on login field (Daryl Chan)
+- Mention group and project name in creation, update and deletion notices (Achilleas Pipinellis)
+- Update gravatar link on profile page to link to configured gravatar host (Ben Bodenmiller)
+- Remove redis-store TTL monkey patch
+- Add support for CI skipped status
+- Fetch code from forks to refs/merge-requests/:id/head when merge request created
+- Remove comments and email addresses when publicly exposing ssh keys (Zeger-Jan van de Weg)
+- Add "Check out branch" button to the MR page.
+- Improve MR merge widget text and UI consistency.
+- Improve text in MR "How To Merge" modal.
+- Cache all events
+- Order commits by date when comparing branches
+- Fix bug causing error when the target branch of a symbolic ref was deleted
+- Include branch/tag name in archive file and directory name
+- Add dropzone upload progress
+- Add a label for merged branches on branches page (Florent Baldino)
+- Detect .mkd and .mkdn files as markdown (Ben Boeckel)
+- Fix: User search feature in admin area does not respect filters
+- Set max-width for README, issue and merge request description for easier read on big screens
+- Update Flowdock integration to support new Flowdock API (Boyan Tabakov)
+- Remove author from files view (Sven Strickroth)
+- Fix infinite loop when SAML was incorrectly configured.
+
+## 7.13.5
+
+- Satellites reverted
+
+## 7.13.4
+
+- Allow users to send abuse reports
+
+## 7.13.3
+
+- Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
+- Allow users to send abuse reports
+- Remove satellites
+- Link username to profile on Group Members page (Tom Webster)
+
+## 7.13.2
+
+- Fix randomly failed spec
+- Create project services on Project creation
+- Add admin_merge_request ability to Developer level and up
+- Fix Error 500 when browsing projects with no HEAD (Stan Hu)
+- Fix labels / assignee / milestone for the merge requests when issues are disabled
+- Show the first tab automatically on MergeRequests#new
+- Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt)
+- Fix Gmail Actions
+
+## 7.13.1
+
+- Fix: Label modifications are not reflected in existing notes and in the issue list
+- Fix: Label not shown in the Issue list, although it's set through web interface
+- Fix: Group/project references are linked incorrectly
+- Improve documentation
+- Fix of migration: Check if session_expire_delay column exists before adding the column
+- Fix: ActionView::Template::Error
+- Fix: "Create Merge Request" isn't always shown in event for newly pushed branch
+- Fix bug causing "Remove source-branch" option not to work for merge requests from the same project.
+- Render Note field hints consistently for "new" and "edit" forms
+
+## 7.13.0
+
+- Remove repository graph log to fix slow cache updates after push event (Stan Hu)
+- Only enable HSTS header for HTTPS and port 443 (Stan Hu)
+- Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
+- Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
+- Add branch switching support for graphs (Daniel Gerhardt)
+- Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
+- Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
+- Add support for unlocking users in admin settings (Stan Hu)
+- Add Irker service configuration options (Stan Hu)
+- Fix order of issues imported from GitHub (Hiroyuki Sato)
+- Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart)
+- Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI
+- Add `two_factor_enabled` field to admin user API (Stan Hu)
+- Fix invalid timestamps in RSS feeds (Rowan Wookey)
+- Fix downloading of patches on public merge requests when user logged out (Stan Hu)
+- Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu)
+- Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu)
+- Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu)
+- Support commenting on diffs in side-by-side mode (Stan Hu)
+- Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu)
+- Return 40x error codes if branch could not be deleted in UI (Stan Hu)
+- Remove project visibility icons from dashboard projects list
+- Rename "Design" profile settings page to "Preferences".
+- Allow users to customize their default Dashboard page.
+- Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8
+- Admin can edit and remove user identities
+- Convert CRLF newlines to LF when committing using the web editor.
+- API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged.
+- Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled.
+- Show a user's Two-factor Authentication status in the administration area.
+- Explicit error when commit not found in the CI
+- Improve performance for issue and merge request pages
+- Users with guest access level can not set assignee, labels or milestones for issue and merge request
+- Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels
+- Better performance for pages with events list, issues list and commits list
+- Faster automerge check and merge itself when source and target branches are in same repository
+- Correctly show anonymous authorized applications under Profile > Applications.
+- Query Optimization in MySQL.
+- Allow users to be blocked and unblocked via the API
+- Use native Postgres database cleaning during backup restore
+- Redesign project page. Show README as default instead of activity. Move project activity to separate page
+- Make left menu more hierarchical and less contextual by adding back item at top
+- A fork can’t have a visibility level that is greater than the original project.
+- Faster code search in repository and wiki. Fixes search page timeout for big repositories
+- Allow administrators to disable 2FA for a specific user
+- Add error message for SSH key linebreaks
+- Store commits count in database (will populate with valid values only after first push)
+- Rebuild cache after push to repository in background job
+- Fix transferring of project to another group using the API.
+
+## 7.12.2
+
+- Correctly show anonymous authorized applications under Profile > Applications.
+- Faster automerge check and merge itself when source and target branches are in same repository
+- Audit log for user authentication
+- Allow custom label to be set for authentication providers.
+
+## 7.12.1
+
+- Fix error when deleting a user who has projects (Stan Hu)
+- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
+- Add SAML to list of social_provider (Matt Firtion)
+- Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets)
+- Fix closed merge request scope at milestone page (Dmitriy Zaporozhets)
+- Revert merge request states renaming
+- Fix hooks for web based events with external issue references (Daniel Gerhardt)
+- Improve performance for issue and merge request pages
+- Compress database dumps to reduce backup size
+
+## 7.12.0
+
+- Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu)
+- Disable changing of target branch in new merge request page when a branch has already been specified (Stan Hu)
+- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
+- Update oauth button logos for Twitter and Google to recommended assets
+- Update browser gem to version 0.8.0 for IE11 support (Stan Hu)
+- Fix timeout when rendering file with thousands of lines.
+- Add "Remember me" checkbox to LDAP signin form.
+- Add session expiration delay configuration through UI application settings
+- Don't notify users mentioned in code blocks or blockquotes.
+- Omit link to generate labels if user does not have access to create them (Stan Hu)
+- Show warning when a comment will add 10 or more people to the discussion.
+- Disable changing of the source branch in merge request update API (Stan Hu)
+- Shorten merge request WIP text.
+- Add option to disallow users from registering any application to use GitLab as an OAuth provider
+- Support editing target branch of merge request (Stan Hu)
+- Refactor permission checks with issues and merge requests project settings (Stan Hu)
+- Fix Markdown preview not working in Edit Milestone page (Stan Hu)
+- Fix Zen Mode not closing with ESC key (Stan Hu)
+- Allow HipChat API version to be blank and default to v2 (Stan Hu)
+- Add file attachment support in Milestone description (Stan Hu)
+- Fix milestone "Browse Issues" button.
+- Set milestone on new issue when creating issue from index with milestone filter active.
+- Make namespace API available to all users (Stan Hu)
+- Add webhook support for note events (Stan Hu)
+- Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu)
+- Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu)
+- Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu)
+- Fix git blame syntax highlighting when different commits break up lines (Stan Hu)
+- Add "Resend confirmation e-mail" link in profile settings (Stan Hu)
+- Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka)
+- Disabled expansion of top/bottom blobs for new file diffs
+- Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka)
+- Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka)
+- Use the user list from the target project in a merge request (Stan Hu)
+- Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen)
+- Add validation to wiki page creation (only [a-zA-Z0-9/_-] are allowed) (Jeroen van Baarsen)
+- Fix new/empty milestones showing 100% completion value (Jonah Bishop)
+- Add a note when an Issue or Merge Request's title changes
+- Consistently refer to MRs as either Merged or Closed.
+- Add Merged tab to MR lists.
+- Prefix EmailsOnPush email subject with `[Git]`.
+- Group project contributions by both name and email.
+- Clarify navigation labels for Project Settings and Group Settings.
+- Move user avatar and logout button to sidebar
+- You can not remove user if he/she is an only owner of group
+- User should be able to leave group. If not - show him proper message
+- User has ability to leave project
+- Add SAML support as an omniauth provider
+- Allow to configure a URL to show after sign out
+- Add an option to automatically sign-in with an Omniauth provider
+- GitLab CI service sends .gitlab-ci.yml in each push call
+- When remove project - move repository and schedule it removal
+- Improve group removing logic
+- Trigger create-hooks on backup restore task
+- Add option to automatically link omniauth and LDAP identities
+- Allow special character in users bio. I.e.: I <3 GitLab
+
+## 7.11.4
+
+- Fix missing bullets when creating lists
+- Set rel="nofollow" on external links
+
+## 7.11.3
+
+- no changes
+- Fix upgrader script (Martins Polakovs)
+
+## 7.11.2
+
+- no changes
+
+## 7.11.1
+
+- no changes
+
+## 7.11.0
+
+- Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger)
+- Get editing comments to work in Chrome 43 again.
+- Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu)
+- Don't show duplicate deploy keys
+- Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger)
+- Make the first branch pushed to an empty repository the default HEAD (Stan Hu)
+- Fix broken view when using a tag to display a tree that contains git submodules (Stan Hu)
+- Make Reply-To config apply to change e-mail confirmation and other Devise notifications (Stan Hu)
+- Add application setting to restrict user signups to e-mail domains (Stan Hu)
+- Don't allow a merge request to be merged when its title starts with "WIP".
+- Add a page title to every page.
+- Allow primary email to be set to an email that you've already added.
+- Fix clone URL field and X11 Primary selection (Dmitry Medvinsky)
+- Ignore invalid lines in .gitmodules
+- Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu)
+- Redirect to sign in page after signing out.
+- Fix "Hello @username." references not working by no longer allowing usernames to end in period.
+- Fix "Revspec not found" errors when viewing diffs in a forked project with submodules (Stan Hu)
+- Improve project page UI
+- Fix broken file browsing with relative submodule in personal projects (Stan Hu)
+- Add "Reply quoting selected text" shortcut key (`r`)
+- Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention.
+- Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention.
+- When use change branches link at MR form - save source branch selection instead of target one
+- Improve handling of large diffs
+- Added GitLab Event header for project hooks
+- Add Two-factor authentication (2FA) for GitLab logins
+- Show Atom feed buttons everywhere where applicable.
+- Add project activity atom feed.
+- Don't crash when an MR from a fork has a cross-reference comment from the target project on one of its commits.
+- Explain how to get a new password reset token in welcome emails
+- Include commit comments in MR from a forked project.
+- Group milestones by title in the dashboard and all other issue views.
+- Query issues, merge requests and milestones with their IID through API (Julien Bianchi)
+- Add default project and snippet visibility settings to the admin web UI.
+- Show incompatible projects in Google Code import status (Stan Hu)
+- Fix bug where commit data would not appear in some subdirectories (Stan Hu)
+- Task lists are now usable in comments, and will show up in Markdown previews.
+- Fix bug where avatar filenames were not actually deleted from the database during removal (Stan Hu)
+- Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu)
+- Protect OmniAuth request phase against CSRF.
+- Don't send notifications to mentioned users that don't have access to the project in question.
+- Add search issues/MR by number
+- Change plots to bar graphs in commit statistics screen
+- Move snippets UI to fluid layout
+- Improve UI for sidebar. Increase separation between navigation and content
+- Improve new project command options (Ben Bodenmiller)
+- Add common method to force UTF-8 and use it to properly handle non-ascii OAuth user properties (Onur Küçük)
+- Prevent sending empty messages to HipChat (Chulki Lee)
+- Improve UI for mobile phones on dashboard and project pages
+- Add room notification and message color option for HipChat
+- Allow to use non-ASCII letters and dashes in project and namespace name. (Jakub Jirutka)
+- Add footnotes support to Markdown (Guillaume Delbergue)
+- Add current_sign_in_at to UserFull REST api.
+- Make Sidekiq MemoryKiller shutdown signal configurable
+- Add "Create Merge Request" buttons to commits and branches pages and push event.
+- Show user roles by comments.
+- Fix automatic blocking of auto-created users from Active Directory.
+- Call merge request webhook for each new commits (Arthur Gautier)
+- Use SIGKILL by default in Sidekiq::MemoryKiller
+- Fix mentioning of private groups.
+- Add style for <kbd> element in markdown
+- Spin spinner icon next to "Checking for CI status..." on MR page.
+- Fix reference links in dashboard activity and ATOM feeds.
+- Ensure that the first added admin performs repository imports
+
+## 7.10.4
+
+- Fix migrations broken in 7.10.2
+- Make tags for GitLab installations running on MySQL case sensitive
+- Get Gitorious importer to work again.
+- Fix adding new group members from admin area
+- Fix DB error when trying to tag a repository (Stan Hu)
+- Fix Error 500 when searching Wiki pages (Stan Hu)
+- Unescape branch names in compare commit (Stan Hu)
+- Order commit comments chronologically in API.
+
+## 7.10.2
+
+- Fix CI links on MR page
+
+## 7.10.0
+
+- Ignore submodules that are defined in .gitmodules but are checked in as directories.
+- Allow projects to be imported from Google Code.
+- Remove access control for uploaded images to fix broken images in emails (Hannes Rosenögger)
+- Allow users to be invited by email to join a group or project.
+- Don't crash when project repository doesn't exist.
+- Add config var to block auto-created LDAP users.
+- Don't use HTML ellipsis in EmailsOnPush subject truncated commit message.
+- Set EmailsOnPush reply-to address to committer email when enabled.
+- Fix broken file browsing with a submodule that contains a relative link (Stan Hu)
+- Fix persistent XSS vulnerability around profile website URLs.
+- Fix project import URL regex to prevent arbitary local repos from being imported.
+- Fix directory traversal vulnerability around uploads routes.
+- Fix directory traversal vulnerability around help pages.
+- Don't leak existence of project via search autocomplete.
+- Don't leak existence of group or project via search.
+- Fix bug where Wiki pages that included a '/' were no longer accessible (Stan Hu)
+- Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu)
+- Add a rake task to check repository integrity with `git fsck`
+- Add ability to configure Reply-To address in gitlab.yml (Stan Hu)
+- Move current user to the top of the list in assignee/author filters (Stan Hu)
+- Fix broken side-by-side diff view on merge request page (Stan Hu)
+- Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu)
+- Allow HTML tags in Markdown input
+- Fix code unfold not working on Compare commits page (Stan Hu)
+- Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik)
+- Fix "Import projects from" button to show the correct instructions (Stan Hu)
+- Fix dots in Wiki slugs causing errors (Stan Hu)
+- Make maximum attachment size configurable via Application Settings (Stan Hu)
+- Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg)
+- Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu)
+- Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu)
+- Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu)
+- enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger)
+- Fix a link in the patch update guide
+- Add a service to support external wikis (Hannes Rosenögger)
+- Omit the "email patches" link and fix plain diff view for merge commits
+- List new commits for newly pushed branch in activity view.
+- Add sidetiq gem dependency to match EE
+- Add changelog, license and contribution guide links to project tab bar.
+- Improve diff UI
+- Fix alignment of navbar toggle button (Cody Mize)
+- Fix checkbox rendering for nested task lists
+- Identical look of selectboxes in UI
+- Upgrade the gitlab_git gem to version 7.1.3
+- Move "Import existing repository by URL" option to button.
+- Improve error message when save profile has error.
+- Passing the name of pushed ref to CI service (requires GitLab CI 7.9+)
+- Add location field to user profile
+- Fix print view for markdown files and wiki pages
+- Fix errors when deleting old backups
+- Improve GitLab performance when working with git repositories
+- Add tag message and last commit to tag hook (Kamil Trzciński)
+- Restrict permissions on backup files
+- Improve oauth accounts UI in profile page
+- Add ability to unlink connected accounts
+- Replace commits calendar with faster contribution calendar that includes issues and merge requests
+- Add inifinite scroll to user page activity
+- Don't include system notes in issue/MR comment count.
+- Don't mark merge request as updated when merge status relative to target branch changes.
+- Link note avatar to user.
+- Make Git-over-SSH errors more descriptive.
+- Fix EmailsOnPush.
+- Refactor issue filtering
+- AJAX selectbox for issue assignee and author filters
+- Fix issue with missing options in issue filtering dropdown if selected one
+- Prevent holding Control-Enter or Command-Enter from posting comment multiple times.
+- Prevent note form from being cleared when submitting failed.
+- Improve file icons rendering on tree (Sullivan Sénéchal)
+- API: Add pagination to project events
+- Get issue links in notification mail to work again.
+- Don't show commit comment button when user is not signed in.
+- Fix admin user projects lists.
+- Don't leak private group existence by redirecting from namespace controller to group controller.
+- Ability to skip some items from backup (database, respositories or uploads)
+- Archive repositories in background worker.
+- Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace.
+- Project labels are now available over the API under the "tag_list" field (Cristian Medina)
+- Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz)
+- Fix and improve help rendering (Sullivan Sénéchal)
+- Fix final line in EmailsOnPush email diff being rendered as error.
+- Prevent duplicate Buildkite service creation.
+- Fix git over ssh errors 'fatal: protocol error: bad line length character'
+- Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled
+- Bust group page project list cache when namespace name or path changes.
+- Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded
+- Allow user to choose a public email to show on public profile
+- Remove truncation from issue titles on milestone page (Jason Blanchard)
+- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
+- Fix merge request comments on files with multiple commits
+- Fix Resource Owner Password Authentication Flow
+- Add icons to Add dropdown items.
+- Allow admin to create public deploy keys that are accessible to any project.
+- Warn when gitlab-shell version doesn't match requirement.
+- Skip email confirmation when set by admin or via LDAP.
+- Only allow users to reference groups, projects, issues, MRs, commits they have access to.
+
+## 7.9.4
+
+- Security: Fix project import URL regex to prevent arbitary local repos from being imported
+- Fixed issue where only 25 commits would load in file listings
+- Fix LDAP identities  after config update
+
+## 7.9.3
+
+- Contains no changes
+
+## 7.9.2
+
+- Contains no changes
+
+## 7.9.1
+
+- Include missing events and fix save functionality in admin service template settings form (Stan Hu)
+- Fix "Import projects from" button to show the correct instructions (Stan Hu)
+- Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu)
+- Fix for LDAP with commas in DN
+- Fix missing events and in admin Slack service template settings form (Stan Hu)
+- Don't show commit comment button when user is not signed in.
+- Downgrade gemnasium-gitlab-service gem
+
+## 7.9.0
+
+- Add HipChat integration documentation (Stan Hu)
+- Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu)
+- Fix broken email images (Hannes Rosenögger)
+- Automatically config git if user forgot, where possible (Zeger-Jan van de Weg)
+- Fix mass SQL statements on initial push (Hannes Rosenögger)
+- Add tag push notifications and normalize HipChat and Slack messages to be consistent (Stan Hu)
+- Add comment notification events to HipChat and Slack services (Stan Hu)
+- Add issue and merge request events to HipChat and Slack services (Stan Hu)
+- Fix merge request URL passed to Webhooks. (Stan Hu)
+- Fix bug that caused a server error when editing a comment to "+1" or "-1" (Stan Hu)
+- Fix code preview theme setting for comments, issues, merge requests, and snippets (Stan Hu)
+- Move labels/milestones tabs to sidebar
+- Upgrade Rails gem to version 4.1.9.
+- Improve error messages for file edit failures
+- Improve UI for commits, issues and merge request lists
+- Fix commit comments on first line of diff not rendering in Merge Request Discussion view.
+- Allow admins to override restricted project visibility settings.
+- Move restricted visibility settings from gitlab.yml into the web UI.
+- Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev)
+- Save web edit in new branch
+- Fix ordering of imported but unchanged projects (Marco Wessel)
+- Mobile UI improvements: make aside content expandable
+- Expose avatar_url in projects API
+- Fix checkbox alignment on the application settings page.
+- Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger)
+- Fix mass-unassignment of issues (Robert Speicher)
+- Fix hidden diff comments in merge request discussion view
+- Allow user confirmation to be skipped for new users via API
+- Add a service to send updates to an Irker gateway (Romain Coltel)
+- Add brakeman (security scanner for Ruby on Rails)
+- Slack username and channel options
+- Add grouped milestones from all projects to dashboard.
+- Webhook sends pusher email as well as commiter
+- Add Bitbucket omniauth provider.
+- Add Bitbucket importer.
+- Support referencing issues to a project whose name starts with a digit
+- Condense commits already in target branch when updating merge request source branch.
+- Send notifications and leave system comments when bulk updating issues.
+- Automatically link commit ranges to compare page: sha1...sha4 or sha1..sha4 (includes sha1 in comparison)
+- Move groups page from profile to dashboard
+- Starred projects page at dashboard
+- Blocking user does not remove him/her from project/groups but show blocked label
+- Change subject of EmailsOnPush emails to include namespace, project and branch.
+- Change subject of EmailsOnPush emails to include first commit message when multiple were pushed.
+- Remove confusing footer from EmailsOnPush mail body.
+- Add list of changed files to EmailsOnPush emails.
+- Add option to send EmailsOnPush emails from committer email if domain matches.
+- Add option to disable code diffs in EmailOnPush emails.
+- Wrap commit message in EmailsOnPush email.
+- Send EmailsOnPush emails when deleting commits using force push.
+- Fix EmailsOnPush email comparison link to include first commit.
+- Fix highliht of selected lines in file
+- Reject access to group/project avatar if the user doesn't have access.
+- Add database migration to clean group duplicates with same path and name (Make sure you have a backup before update)
+- Add GitLab active users count to rake gitlab:check
+- Starred projects page at dashboard
+- Make email display name configurable
+- Improve json validation in hook data
+- Use Emoji One
+- Updated emoji help documentation to properly reference EmojiOne.
+- Fix missing GitHub organisation repositories on import page.
+- Added blue theme
+- Remove annoying notice messages when create/update merge request
+- Allow smb:// links in Markdown text.
+- Filter merge request by title or description at Merge Requests page
+- Block user if he/she was blocked in Active Directory
+- Fix import pages not working after first load.
+- Use custom LDAP label in LDAP signin form.
+- Execute hooks and services when branch or tag is created or deleted through web interface.
+- Block and unblock user if he/she was blocked/unblocked in Active Directory
+- Raise recommended number of unicorn workers from 2 to 3
+- Use same layout and interactivity for project members as group members.
+- Prevent gitlab-shell character encoding issues by receiving its changes as raw data.
+- Ability to unsubscribe/subscribe to issue or merge request
+- Delete deploy key when last connection to a project is destroyed.
+- Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther)
+- Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup)
+- Add canceled status for CI
+- Send EmailsOnPush email when branch or tag is created or deleted.
+- Faster merge request processing for large repository
+- Prevent doubling AJAX request with each commit visit via Turbolink
+- Prevent unnecessary doubling of js events on import pages and user calendar
+
+## 7.8.4
+
+- Fix issue_tracker_id substitution in custom issue trackers
+- Fix path and name duplication in namespaces
+
+## 7.8.3
+
+- Bump version of gitlab_git fixing annotated tags without message
+
+## 7.8.2
+
+- Fix service migration issue when upgrading from versions prior to 7.3
+- Fix setting of the default use project limit via admin UI
+- Fix showing of already imported projects for GitLab and Gitorious importers
+- Fix response of push to repository to return "Not found" if user doesn't have access
+- Fix check if user is allowed to view the file attachment
+- Fix import check for case sensetive namespaces
+- Increase timeout for Git-over-HTTP requests to 1 hour since large pulls/pushes can take a long time.
+- Properly handle autosave local storage exceptions.
+- Escape wildcards when searching LDAP by username.
+
+## 7.8.1
+
+- Fix run of custom post receive hooks
+- Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3
+- Fix the warning for LDAP users about need to set password
+- Fix avatars which were not shown for non logged in users
+- Fix urls for the issues when relative url was enabled
+
+## 7.8.0
+
+- Fix access control and protection against XSS for note attachments and other uploads.
+- Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
+- Make project search case insensitive (Hannes Rosenögger)
+- Include issue/mr participants in list of recipients for reassign/close/reopen emails
+- Expose description in groups API
+- Better UI for project services page
+- Cleaner UI for web editor
+- Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
+- Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen)
+- View note image attachments in new tab when clicked instead of downloading them
+- Improve sorting logic in UI and API. Explicitly define what sorting method is used by default
+- Fix overflow at sidebar when have several items
+- Add notes for label changes in issue and merge requests
+- Show tags in commit view (Hannes Rosenögger)
+- Only count a user's vote once on a merge request or issue (Michael Clarke)
+- Increase font size when browse source files and diffs
+- Service Templates now let you set default values for all services
+- Create new file in empty repository using GitLab UI
+- Ability to clone project using oauth2 token
+- Upgrade Sidekiq gem to version 3.3.0
+- Stop git zombie creation during force push check
+- Show success/error messages for test setting button in services
+- Added Rubocop for code style checks
+- Fix commits pagination
+- Async load a branch information at the commit page
+- Disable blacklist validation for project names
+- Allow configuring protection of the default branch upon first push (Marco Wessel)
+- Add gitlab.com importer
+- Add an ability to login with gitlab.com
+- Add a commit calendar to the user profile (Hannes Rosenögger)
+- Submit comment on command-enter
+- Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`.
+- Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" (Julien Bianchi and Hannes Rosenögger)
+- Fix long broadcast message cut-off on left sidebar (Visay Keo)
+- Add Project Avatars (Steven Thonus and Hannes Rosenögger)
+- Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
+- Edit group members via API
+- Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks)
+- Add action property to merge request hook (Julien Bianchi)
+- Remove duplicates from group milestone participants list.
+- Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger)
+- API: Access groups with their path (Julien Bianchi)
+- Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard)
+- Allow notification email to be set separately from primary email.
+- API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
+- Don't have Markdown preview fail for long comments/wiki pages.
+- When test webhook - show error message instead of 500 error page if connection to hook url was reset
+- Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
+- Added persistent collapse button for left side nav bar (Jason Blanchard)
+- Prevent losing unsaved comments by automatically restoring them when comment page is loaded again.
+- Don't allow page to be scaled on mobile.
+- Clean the username acquired from OAuth/LDAP so it doesn't fail username validation and block signing up.
+- Show assignees in merge request index page (Kelvin Mutuma)
+- Link head panel titles to relevant root page.
+- Allow users that signed up via OAuth to set their password in order to use Git over HTTP(S).
+- Show users button to share their newly created public or internal projects on twitter
+- Add quick help links to the GitLab pricing and feature comparison pages.
+- Fix duplicate authorized applications in user profile and incorrect application client count in admin area.
+- Make sure Markdown previews always use the same styling as the eventual destination.
+- Remove deprecated Group#owner_id from API
+- Show projects user contributed to on user page. Show stars near project on user page.
+- Improve database performance for GitLab
+- Add Asana service (Jeremy Benoist)
+- Improve project webhooks with extra data
+
+## 7.7.2
+
+- Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
+- Fix issue when LDAP user can't login with existing GitLab account
+
+## 7.7.1
+
+- Improve mention autocomplete performance
+- Show setup instructions for GitHub import if disabled
+- Allow use http for OAuth applications
+
+## 7.7.0
+
+- Import from GitHub.com feature
+- Add Jetbrains Teamcity CI service (Jason Lippert)
+- Mention notification level
+- Markdown preview in wiki (Yuriy Glukhov)
+- Raise group avatar filesize limit to 200kb
+- OAuth applications feature
+- Show user SSH keys in admin area
+- Developer can push to protected branches option
+- Set project path instead of project name in create form
+- Block Git HTTP access after 10 failed authentication attempts
+- Updates to the messages returned by API (sponsored by O'Reilly Media)
+- New UI layout with side navigation
+- Add alert message in case of outdated browser (IE < 10)
+- Added API support for sorting projects
+- Update gitlab_git to version 7.0.0.rc14
+- Add API project search filter option for authorized projects
+- Fix File blame not respecting branch selection
+- Change some of application settings on fly in admin area UI
+- Redesign signin/signup pages
+- Close standard input in Gitlab::Popen.popen
+- Trigger GitLab CI when push tags
+- When accept merge request - do merge using sidaekiq job
+- Enable web signups by default
+- Fixes for diff comments: drag-n-drop images, selecting images
+- Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update
+- Remove password strength indicator
+
+## 7.6.0
+
+- Fork repository to groups
+- New rugged version
+- Add CRON=1 backup setting for quiet backups
+- Fix failing wiki restore
+- Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
+- Monokai highlighting style now more faithful to original design (Mark Riedesel)
+- Create project with repository in synchrony
+- Added ability to create empty repo or import existing one if project does not have repository
+- Reactivate highlight.js language autodetection
+- Mobile UI improvements
+- Change maximum avatar file size from 100KB to 200KB
+- Strict validation for snippet file names
+- Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada)
+- In the docker directory is a container template based on the Omnibus packages.
+- Update Sidekiq to version 2.17.8
+- Add author filter to project issues and merge requests pages
+- Atom feed for user activity
+- Support multiple omniauth providers for the same user
+- Rendering cross reference in issue title and tooltip for merge request
+- Show username in comments
+- Possibility to create Milestones or Labels when Issues are disabled
+- Fix bug with showing gpg signature in tag
+
+## 7.5.3
+
+- Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
+
+## 7.5.2
+
+- Don't log Sidekiq arguments by default
+- Fix restore of wiki repositories from backups
+
+## 7.5.1
+
+- Add missing timestamps to 'members' table
+
+## 7.5.0
+
+- API: Add support for Hipchat (Kevin Houdebert)
+- Add time zone configuration in gitlab.yml (Sullivan Senechal)
+- Fix LDAP authentication for Git HTTP access
+- Run 'GC.start' after every EmailsOnPushWorker job
+- Fix LDAP config lookup for provider 'ldap'
+- Drop all sequences during Postgres database restore
+- Project title links to project homepage (Ben Bodenmiller)
+- Add Atlassian Bamboo CI service (Drew Blessing)
+- Mentioned @user will receive email even if he is not participating in issue or commit
+- Session API: Use case-insensitive authentication like in UI (Andrey Krivko)
+- Tie up loose ends with annotated tags: API & UI (Sean Edge)
+- Return valid json for deleting branch via API (sponsored by O'Reilly Media)
+- Expose username in project events API (sponsored by O'Reilly Media)
+- Adds comments to commits in the API
+- Performance improvements
+- Fix post-receive issue for projects with deleted forks
+- New gitlab-shell version with custom hooks support
+- Improve code
+- GitLab CI 5.2+ support (does not support older versions)
+- Fixed bug when you can not push commits starting with 000000 to protected branches
+- Added a password strength indicator
+- Change project name and path in one form
+- Display renamed files in diff views (Vinnie Okada)
+- Fix raw view for public snippets
+- Use secret token with GitLab internal API.
+- Add missing timestamps to 'members' table
+
+## 7.4.5
+
+- Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
+
+## 7.4.4
+
+- No changes
+
+## 7.4.3
+
+- Fix raw snippets view
+- Fix security issue for member api
+- Fix buildbox integration
+
+## 7.4.2
+
+- Fix internal snippet exposing for unauthenticated users
+
+## 7.4.1
+
+- Fix LDAP authentication for Git HTTP access
+- Fix LDAP config lookup for provider 'ldap'
+- Fix public snippets
+- Fix 500 error on projects with nested submodules
+
+## 7.4.0
+
+- Refactored membership logic
+- Improve error reporting on users API (Julien Bianchi)
+- Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally
+- Default branch is protected by default
+- Increase unicorn timeout to 60 seconds
+- Sort search autocomplete projects by stars count so most popular go first
+- Add README to tab on project show page
+- Do not delete tmp/repositories itself during clean-up, only its contents
+- Support for backup uploads to remote storage
+- Prevent notes polling when there are not notes
+- Internal ForkService: Prepare support for fork to a given namespace
+- API: Add support for forking a project via the API (Bernhard Kaindl)
+- API: filter project issues by milestone (Julien Bianchi)
+- Fail harder in the backup script
+- Changes to Slack service structure, only webhook url needed
+- Zen mode for wiki and milestones (Robert Schilling)
+- Move Emoji parsing to html-pipeline-gitlab (Robert Schilling)
+- Font Awesome 4.2 integration (Sullivan Senechal)
+- Add Pushover service integration (Sullivan Senechal)
+- Add select field type for services options (Sullivan Senechal)
+- Add cross-project references to the Markdown parser (Vinnie Okada)
+- Add task lists to issue and merge request descriptions (Vinnie Okada)
+- Snippets can be public, internal or private
+- Improve danger zone: ask project path to confirm data-loss action
+- Raise exception on forgery
+- Show build coverage in Merge Requests (requires GitLab CI v5.1)
+- New milestone and label links on issue edit form
+- Improved repository graphs
+- Improve event note display in dashboard and project activity views (Vinnie Okada)
+- Add users sorting to admin area
+- UI improvements
+- Fix ambiguous sha problem with mentioned commit
+- Fixed bug with apostrophe when at mentioning users
+- Add active directory ldap option
+- Developers can push to wiki repo. Protected branches does not affect wiki repo any more
+- Faster rev list
+- Fix branch removal
+
+## 7.3.2
+
+- Fix creating new file via web editor
+- Use gitlab-shell v2.0.1
+
+## 7.3.1
+
+- Fix ref parsing in Gitlab::GitAccess
+- Fix error 500 when viewing diff on a file with changed permissions
+- Fix adding comments to MR when source branch is master
+- Fix error 500 when searching description contains relative link
+
+## 7.3.0
+
+- Always set the 'origin' remote in satellite actions
+- Write authorized_keys in tmp/ during tests
+- Use sockets to connect to Redis
+- Add dormant New Relic gem (can be enabled via environment variables)
+- Expire Rack sessions after 1 week
+- Cleaner signin/signup pages
+- Improved comments UI
+- Better search with filtering, pagination etc
+- Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov)
+- Prevent project stars duplication when fork project
+- Use the default Unicorn socket backlog value of 1024
+- Support Unix domain sockets for Redis
+- Store session Redis keys in 'session:gitlab:' namespace
+- Deprecate LDAP account takeover based on partial LDAP email / GitLab username match
+- Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy)
+- Keyboard shortcuts for productivity (Robert Schilling)
+- API: filter issues by state (Julien Bianchi)
+- API: filter issues by labels (Julien Bianchi)
+- Add system hook for ssh key changes
+- Add blob permalink link (Ciro Santilli)
+- Create annotated tags through UI and API (Sean Edge)
+- Snippets search (Charles Bushong)
+- Comment new push to existing MR
+- Add 'ci' to the blacklist of forbidden names
+- Improve text filtering on issues page
+- Comment & Close button
+- Process git push --all much faster
+- Don't allow edit of system notes
+- Project wiki search (Ralf Seidler)
+- Enabled Shibboleth authentication support (Matus Banas)
+- Zen mode (fullscreen) for issues/MR/notes (Robert Schilling)
+- Add ability to configure webhook timeout via gitlab.yml (Wes Gurney)
+- Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media)
+- Add Redis socket support to 'rake gitlab:shell:install'
+
+## 7.2.1
+
+- Delete orphaned labels during label migration (James Brooks)
+- Security: prevent XSS with stricter MIME types for raw repo files
+
+## 7.2.0
+
+- Explore page
+- Add project stars (Ciro Santilli)
+- Log Sidekiq arguments
+- Better labels: colors, ability to rename and remove
+- Improve the way merge request collects diffs
+- Improve compare page for large diffs
+- Expose the full commit message via API
+- Fix 500 error on repository rename
+- Fix bug when MR download patch return invalid diff
+- Test gitlab-shell integration
+- Repository import timeout increased from 2 to 4 minutes allowing larger repos to be imported
+- API for labels (Robert Schilling)
+- API: ability to set an import url when creating project for specific user
+
+## 7.1.1
+
+- Fix cpu usage issue in Firefox
+- Fix redirect loop when changing password by new user
+- Fix 500 error on new merge request page
+
+## 7.1.0
+
+- Remove observers
+- Improve MR discussions
+- Filter by description on Issues#index page
+- Fix bug with namespace select when create new project page
+- Show README link after description for non-master members
+- Add @all mention for comments
+- Dont show reply button if user is not signed in
+- Expose more information for issues with webhook
+- Add a mention of the merge request into the default merge request commit message
+- Improve code highlight, introduce support for more languages like Go, Clojure, Erlang etc
+- Fix concurrency issue in repository download
+- Dont allow repository name start with ?
+- Improve email threading (Pierre de La Morinerie)
+- Cleaner help page
+- Group milestones
+- Improved email notifications
+- Contributors API (sponsored by Mobbr)
+- Fix LDAP TLS authentication (Boris HUISGEN)
+- Show VERSION information on project sidebar
+- Improve branch removal logic when accept MR
+- Fix bug where comment form is spawned inside the Reply button
+- Remove Dir.chdir from Satellite#lock for thread-safety
+- Increased default git max_size value from 5MB to 20MB in gitlab.yml. Please update your configs!
+- Show error message in case of timeout in satellite when create MR
+- Show first 100 files for huge diff instead of hiding all
+- Change default admin email from admin@local.host to admin@example.com
+
+## 7.0.0
+
+- The CPU no longer overheats when you hold down the spacebar
+- Improve edit file UI
+- Add ability to upload group avatar when create
+- Protected branch cannot be removed
+- Developers can remove normal branches with UI
+- Remove branch via API (sponsored by O'Reilly Media)
+- Move protected branches page to Project settings area
+- Redirect to Files view when create new branch via UI
+- Drag and drop upload of image in every markdown-area (Earle Randolph Bunao and Neil Francis Calabroso)
+- Refactor the markdown relative links processing
+- Make it easier to implement other CI services for GitLab
+- Group masters can create projects in group
+- Deprecate ruby 1.9.3 support
+- Only masters can rewrite/remove git tags
+- Add X-Frame-Options SAMEORIGIN to Nginx config so Sidekiq admin is visible
+- UI improvements
+- Case-insensetive search for issues
+- Update to rails 4.1
+- Improve performance of application for projects and groups with a lot of members
+- Formally support Ruby 2.1
+- Include Nginx gitlab-ssl config
+- Add manual language detection for highlight.js
+- Added example.com/:username routing
+- Show notice if your profile is public
+- UI improvements for mobile devices
+- Improve diff rendering performance
+- Drag-n-drop for issues and merge requests between states at milestone page
+- Fix '0 commits' message for huge repositories on project home page
+- Prevent 500 error page when visit commit page from large repo
+- Add notice about huge push over http to unicorn config
+- File action in satellites uses default 30 seconds timeout instead of old 10 seconds one
+- Overall performance improvements
+- Skip init script check on omnibus-gitlab
+- Be more selective when killing stray Sidekiqs
+- Check LDAP user filter during sign-in
+- Remove wall feature (no data loss - you can take it from database)
+- Dont expose user emails via API unless you are admin
+- Detect issues closed by Merge Request description
+- Better email subject lines from email on push service (Alex Elman)
+- Enable identicon for gravatar be default
+
+## 6.9.2
+
+- Revert the commit that broke the LDAP user filter
+
+## 6.9.1
+
+- Fix scroll to highlighted line
+- Fix the pagination on load for commits page
+
+## 6.9.0
+
+- Store Rails cache data in the Redis `cache:gitlab` namespace
+- Adjust MySQL limits for existing installations
+- Add db index on project_id+iid column. This prevents duplicate on iid (During migration duplicates will be removed)
+- Markdown preview or diff during editing via web editor (Evgeniy Sokovikov)
+- Give the Rails cache its own Redis namespace
+- Add ability to set different ssh host, if different from http/https
+- Fix syntax highlighting for code comments blocks
+- Improve comments loading logic
+- Stop refreshing comments when the tab is hidden
+- Improve issue and merge request mobile UI (Drew Blessing)
+- Document how to convert a backup to PostgreSQL
+- Fix locale bug in backup manager
+- Fix can not automerge when MR description is too long
+- Fix wiki backup skip bug
+- Two Step MR creation process
+- Remove unwanted files from satellite working directory with git clean -fdx
+- Accept merge request via API (sponsored by O'Reilly Media)
+- Add more access checks during API calls
+- Block SSH access for 'disabled' Active Directory users
+- Labels for merge requests (Drew Blessing)
+- Threaded emails by setting a Message-ID (Philip Blatter)
+
+## 6.8.0
+
+- Ability to at mention users that are participating in issue and merge req. discussion
+- Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu)
+- Make user search case-insensitive (Christopher Arnold)
+- Remove omniauth-ldap nickname bug workaround
+- Drop all tables before restoring a Postgres backup
+- Make the repository downloads path configurable
+- Create branches via API (sponsored by O'Reilly Media)
+- Changed permission of gitlab-satellites directory not to be world accessible
+- Protected branch does not allow force push
+- Fix popen bug in `rake gitlab:satellites:create`
+- Disable connection reaping for MySQL
+- Allow oauth signup without email for twitter and github
+- Fix faulty namespace names that caused 500 on user creation
+- Option to disable standard login
+- Clean old created archives from repository downloads directory
+- Fix download link for huge MR diffs
+- Expose event and mergerequest timestamps in API
+- Fix emails on push service when only one commit is pushed
+
+## 6.7.3
+
+- Fix the merge notification email not being sent (Pierre de La Morinerie)
+- Drop all tables before restoring a Postgres backup
+- Remove yanked modernizr gem
+
+## 6.7.2
+
+- Fix upgrader script
+
+## 6.7.1
+
+- Fix GitLab CI integration
+
+## 6.7.0
+
+- Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations
+- Add support for Gemnasium as a Project Service (Olivier Gonzalez)
+- Add edit file button to MergeRequest diff
+- Public groups (Jason Hollingsworth)
+- Cleaner headers in Notification Emails (Pierre de La Morinerie)
+- Blob and tree gfm links to anchors work
+- Piwik Integration (Sebastian Winkler)
+- Show contribution guide link for new issue form (Jeroen van Baarsen)
+- Fix CI status for merge requests from fork
+- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
+- New page load indicator that includes a spinner that scrolls with the page
+- Converted all the help sections into markdown
+- LDAP user filters
+- Streamline the content of notification emails (Pierre de La Morinerie)
+- Fixes a bug with group member administration (Matt DeTullio)
+- Sort tag names using VersionSorter (Robert Speicher)
+- Add GFM autocompletion for MergeRequests (Robert Speicher)
+- Add webhook when a new tag is pushed (Jeroen van Baarsen)
+- Add button for toggling inline comments in diff view
+- Add retry feature for repository import
+- Reuse the GitLab LDAP connection within each request
+- Changed markdown new line behaviour to conform to markdown standards
+- Fix global search
+- Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5)
+- Create and Update MR calls now support the description parameter (Greg Messner)
+- Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository
+- Added Slack service integration (Federico Ravasio)
+- Better API responses for access_levels (sponsored by O'Reilly Media)
+- Requires at least 2 unicorn workers
+- Requires gitlab-shell v1.9+
+- Replaced gemoji(due to closed licencing problem) with Phantom Open Emoji library(combined SIL Open Font License, MIT License and the CC 3.0 License)
+- Fix `/:username.keys` response content type (Dmitry Medvinsky)
+
+## 6.6.5
+
+- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
+- Hide mr close button for comment form if merge request was closed or inline comment
+- Adds ability to reopen closed merge request
+
+## 6.6.4
+
+- Add missing html escape for highlighted code blocks in comments, issues
+
+## 6.6.3
+
+- Fix 500 error when edit yourself from admin area
+- Hide private groups for public profiles
+
+## 6.6.2
+
+- Fix 500 error on branch/tag create or remove via UI
+
+## 6.6.1
+
+- Fix 500 error on files tab if submodules presents
+
+## 6.6.0
+
+- Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys
+- Permissions: Developer now can manage issue tracker (modify any issue)
+- Improve Code Compare page performance
+- Group avatar
+- Pygments.rb replaced with highlight.js
+- Improve Merge request diff store logic
+- Improve render performnace for MR show page
+- Fixed Assembla hardcoded project name
+- Jira integration documentation
+- Refactored app/services
+- Remove snippet expiration
+- Mobile UI improvements (Drew Blessing)
+- Fix block/remove UI for admin::users#show page
+- Show users' group membership on users' activity page (Robert Djurasaj)
+- User pages are visible without login if user is authorized to a public project
+- Markdown rendered headers have id derived from their name and link to their id
+- Improve application to work faster with large groups (100+ members)
+- Multiple emails per user
+- Show last commit for file when view file source
+- Restyle Issue#show page and MR#show page
+- Ability to filter by multiple labels for Issues page
+- Rails version to 4.0.3
+- Fixed attachment identifier displaying underneath note text (Jason Blanchard)
+
+## 6.5.1
+
+- Fix branch selectbox when create merge request from fork
+
+## 6.5.0
+
+- Dropdown menus on issue#show page for assignee and milestone (Jason Blanchard)
+- Add color custimization and previewing to broadcast messages
+- Fixed notes anchors
+- Load new comments in issues dynamically
+- Added sort options to Public page
+- New filters (assigned/authored/all) for Dashboard#issues/merge_requests (sponsored by Say Media)
+- Add project visibility icons to dashboard
+- Enable secure cookies if https used
+- Protect users/confirmation with rack_attack
+- Default HTTP headers to protect against MIME-sniffing, force https if enabled
+- Bootstrap 3 with responsive UI
+- New repository download formats: tar.bz2, zip, tar (Jason Hollingsworth)
+- Restyled accept widgets for MR
+- SCSS refactored
+- Use jquery timeago plugin
+- Fix 500 error for rdoc files
+- Ability to customize merge commit message (sponsored by Say Media)
+- Search autocomplete via ajax
+- Add website url to user profile
+- Files API supports base64 encoded content (sponsored by O'Reilly Media)
+- Added support for Go's repository retrieval (Bruno Albuquerque)
+
+## 6.4.3
+
+- Don't use unicorn worker killer if PhusionPassenger is defined
+
+## 6.4.2
+
+- Fixed wrong behaviour of script/upgrade.rb
+
+## 6.4.1
+
+- Fixed bug with repository rename
+- Fixed bug with project transfer
+
+## 6.4.0
+
+- Added sorting to project issues page (Jason Blanchard)
+- Assembla integration (Carlos Paramio)
+- Fixed another 500 error with submodules
+- UI: More compact issues page
+- Minimal password length increased to 8 symbols
+- Side-by-side diff view (Steven Thonus)
+- Internal projects (Jason Hollingsworth)
+- Allow removal of avatar (Drew Blessing)
+- Project webhooks now support issues and merge request events
+- Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth)
+- Expire event cache on avatar creation/removal (Drew Blessing)
+- Archiving old projects (Steven Thonus)
+- Rails 4
+- Add time ago tooltips to show actual date/time
+- UI: Fixed UI for admin system hooks
+- Ruby script for easier GitLab upgrade
+- Do not remove Merge requests if fork project was removed
+- Improve sign-in/signup UX
+- Add resend confirmation link to sign-in page
+- Set noreply@HOSTNAME for reply_to field in all emails
+- Show GitLab API version on Admin#dashboard
+- API Cross-origin resource sharing
+- Show READMe link at project home page
+- Show repo size for projects in Admin area
+
+## 6.3.0
+
+- API for adding gitlab-ci service
+- Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey)
+- Restyle project home page
+- Grammar fixes
+- Show branches list (which branches contains commit) on commit page (Andrew Kumanyaev)
+- Security improvements
+- Added support for GitLab CI 4.0
+- Fixed issue with 500 error when group did not exist
+- Ability to leave project
+- You can create file in repo using UI
+- You can remove file from repo using UI
+- API: dropped default_branch attribute from project during creation
+- Project default_branch is not stored in db any more. It takes from repo now.
+- Admin broadcast messages
+- UI improvements
+- Dont show last push widget if user removed this branch
+- Fix 500 error for repos with newline in file name
+- Extended html titles
+- API: create/update/delete repo files
+- Admin can transfer project to any namespace
+- API: projects/all for admin users
+- Fix recent branches order
+
+## 6.2.4
+
+- Security: Cast API private_token to string (CVE-2013-4580)
+- Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
+- Fix for Git SSH access for LDAP users
+
+## 6.2.3
+
+- Security: More protection against CVE-2013-4489
+- Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
+- Fix sidekiq rake tasks
+
+## 6.2.2
+
+- Security: Update gitlab_git (CVE-2013-4489)
+
+## 6.2.1
+
+- Security: Fix issue with generated passwords for new users
+
+## 6.2.0
+
+- Public project pages are now visible to everyone (files, issues, wik, etc.)
+  THIS MEANS YOUR ISSUES AND WIKI FOR PUBLIC PROJECTS ARE PUBLICLY VISIBLE AFTER THE UPGRADE
+- Add group access to permissions page
+- Require current password to change one
+- Group owner or admin can remove other group owners
+- Remove group transfer since we have multiple owners
+- Respect authorization in Repository API
+- Improve UI for Project#files page
+- Add more security specs
+- Added search for projects by name to api (Izaak Alpert)
+- Make default user theme configurable (Izaak Alpert)
+- Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev)
+- Rake tasks for webhooks management (Jonhnny Weslley)
+- Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov)
+- API: Remove group
+- API: Remove project
+- Avatar upload on profile page with a maximum of 100KB (Steven Thonus)
+- Store the sessions in Redis instead of the cookie store
+- Fixed relative links in markdown
+- User must confirm their email if signup enabled
+- User must confirm changed email
+
+## 6.1.0
+
+- Project specific IDs for issues, mr, milestones
+  Above items will get a new id and for example all bookmarked issue urls will change.
+  Old issue urls are redirected to the new one if the issue id is too high for an internal id.
+- Description field added to Merge Request
+- API: Sudo api calls (Izaak Alpert)
+- API: Group membership api (Izaak Alpert)
+- Improved commit diff
+- Improved large commit handling (Boyan Tabakov)
+- Rewrite: Init script now less prone to errors and keeps better track of the service (Rovanion Luckey)
+- Link issues, merge requests, and commits when they reference each other with GFM (Ash Wilson)
+- Close issues automatically when pushing commits with a special message
+- Improve user removal from admin area
+- Invalidate events cache when project was moved
+- Remove deprecated classes and rake tasks
+- Add event filter for group and project show pages
+- Add links to create branch/tag from project home page
+- Add public-project? checkbox to new-project view
+- Improved compare page. Added link to proceed into Merge Request
+- Send an email to a user when they are added to group
+- New landing page when you have 0 projects
+
+## 6.0.0
+
+- Feature: Replace teams with group membership
+  We introduce group membership in 6.0 as a replacement for teams.
+  The old combination of groups and teams was confusing for a lot of people.
+  And when the members of a team where changed this wasn't reflected in the project permissions.
+  In GitLab 6.0 you will be able to add members to a group with a permission level for each member.
+  These group members will have access to the projects in that group.
+  Any changes to group members will immediately be reflected in the project permissions.
+  You can even have multiple owners for a group, greatly simplifying administration.
+- Feature: Ability to have multiple owners for group
+- Feature: Merge Requests between fork and project (Izaak Alpert)
+- Feature: Generate fingerprint for ssh keys
+- Feature: Ability to create and remove branches with UI
+- Feature: Ability to create and remove git tags with UI
+- Feature: Groups page in profile. You can leave group there
+- API: Allow login with LDAP credentials
+- Redesign: project settings navigation
+- Redesign: snippets area
+- Redesign: ssh keys page
+- Redesign: buttons, blocks and other ui elements
+- Add comment title to rss feed
+- You can use arrows to navigate at tree view
+- Add project filter on dashboard
+- Cache project graph
+- Drop support of root namespaces
+- Default theme is classic now
+- Cache result of methods like authorize_projects, project.team.members etc
+- Remove $.ready events
+- Fix onclick events being double binded
+- Add notification level to group membership
+- Move all project controllers/views under Projects:: module
+- Move all profile controllers/views under Profiles:: module
+- Apply user project limit only for personal projects
+- Unicorn is default web server again
+- Store satellites lock files inside satellites dir
+- Disabled threadsafety mode in rails
+- Fixed bug with loosing MR comments
+- Improved MR comments logic
+- Render readme file for projects in public area
+
+## 5.4.2
+
+- Security: Cast API private_token to string (CVE-2013-4580)
+- Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
+
+## 5.4.1
+
+- Security: Fixes for CVE-2013-4489
+- Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
+
+## 5.4.0
+
+- Ability to edit own comments
+- Documentation improvements
+- Improve dashboard projects page
+- Fixed nav for empty repos
+- GitLab Markdown help page
+- Misspelling fixes
+- Added support of unicorn and fog gems
+- Added client list to API doc
+- Fix PostgreSQL database restoration problem
+- Increase snippet content column size
+- allow project import via git:// url
+- Show participants on issues, including mentions
+- Notify mentioned users with email
+
+## 5.3.0
+
+- Refactored services
+- Campfire service added
+- HipChat service added
+- Fixed bug with LDAP + git over http
+- Fixed bug with google analytics code being ignored
+- Improve sign-in page if ldap enabled
+- Respect newlines in wall messages
+- Generate the Rails secret token on first run
+- Rename repo feature
+- Init.d: remove gitlab.socket on service start
+- Api: added teams api
+- Api: Prevent blob content being escaped
+- Api: Smart deploy key add behaviour
+- Api: projects/owned.json return user owned project
+- Fix bug with team assignation on project from #4109
+- Advanced snippets: public/private, project/personal (Andrew Kulakov)
+- Repository Graphs (Karlo Nicholas T. Soriano)
+- Fix dashboard lost if comment on commit
+- Update gitlab-grack. Fixes issue with --depth option
+- Fix project events duplicate on project page
+- Fix postgres error when displaying network graph.
+- Fix dashboard event filter when navigate via turbolinks
+- init.d: Ensure socket is removed before starting service
+- Admin area: Style teams:index, group:show pages
+- Own page for failed forking
+- Scrum view for milestone
+
+## 5.2.0
+
+- Turbolinks
+- Git over http with ldap credentials
+- Diff with better colors and some spacing on the corners
+- Default values for project features
+- Fixed huge_commit view
+- Restyle project clone panel
+- Move Gitlab::Git code to gitlab_git gem
+- Move update docs in repo
+- Requires gitlab-shell v1.4.0
+- Fixed submodules listing under file tab
+- Fork feature (Angus MacArthur)
+- git version check in gitlab:check
+- Shared deploy keys feature
+- Ability to generate default labels set for issues
+- Improve gfm autocomplete (Harold Luo)
+- Added support for Google Analytics
+- Code search feature (Javier Castro)
+
+## 5.1.0
+
+- You can login with email or username now
+- Corrected project transfer rollback when repository cannot be moved
+- Move both repo and wiki when project transfer requested
+- Admin area: project editing was removed from admin namespace
+- Access: admin user has now access to any project.
+- Notification settings
+- Gitlab::Git set of objects to abstract from grit library
+- Replace Unicorn web server with Puma
+- Backup/Restore refactored. Backup dump project wiki too now
+- Restyled Issues list. Show milestone version in issue row
+- Restyled Merge Request list
+- Backup now dump/restore uploads
+- Improved performance of dashboard (Andrew Kumanyaev)
+- File history now tracks renames (Akzhan Abdulin)
+- Drop wiki migration tools
+- Drop sqlite migration tools
+- project tagging
+- Paginate users in API
+- Restyled network graph (Hiroyuki Sato)
+
+## 5.0.1
+
+- Fixed issue with gitlab-grit being overridden by grit
+
+## 5.0.0
+
+- Replaced gitolite with gitlab-shell
+- Removed gitolite-related libraries
+- State machine added
+- Setup gitlab as git user
+- Internal API
+- Show team tab for empty projects
+- Import repository feature
+- Updated rails
+- Use lambda for scopes
+- Redesign admin area -> users
+- Redesign admin area -> user
+- Secure link to file attachments
+- Add validations for Group and Team names
+- Restyle team page for project
+- Update capybara, rspec-rails, poltergeist to recent versions
+- Wiki on git using Gollum
+- Added Solarized Dark theme for code review
+- Don't show user emails in autocomplete lists, profile pages
+- Added settings tab for group, team, project
+- Replace user popup with icons in header
+- Handle project moving with gitlab-shell
+- Added select2-rails for selectboxes with ajax data load
+- Fixed search field on projects page
+- Added teams to search autocomplete
+- Move groups and teams on dashboard sidebar to sub-tabs
+- API: improved return codes and docs. (Felix Gilcher, Sebastian Ziebell)
+- Redesign wall to be more like chat
+- Snippets, Wall features are disabled by default for new projects
+
+## 4.2.0
+
+- Teams
+- User show page. Via /u/username
+- Show help contents on pages for better navigation
+- Async gitolite calls
+- added satellites logs
+- can_create_group, can_create_team booleans for User
+- Process webhooks async
+- GFM: Fix images escaped inside links
+- Network graph improved
+- Switchable branches for network graph
+- API: Groups
+- Fixed project download
+
+## 4.1.0
+
+- Optional Sign-Up
+- Discussions
+- Satellites outside of tmp
+- Line numbers for blame
+- Project public mode
+- Public area with unauthorized access
+- Load dashboard events with ajax
+- remember dashboard filter in cookies
+- replace resque with sidekiq
+- fix routing issues
+- cleanup rake tasks
+- fix backup/restore
+- scss cleanup
+- show preview for note images
+- improved network-graph
+- get rid of app/roles/
+- added new classes Team, Repository
+- Reduce amount of gitolite calls
+- Ability to add user in all group projects
+- remove deprecated configs
+- replaced Korolev font with open font
+- restyled admin/dashboard page
+- restyled admin/projects page
+
+## 4.0.0
+
+- Remove project code and path from API. Use id instead
+- Return valid cloneable url to repo for webhook
+- Fixed backup issue
+- Reorganized settings
+- Fixed commits compare
+- Refactored scss
+- Improve status checks
+- Validates presence of User#name
+- Fixed postgres support
+- Removed sqlite support
+- Modified post-receive hook
+- Milestones can be closed now
+- Show comment events on dashboard
+- Quick add team members via group#people page
+- [API] expose created date for hooks and SSH keys
+- [API] list, create issue notes
+- [API] list, create snippet notes
+- [API] list, create wall notes
+- Remove project code - use path instead
+- added username field to user
+- rake task to fill usernames based on emails create namespaces for users
+- STI Group < Namespace
+- Project has namespace_id
+- Projects with namespaces also namespaced in gitolite and stored in subdir
+- Moving project to group will move it under group namespace
+- Ability to move project from namespaces to another
+- Fixes commit patches getting escaped (see #2036)
+- Support diff and patch generation for commits and merge request
+- MergeReqest doesn't generate a temporary file for the patch any more
+- Update the UI to allow downloading Patch or Diff
+
+## 3.1.0
+
+- Updated gems
+- Services: Gitlab CI integration
+- Events filter on dashboard
+- Own namespace for redis/resque
+- Optimized commit diff views
+- add alphabetical order for projects admin page
+- Improved web editor
+- Commit stats page
+- Documentation split and cleanup
+- Link to commit authors everywhere
+- Restyled milestones list
+- added Milestone to Merge Request
+- Restyled Top panel
+- Refactored Satellite Code
+- Added file line links
+- moved from capybara-webkit to poltergeist + phantomjs
+
+## 3.0.3
+
+- Fixed bug with issues list in Chrome
+- New Feature: Import team from another project
+
+## 3.0.2
+
+- Fixed gitlab:app:setup
+- Fixed application error on empty project in admin area
+- Restyled last push widget
+
+## 3.0.1
+
+- Fixed git over http
+
+## 3.0.0
+
+- Projects groups
+- Web Editor
+- Fixed bug with gitolite keys
+- UI improved
+- Increased performance of application
+- Show user avatar in last commit when browsing Files
+- Refactored Gitlab::Merge
+- Use Font Awesome for icons
+- Separate observing of Note and MergeRequests
+- Milestone "All Issues" filter
+- Fix issue close and reopen button text and styles
+- Fix forward/back while browsing Tree hierarchy
+- Show number of notes for commits and merge requests
+- Added support pg from box and update installation doc
+- Reject ssh keys that break gitolite
+- [API] list one project hook
+- [API] edit project hook
+- [API] list project snippets
+- [API] allow to authorize using private token in HTTP header
+- [API] add user creation
+
+## 2.9.1
+
+- Fixed resque custom config init
+
+## 2.9.0
+
+- fixed inline notes bugs
+- refactored rspecs
+- refactored gitolite backend
+- added factory_girl
+- restyled projects list on dashboard
+- ssh keys validation to prevent gitolite crash
+- send notifications if changed permission in project
+- scss refactoring. gitlab_bootstrap/ dir
+- fix git push http body bigger than 112k problem
+- list of labels  page under issues tab
+- API for milestones, keys
+- restyled buttons
+- OAuth
+- Comment order changed
+
+## 2.8.1
+
+- ability to disable gravatars
+- improved MR diff logic
+- ssh key help page
+
+## 2.8.0
+
+- Gitlab Flavored Markdown
+- Bulk issues update
+- Issues API
+- Cucumber coverage increased
+- Post-receive files fixed
+- UI improved
+- Application cleanup
+- more cucumber
+- capybara-webkit + headless
+
+## 2.7.0
+
+- Issue Labels
+- Inline diff
+- Git HTTP
+- API
+- UI improved
+- System hooks
+- UI improved
+- Dashboard events endless scroll
+- Source performance increased
+
+## 2.6.0
+
+- UI polished
+- Improved network graph + keyboard nav
+- Handle huge commits
+- Last Push widget
+- Bugfix
+- Better performance
+- Email in resque
+- Increased test coverage
+- Ability to remove branch with MR accept
+- a lot of code refactored
+
+## 2.5.0
+
+- UI polished
+- Git blame for file
+- Bugfix
+- Email in resque
+- Better test coverage
+
+## 2.4.0
+
+- Admin area stats page
+- Ability to block user
+- Simplified dashboard area
+- Improved admin area
+- Bootstrap 2.0
+- Responsive layout
+- Big commits handling
+- Performance improved
+- Milestones
+
+## 2.3.1
+
+- Issues pagination
+- ssl fixes
+- Merge Request pagination
+
+## 2.3.0
+
+- Dashboard r1
+- Search r1
+- Project page
+- Close merge request on push
+- Persist MR diff after merge
+- mysql support
+- Documentation
+
+## 2.2.0
+
+- We’ve added support of LDAP auth
+- Improved permission logic (4 roles system)
+- Protected branches (now only masters can push to protected branches)
+- Usability improved
+- twitter bootstrap integrated
+- compare view between commits
+- wiki feature
+- now you can enable/disable issues, wiki, wall features per project
+- security fixes
+- improved code browsing (ajax branch switch etc)
+- improved per-line commenting
+- git submodules displayed
+- moved to rails 3.2
+- help section improved
+
+## 2.1.0
+
+- Project tab r1
+- List branches/tags
+- per line comments
+- mass user import
+
+## 2.0.0
+
+- gitolite as main git host system
+- merge requests
+- project/repo access
+- link to commit/issue feed
+- design tab
+- improved email notifications
+- restyled dashboard
+- bugfix
+
+## 1.2.2
+
+- common config file gitlab.yml
+- issues restyle
+- snippets restyle
+- clickable news feed header on dashboard
+- bugfix
+
+## 1.2.1
+
+- bugfix
+
+## 1.2.0
+
+- new design
+- user dashboard
+- network graph
+- markdown support for comments
+- encoding issues
+- wall like twitter timeline
+
+## 1.1.0
+
+- project dashboard
+- wall redesigned
+- feature: code snippets
+- fixed horizontal scroll on file preview
+- fixed app crash if commit message has invalid chars
+- bugfix & code cleaning
+
+## 1.0.2
+
+- fixed bug with empty project
+- added adv validation for project path & code
+- feature: issues can be sortable
+- bugfix
+- username displayed on top panel
+
+## 1.0.1
+
+- fixed: with invalid source code for commit
+- fixed: lose branch/tag selection when use tree navigation
+- when history clicked - display path
+- bug fix & code cleaning
+
+## 1.0.0
+
+- bug fix
+- projects preview mode
+
+## 0.9.6
+
+- css fix
+- new repo empty tree until restart server - fixed
+
+## 0.9.4
+
+- security improved
+- authorization improved
+- html escaping
+- bug fix
+- increased test coverage
+- design improvements
+
+## 0.9.1
+
+- increased test coverage
+- design improvements
+- new issue email notification
+- updated app name
+- issue redesigned
+- issue can be edit
+
+## 0.8.0
+
+- syntax highlight for main file types
+- redesign
+- stability
+- security fixes
+- increased test coverage
+- email notification
diff --git a/changelogs/unreleased/.gitkeep b/changelogs/unreleased/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 4a01b9e40fb3f4ca3b0fd39dc2a1bb9ca8c585d5..195108b921b749f8a85bc733450f0992153ce0b6 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -299,6 +299,9 @@ Settings.cron_jobs['remove_expired_members_worker']['job_class'] = 'RemoveExpire
 Settings.cron_jobs['remove_expired_group_links_worker'] ||= Settingslogic.new({})
 Settings.cron_jobs['remove_expired_group_links_worker']['cron'] ||= '10 0 * * *'
 Settings.cron_jobs['remove_expired_group_links_worker']['job_class'] = 'RemoveExpiredGroupLinksWorker'
+Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '* */6 * * *'
+Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker'
 
 #
 # GitLab Shell
diff --git a/config/initializers/gitlab_workhorse_secret.rb b/config/initializers/gitlab_workhorse_secret.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ed54dc11098724e7d7e84dcf0a0e638e6b9dab30
--- /dev/null
+++ b/config/initializers/gitlab_workhorse_secret.rb
@@ -0,0 +1,8 @@
+begin
+  Gitlab::Workhorse.secret
+rescue
+  Gitlab::Workhorse.write_secret
+end
+
+# Try a second time. If it does not work this will raise.
+Gitlab::Workhorse.secret
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 52522e099e7de06c3bd9a3588f43332f8fdadd08..be22085b0df75c54e43b09ebea328a7cce4c85af 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -68,7 +68,8 @@ if Gitlab::Metrics.enabled?
       ['app', 'mailers', 'emails']          => ['app', 'mailers'],
       ['app', 'services', '**']             => ['app', 'services'],
       ['lib', 'gitlab', 'diff']             => ['lib'],
-      ['lib', 'gitlab', 'email', 'message'] => ['lib']
+      ['lib', 'gitlab', 'email', 'message'] => ['lib'],
+      ['lib', 'gitlab', 'checks']           => ['lib']
     }
 
     paths_to_instrument.each do |(path, prefix)|
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index f498732feca21222c6c0dc921b3482463a35a7f0..5e3e4c966cbbf5013b763c34c05f6b85e63db6c8 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -13,9 +13,5 @@ Mime::Type.register "video/mp4",  :mp4, [], [:m4v, :mov]
 Mime::Type.register "video/webm", :webm
 Mime::Type.register "video/ogg",  :ogv
 
-middlewares = Gitlab::Application.config.middleware
-middlewares.swap(ActionDispatch::ParamsParser, ActionDispatch::ParamsParser, {
-  Mime::Type.lookup('application/vnd.git-lfs+json') => lambda do |body|
-    ActiveSupport::JSON.decode(body)
-  end
-})
+Mime::Type.unregister :json
+Mime::Type.register 'application/json', :json, %w(application/vnd.git-lfs+json application/json)
diff --git a/config/routes.rb b/config/routes.rb
index ac056148ab445b15a2b69c7bbbe9ec316cb197d5..33b070188dc5e9cbe12d1ffc59edc71cc466d1fe 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -747,6 +747,7 @@ Rails.application.routes.draw do
             get :branch_to
             get :update_branches
             get :diff_for_path
+            post :bulk_update
           end
 
           resources :discussions, only: [], constraints: { id: /\h{40}/ } do
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index 49e6e2361b1d7a8e3a4ccddf9a4c4f5935c35a9e..650b410595cd3b60313e1bda5dbd920db929c92c 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines
   BUILDS = [
     { name: 'build:linux', stage: 'build', status: :success },
     { name: 'build:osx', stage: 'build', status: :success },
-    { name: 'rspec:linux', stage: 'test', status: :success },
-    { name: 'rspec:windows', stage: 'test', status: :success },
-    { name: 'rspec:windows', stage: 'test', status: :success },
+    { name: 'rspec:linux 0 3', stage: 'test', status: :success },
+    { name: 'rspec:linux 1 3', stage: 'test', status: :success },
+    { name: 'rspec:linux 2 3', stage: 'test', status: :success },
+    { name: 'rspec:windows 0 3', stage: 'test', status: :success },
+    { name: 'rspec:windows 1 3', stage: 'test', status: :success },
+    { name: 'rspec:windows 2 3', stage: 'test', status: :success },
+    { name: 'rspec:windows 2 3', stage: 'test', status: :success },
     { name: 'rspec:osx', stage: 'test', status_event: :success },
     { name: 'spinach:linux', stage: 'test', status: :success },
     { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true},
diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb
index 84463727b3b8a7cdf925594384c87361a488153c..e8de7ccf3db39dce3575d533a7fc1cf600510ae4 100644
--- a/db/migrate/20140502125220_migrate_repo_size.rb
+++ b/db/migrate/20140502125220_migrate_repo_size.rb
@@ -1,12 +1,15 @@
 # rubocop:disable all
 class MigrateRepoSize < ActiveRecord::Migration
+  DOWNTIME = false
+
   def up
     project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id')
 
     project_data.each do |project|
       id = project['id']
       namespace_path = project['namespace_path'] || ''
-      path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git')
+      repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default
+      path = File.join(repos_path, namespace_path, project['project_path'] + '.git')
 
       begin
         repo = Gitlab::Git::Repository.new(path)
diff --git a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb
index c8cbd2718ff0825038d3d78a6c426427049e5909..75a3eb15124cf68a235b4c34535b9857d1e3e814 100644
--- a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb
+++ b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb
@@ -8,14 +8,28 @@ class MergeRequestDiffRemoveUniq < ActiveRecord::Migration
   DOWNTIME = false
 
   def up
-    if index_exists?(:merge_request_diffs, :merge_request_id)
-      remove_index :merge_request_diffs, :merge_request_id
+    constraint_name = 'merge_request_diffs_merge_request_id_key'
+
+    transaction do
+      if index_exists?(:merge_request_diffs, :merge_request_id)
+        remove_index(:merge_request_diffs, :merge_request_id)
+      end
+
+      # In some bizarre cases PostgreSQL might have a separate unique constraint
+      # that we'll need to drop.
+      if constraint_exists?(constraint_name) && Gitlab::Database.postgresql?
+        execute("ALTER TABLE merge_request_diffs DROP CONSTRAINT IF EXISTS #{constraint_name};")
+      end
     end
   end
 
   def down
     unless index_exists?(:merge_request_diffs, :merge_request_id)
-      add_concurrent_index :merge_request_diffs, :merge_request_id, unique: true
+      add_concurrent_index(:merge_request_diffs, :merge_request_id, unique: true)
     end
   end
+
+  def constraint_exists?(name)
+    indexes(:merge_request_diffs).map(&:name).include?(name)
+  end
 end
diff --git a/db/migrate/20160808085531_add_token_to_build.rb b/db/migrate/20160808085531_add_token_to_build.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3ed2a103ae3ca31d8ee9df38e215198e1bc5a28f
--- /dev/null
+++ b/db/migrate/20160808085531_add_token_to_build.rb
@@ -0,0 +1,10 @@
+class AddTokenToBuild < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  def change
+    add_column :ci_builds, :token, :string
+  end
+end
diff --git a/db/migrate/20160808085602_add_index_for_build_token.rb b/db/migrate/20160808085602_add_index_for_build_token.rb
new file mode 100644
index 0000000000000000000000000000000000000000..10ef42afce18c316cb54296039c8b792dae65c62
--- /dev/null
+++ b/db/migrate/20160808085602_add_index_for_build_token.rb
@@ -0,0 +1,12 @@
+class AddIndexForBuildToken < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def change
+    add_concurrent_index :ci_builds, :token, unique: true
+  end
+end
diff --git a/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd413d1ca8cf20147d319f4ca1a45814b4d97bd5
--- /dev/null
+++ b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb
@@ -0,0 +1,12 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLfsEnabledToNamespaces < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column :namespaces, :lfs_enabled, :boolean
+  end
+end
diff --git a/db/migrate/20160907131111_add_environment_type_to_environments.rb b/db/migrate/20160907131111_add_environment_type_to_environments.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fac73753d5b44fba877c479b121b8ed1889adaf0
--- /dev/null
+++ b/db/migrate/20160907131111_add_environment_type_to_environments.rb
@@ -0,0 +1,9 @@
+class AddEnvironmentTypeToEnvironments < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column :environments, :environment_type, :string
+  end
+end
diff --git a/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb
new file mode 100644
index 0000000000000000000000000000000000000000..18ea9d43a43d0d0fe6f8f2612f88daaec9eb1841
--- /dev/null
+++ b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveProjectsPushesSinceGc < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = true
+  DOWNTIME_REASON = 'This migration removes an existing column'
+
+  disable_ddl_transaction!
+
+  def up
+    remove_column :projects, :pushes_since_gc
+  end
+
+  def down
+    add_column_with_default :projects, :pushes_since_gc, :integer, default: 0
+  end
+end
diff --git a/db/migrate/20160913212128_change_artifacts_size_column.rb b/db/migrate/20160913212128_change_artifacts_size_column.rb
new file mode 100644
index 0000000000000000000000000000000000000000..063bbca537c04d70573e87f36d9a7cdfcb9146a6
--- /dev/null
+++ b/db/migrate/20160913212128_change_artifacts_size_column.rb
@@ -0,0 +1,15 @@
+class ChangeArtifactsSizeColumn < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = true
+
+  DOWNTIME_REASON = 'Changing an integer column size requires a full table rewrite.'
+
+  def up
+    change_column :ci_builds, :artifacts_size, :integer, limit: 8
+  end
+
+  def down
+    # do nothing
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 26d49e7b261d1b4d213ae6e1f2b688e2b575167b..adb7d2dc91e4fd97d1852baaae06c21d21ab3e1e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -177,10 +177,11 @@ ActiveRecord::Schema.define(version: 20160915081353) do
     t.datetime "erased_at"
     t.datetime "artifacts_expire_at"
     t.string   "environment"
-    t.integer  "artifacts_size"
+    t.integer  "artifacts_size",      limit: 8
     t.string   "when"
     t.text     "yaml_variables"
     t.datetime "queued_at"
+    t.string   "token"
   end
 
   add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
@@ -192,6 +193,7 @@ ActiveRecord::Schema.define(version: 20160915081353) do
   add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
   add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
   add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
+  add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree
 
   create_table "ci_commits", force: :cascade do |t|
     t.integer  "project_id"
@@ -390,10 +392,11 @@ ActiveRecord::Schema.define(version: 20160915081353) do
 
   create_table "environments", force: :cascade do |t|
     t.integer  "project_id"
-    t.string   "name",         null: false
+    t.string   "name",             null: false
     t.datetime "created_at"
     t.datetime "updated_at"
     t.string   "external_url"
+    t.string   "environment_type"
   end
 
   add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree
@@ -683,6 +686,7 @@ ActiveRecord::Schema.define(version: 20160915081353) do
     t.integer  "visibility_level",       default: 20,    null: false
     t.boolean  "request_access_enabled", default: true,  null: false
     t.datetime "deleted_at"
+    t.boolean  "lfs_enabled"
   end
 
   add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
@@ -857,7 +861,6 @@ ActiveRecord::Schema.define(version: 20160915081353) do
     t.integer  "build_timeout",                      default: 3600,      null: false
     t.boolean  "pending_delete",                     default: false
     t.boolean  "public_builds",                      default: true,      null: false
-    t.integer  "pushes_since_gc",                    default: 0
     t.boolean  "last_repository_check_failed"
     t.datetime "last_repository_check_at"
     t.boolean  "container_registry_enabled"
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 28c4c7c86ca4c1fe543c7b973b0205769155353f..c5611e2a12154d30fedcff826ac5b00c4f5ef08e 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -406,7 +406,8 @@ To configure the storage driver in Omnibus:
       's3' => {
         'accesskey' => 's3-access-key',
         'secretkey' => 's3-secret-key-for-access-key',
-        'bucket' => 'your-s3-bucket'
+        'bucket' => 'your-s3-bucket',
+        'region' => 'your-s3-region'
       }
     }
     ```
@@ -428,6 +429,7 @@ storage:
     accesskey: 'AKIAKIAKI'
     secretkey: 'secret123'
     bucket: 'gitlab-registry-bucket-AKIAKIAKI'
+    region: 'your-s3-region'
   cache:
     blobdescriptor: inmemory
   delete:
diff --git a/doc/api/README.md b/doc/api/README.md
index 96d94e08487a35be96d788cde79a5f9ab13e037f..7661e1eea028b0db1591d410647caeb1637ba0ce 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -27,6 +27,7 @@ following locations:
 - [Open source license templates](licenses.md)
 - [Namespaces](namespaces.md)
 - [Notes](notes.md) (comments)
+- [Notification settings](notification_settings.md)
 - [Pipelines](pipelines.md)
 - [Projects](projects.md) including setting Webhooks
 - [Project Access Requests](access_requests.md)
@@ -41,8 +42,9 @@ following locations:
 - [Sidekiq metrics](sidekiq_metrics.md)
 - [System Hooks](system_hooks.md)
 - [Tags](tags.md)
-- [Users](users.md)
 - [Todos](todos.md)
+- [Users](users.md)
+- [Validate CI configuration](ci/lint.md)
 
 ### Internal CI API
 
diff --git a/doc/api/ci/builds.md b/doc/api/ci/builds.md
index 2a71b087f193a091d5354c604b7cd6d0f5d1f692..b6d79706a843116d03ecf75b2041a624b742c316 100644
--- a/doc/api/ci/builds.md
+++ b/doc/api/ci/builds.md
@@ -38,6 +38,15 @@ POST /ci/api/v1/builds/register
 curl --request POST "https://gitlab.example.com/ci/api/v1/builds/register" --form "token=t0k3n"
 ```
 
+**Responses:**
+
+| Status | Data |Description                                                                |
+|--------|------|---------------------------------------------------------------------------|
+| `201`  | yes  | When a build is scheduled for a runner                                    |
+| `204`  | no   | When no builds are scheduled for a runner (for GitLab Runner >= `v1.3.0`) |
+| `403`  | no   | When invalid token is used or no token is sent                            |
+| `404`  | no   | When no builds are scheduled for a runner (for GitLab Runner < `v1.3.0`) **or** when the runner is set to `paused` in GitLab runner's configuration page |
+
 ### Update details of an existing build
 
 ```
diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md
new file mode 100644
index 0000000000000000000000000000000000000000..0c96b3ee335778cb1d7c11fb14d86098fde4aec4
--- /dev/null
+++ b/doc/api/ci/lint.md
@@ -0,0 +1,49 @@
+# Validate the .gitlab-ci.yml
+
+> [Introduced][ce-5953] in GitLab 8.12.
+
+Checks if your .gitlab-ci.yml file is valid.
+
+```
+POST ci/lint
+```
+
+| Attribute  | Type    | Required | Description |
+| ---------- | ------- | -------- | -------- |
+| `content`  | string    | yes      | the .gitlab-ci.yaml content|
+
+```bash
+curl --header "Content-Type: application/json" https://gitlab.example.com/api/v3/ci/lint --data '{"content": "{ \"image\": \"ruby:2.1\", \"services\": [\"postgres\"], \"before_script\": [\"gem install bundler\", \"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}'
+```
+
+Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky about indentation and spaces.
+
+Example responses:
+
+* Valid content:
+
+    ```json
+    {
+      "status": "valid",
+      "errors": []
+    }
+    ```
+
+* Invalid content:
+
+    ```json
+    {
+      "status": "invalid",
+      "errors": [
+        "variables config should be a hash of key value pairs"
+      ]
+    }
+    ```
+
+* Without the content attribute:
+
+    ```json
+    {
+      "error": "content is missing"
+    }
+    ```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index a898387eaa2a6887ee7deedd3468462af8445b69..e81d6f9de4b0aca6b7ff8a2cf89040c9be4b5e95 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -84,7 +84,8 @@ Parameters:
     "forks_count": 0,
     "open_issues_count": 3,
     "public_builds": true,
-    "shared_with_groups": []
+    "shared_with_groups": [],
+    "request_access_enabled": false
   }
 ]
 ```
@@ -118,6 +119,7 @@ Example response:
   "visibility_level": 20,
   "avatar_url": null,
   "web_url": "https://gitlab.example.com/groups/twitter",
+  "request_access_enabled": false,
   "projects": [
     {
       "id": 7,
@@ -163,7 +165,8 @@ Example response:
       "forks_count": 0,
       "open_issues_count": 3,
       "public_builds": true,
-      "shared_with_groups": []
+      "shared_with_groups": [],
+      "request_access_enabled": false
     },
     {
       "id": 6,
@@ -209,7 +212,8 @@ Example response:
       "forks_count": 0,
       "open_issues_count": 8,
       "public_builds": true,
-      "shared_with_groups": []
+      "shared_with_groups": [],
+      "request_access_enabled": false
     }
   ],
   "shared_projects": [
@@ -288,6 +292,8 @@ Parameters:
 - `path` (required) - The path of the group
 - `description` (optional) - The group's description
 - `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public.
+- `lfs_enabled` (optional)      - Enable/disable Large File Storage (LFS) for the projects in this group
+- `request_access_enabled` (optional) - Allow users to request member access.
 
 ## Transfer project to group
 
@@ -317,6 +323,8 @@ PUT /groups/:id
 | `path` | string | no | The path of the group |
 | `description` | string | no | The description of the group |
 | `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. |
+| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
+| `request_access_enabled` | boolean | no | Allow users to request member access. |
 
 ```bash
 curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental"
@@ -334,6 +342,7 @@ Example response:
   "visibility_level": 10,
   "avatar_url": null,
   "web_url": "http://gitlab.example.com/groups/h5bp",
+  "request_access_enabled": false,
   "projects": [
     {
       "id": 9,
@@ -378,7 +387,8 @@ Example response:
       "forks_count": 0,
       "open_issues_count": 3,
       "public_builds": true,
-      "shared_with_groups": []
+      "shared_with_groups": [],
+      "request_access_enabled": false
     }
   ]
 }
diff --git a/doc/api/members.md b/doc/api/members.md
index fd6d728dad24b083b08a7461860cbed8419ef7b2..6535e9a7801df3afc35d8a8b6835d29388a86f91 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -110,8 +110,8 @@ POST /projects/:id/members
 | `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
 
 ```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id?access_level=30
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id?access_level=30
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v3/groups/:id/members
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v3/projects/:id/members
 ```
 
 Example response:
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 85d140d06acfe5818232df1624c7f2802238bd71..572844b8b3f7c22323c05b447afc34d4bb93f01c 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -78,7 +78,8 @@ Parameters:
 
 ### Create new issue note
 
-Creates a new note to a single project issue.
+Creates a new note to a single project issue. If you create a note where the body
+only contains an Award Emoji, you'll receive this object back.
 
 ```
 POST /projects/:id/issues/:issue_id/notes
@@ -204,6 +205,7 @@ Parameters:
 ### Create new snippet note
 
 Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet.
+If you create a note where the body only contains an Award Emoji, you'll receive this object back.
 
 ```
 POST /projects/:id/snippets/:snippet_id/notes
@@ -332,6 +334,8 @@ Parameters:
 ### Create new merge request note
 
 Creates a new note for a single merge request.
+If you create a note where the body only contains an Award Emoji, you'll receive
+this object back.
 
 ```
 POST /projects/:id/merge_requests/:merge_request_id/notes
diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md
new file mode 100644
index 0000000000000000000000000000000000000000..ff6c9e4931c747722f18feebf7b88b438ff482ad
--- /dev/null
+++ b/doc/api/notification_settings.md
@@ -0,0 +1,169 @@
+# Notification settings
+
+>**Note:** This feature was [introduced][ce-5632] in GitLab 8.12.
+
+**Valid notification levels**
+
+The notification levels are defined in the `NotificationSetting::level` model enumeration. Currently, these levels are recognized:
+
+```
+disabled
+participating
+watch
+global
+mention
+custom
+```
+
+If the `custom` level is used, specific email events can be controlled. Notification email events are defined in the `NotificationSetting::EMAIL_EVENTS` model variable. Currently, these events are recognized:
+
+```
+new_note
+new_issue
+reopen_issue
+close_issue
+reassign_issue
+new_merge_request
+reopen_merge_request
+close_merge_request
+reassign_merge_request
+merge_merge_request
+```
+
+## Global notification settings
+
+Get current notification settings and email address.
+
+```
+GET /notification_settings
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings
+```
+
+Example response:
+
+```json
+{
+  "level": "participating",
+  "notification_email": "admin@example.com"
+}
+```
+
+## Update global notification settings
+
+Update current notification settings and email address.
+
+```
+PUT /notification_settings
+```
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings?level=watch
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `level` | string | no | The global notification level |
+| `notification_email` | string | no | The email address to send notifications |
+| `new_note` | boolean | no | Enable/disable this notification |
+| `new_issue` | boolean | no | Enable/disable this notification |
+| `reopen_issue` | boolean | no | Enable/disable this notification |
+| `close_issue` | boolean | no | Enable/disable this notification |
+| `reassign_issue` | boolean | no | Enable/disable this notification |
+| `new_merge_request` | boolean | no | Enable/disable this notification |
+| `reopen_merge_request` | boolean | no | Enable/disable this notification |
+| `close_merge_request` | boolean | no | Enable/disable this notification |
+| `reassign_merge_request` | boolean | no | Enable/disable this notification |
+| `merge_merge_request` | boolean | no | Enable/disable this notification |
+
+Example response:
+
+```json
+{
+  "level": "watch",
+  "notification_email": "admin@example.com"
+}
+```
+
+## Group / project level notification settings
+
+Get current group or project notification settings.
+
+```
+GET /groups/:id/notification_settings
+GET /projects/:id/notification_settings
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+
+Example response:
+
+```json
+{
+  "level": "global"
+}
+```
+
+## Update group/project level notification settings
+
+Update current group/project notification settings.
+
+```
+PUT /groups/:id/notification_settings
+PUT /projects/:id/notification_settings
+```
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings?level=watch
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings?level=custom&new_note=true
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+| `level` | string | no | The global notification level |
+| `new_note` | boolean | no | Enable/disable this notification |
+| `new_issue` | boolean | no | Enable/disable this notification |
+| `reopen_issue` | boolean | no | Enable/disable this notification |
+| `close_issue` | boolean | no | Enable/disable this notification |
+| `reassign_issue` | boolean | no | Enable/disable this notification |
+| `new_merge_request` | boolean | no | Enable/disable this notification |
+| `reopen_merge_request` | boolean | no | Enable/disable this notification |
+| `close_merge_request` | boolean | no | Enable/disable this notification |
+| `reassign_merge_request` | boolean | no | Enable/disable this notification |
+| `merge_merge_request` | boolean | no | Enable/disable this notification |
+
+Example responses:
+
+```json
+{
+  "level": "watch"
+}
+
+{
+  "level": "custom",
+  "events": {
+    "new_note": true,
+    "new_issue": false,
+    "reopen_issue": false,
+    "close_issue": false,
+    "reassign_issue": false,
+    "new_merge_request": false,
+    "reopen_merge_request": false,
+    "close_merge_request": false,
+    "reassign_merge_request": false,
+    "merge_merge_request": false
+  }
+}
+```
+
+[ce-5632]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5632
diff --git a/doc/api/projects.md b/doc/api/projects.md
index fe3c8709d1391caae9068820b3dcc4a99d913f36..750ce1508df3ba99ecb2ed26301d2824390c9cbb 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -85,7 +85,8 @@ Parameters:
     "runners_token": "b8547b1dc37721d05889db52fa2f02",
     "public_builds": true,
     "shared_with_groups": [],
-    "only_allow_merge_if_build_succeeds": false
+    "only_allow_merge_if_build_succeeds": false,
+    "request_access_enabled": false
   },
   {
     "id": 6,
@@ -146,7 +147,8 @@ Parameters:
     "runners_token": "b8547b1dc37721d05889db52fa2f02",
     "public_builds": true,
     "shared_with_groups": [],
-    "only_allow_merge_if_build_succeeds": false
+    "only_allow_merge_if_build_succeeds": false,
+    "request_access_enabled": false
   }
 ]
 ```
@@ -283,7 +285,8 @@ Parameters:
       "group_access_level": 10
     }
   ],
-  "only_allow_merge_if_build_succeeds": false
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
@@ -453,6 +456,7 @@ Parameters:
 - `public_builds` (optional)
 - `only_allow_merge_if_build_succeeds` (optional)
 - `lfs_enabled` (optional)
+- `request_access_enabled` (optional) - Allow users to request member access.
 
 ### Create project for user
 
@@ -480,6 +484,7 @@ Parameters:
 - `public_builds` (optional)
 - `only_allow_merge_if_build_succeeds` (optional)
 - `lfs_enabled` (optional)
+- `request_access_enabled` (optional) - Allow users to request member access.
 
 ### Edit project
 
@@ -508,6 +513,7 @@ Parameters:
 - `public_builds` (optional)
 - `only_allow_merge_if_build_succeeds` (optional)
 - `lfs_enabled` (optional)
+- `request_access_enabled` (optional) - Allow users to request member access.
 
 On success, method returns 200 with the updated project. If parameters are
 invalid, 400 is returned.
@@ -588,7 +594,8 @@ Example response:
   "star_count": 1,
   "public_builds": true,
   "shared_with_groups": [],
-  "only_allow_merge_if_build_succeeds": false
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
@@ -655,7 +662,8 @@ Example response:
   "star_count": 0,
   "public_builds": true,
   "shared_with_groups": [],
-  "only_allow_merge_if_build_succeeds": false
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
@@ -742,7 +750,8 @@ Example response:
   "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
   "public_builds": true,
   "shared_with_groups": [],
-  "only_allow_merge_if_build_succeeds": false
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
@@ -829,7 +838,8 @@ Example response:
   "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
   "public_builds": true,
   "shared_with_groups": [],
-  "only_allow_merge_if_build_succeeds": false
+  "only_allow_merge_if_build_succeeds": false,
+  "request_access_enabled": false
 }
 ```
 
diff --git a/doc/api/settings.md b/doc/api/settings.md
index a76dad0ebd47b9d2d1b1f3d15167d3c1f9321f96..aaa2c99642bc79e0f85fffac5041c384467dc15c 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -67,7 +67,7 @@ PUT /application/settings
 | `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
 | `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
 | `domain_blacklist_enabled` | boolean | no | Enable/disable the `domain_blacklist` |
-| `domain_blacklist` | array of strings | yes (if `domain_whitelist_enabled` is `true` | People trying to sign-up with emails from this domain will not be allowed to do so. |
+| `domain_blacklist` | array of strings | yes (if `domain_blacklist_enabled` is `true`) | People trying to sign-up with emails from this domain will not be allowed to do so. |
 | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
 | `after_sign_out_path` | string | no | Where to redirect users after logout |
 | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes |
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index c134106bfd0eb631d80230913b63fb42abba3b5d..71670e6247cc5c47f4a542b9b32d607f5966110e 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -1,17 +1,19 @@
 # CI Examples
 
+A collection of `.gitlab-ci.yml` files is maintained at the [GitLab CI Yml project][gitlab-ci-templates].
+If your favorite programming language or framework are missing we would love your help by sending a merge request
+with a `.gitlab-ci.yml`.
+
+Apart from those, here is an collection of tutorials and guides on setting up your CI pipeline:
+
 - [Testing a PHP application](php.md)
 - [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
 - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
 - [Test a Clojure application](test-clojure-application.md)
 - [Test a Scala application](test-scala-application.md)
 - [Using `dpl` as deployment tool](deployment/README.md)
-- Help your favorite programming language and GitLab by sending a merge request
-  with a guide for that language.
-
-## Outside the documentation
-
 - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
 - [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples)
 - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
-- [A collection of useful .gitlab-ci.yml templates](https://gitlab.com/gitlab-org/gitlab-ci-yml)
+
+[gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index c835ebc2d449a2eca852061f935d3976814c1ff4..c40cdd55ea5e9127475c2557a99c0e7bf8762732 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -105,7 +105,8 @@ What is important is that each job is run independently from each other.
 
 If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
 Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
-the link under **Settings > CI settings** in your project.
+a "CI Lint" button to go to this page under **Pipelines > Pipelines** and
+**Pipelines > Builds** in your project.
 
 For more information and a complete `.gitlab-ci.yml` syntax, please read
 [the documentation on .gitlab-ci.yml](../yaml/README.md).
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 4a7c21f811de542b1cdfdda2ce149f7bc1da9021..6a971c3ae87977b2e95070eb63b71fff9002a11b 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -34,6 +34,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`.
 | **CI_BUILD_REF_NAME**   | all    | all    | The branch or tag name for which project is built |
 | **CI_BUILD_REPO**       | all    | all    | The URL to clone the Git repository |
 | **CI_BUILD_TRIGGERED**  | all    | 0.5    | The flag to indicate that build was [triggered] |
+| **CI_BUILD_MANUAL**     | 8.12   | all    | The flag to indicate that build was manually started |
 | **CI_BUILD_TOKEN**      | all    | 1.2    | Token used for authenticating with the GitLab Container Registry |
 | **CI_PIPELINE_ID**      | 8.10   | 0.5    | The unique id of the current pipeline that GitLab CI uses internally |
 | **CI_PROJECT_ID**       | all    | all    | The unique id of the current project that GitLab CI uses internally |
@@ -47,6 +48,8 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`.
 | **CI_RUNNER_ID**        | 8.10   | 0.5    | The unique id of runner being used |
 | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5    | The description of the runner as saved in GitLab |
 | **CI_RUNNER_TAGS**      | 8.10   | 0.5    | The defined runner tags |
+| **GITLAB_USER_ID**      | 8.12   | all    | The id of the user who started the build |
+| **GITLAB_USER_EMAIL**   | 8.12   | all    | The email of the user who started the build |
 
 **Some of the variables are only available when using runner with at least defined version.**
 
@@ -60,6 +63,7 @@ export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/git
 export CI_BUILD_TAG="1.0.0"
 export CI_BUILD_NAME="spec:other"
 export CI_BUILD_STAGE="test"
+export CI_BUILD_MANUAL="true"
 export CI_BUILD_TRIGGERED="true"
 export CI_BUILD_TOKEN="abcde-1234ABCD5678ef"
 export CI_PIPELINE_ID="1000"
@@ -76,8 +80,10 @@ export CI_RUNNER_DESCRIPTION="my runner"
 export CI_RUNNER_TAGS="docker, linux"
 export CI_SERVER="yes"
 export CI_SERVER_NAME="GitLab"
-export CI_SERVER_REVISION="8.9.0"
-export CI_SERVER_VERSION="70606bf"
+export CI_SERVER_REVISION="70606bf"
+export CI_SERVER_VERSION="8.9.0"
+export GITLAB_USER_ID="42"
+export GITLAB_USER_EMAIL="alexzander@sporer.com"
 ```
 
 ### YAML-defined variables
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index ff4c8ddc54b701a09f596de4e946d22157b20b94..16868554c1ffa9c15e4bf4002be519ebf81c30d6 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -90,8 +90,7 @@ builds, including deploy builds. This can be an array or a multi-line string.
 
 ### after_script
 
->**Note:**
-Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
+> Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
 
 `after_script` is used to define the command that will be run after for all
 builds. This has to be an array or a multi-line string.
@@ -135,8 +134,7 @@ Alias for [stages](#stages).
 
 ### variables
 
->**Note:**
-Introduced in GitLab Runner v0.5.0.
+> Introduced in GitLab Runner v0.5.0.
 
 GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the
 build environment. The variables are stored in the Git repository and are meant
@@ -158,8 +156,7 @@ Variables can be also defined on [job level](#job-variables).
 
 ### cache
 
->**Note:**
-Introduced in GitLab Runner v0.7.0.
+> Introduced in GitLab Runner v0.7.0.
 
 `cache` is used to specify a list of files and directories which should be
 cached between builds.
@@ -220,8 +217,7 @@ will be always present. For implementation details, please check GitLab Runner.
 
 #### cache:key
 
->**Note:**
-Introduced in GitLab Runner v1.0.0.
+> Introduced in GitLab Runner v1.0.0.
 
 The `key` directive allows you to define the affinity of caching
 between jobs, allowing to have a single cache for all jobs,
@@ -531,8 +527,7 @@ The above script will:
 
 #### Manual actions
 
->**Note:**
-Introduced in GitLab 8.10.
+> Introduced in GitLab 8.10.
 
 Manual actions are a special type of job that are not executed automatically;
 they need to be explicitly started by a user. Manual actions can be started
@@ -543,17 +538,16 @@ An example usage of manual actions is deployment to production.
 
 ### environment
 
->**Note:**
-Introduced in GitLab 8.9.
+> Introduced in GitLab 8.9.
 
-`environment` is used to define that a job deploys to a specific environment.
+`environment` is used to define that a job deploys to a specific [environment].
 This allows easy tracking of all deployments to your environments straight from
 GitLab.
 
 If `environment` is specified and no environment under that name exists, a new
 one will be created automatically.
 
-The `environment` name must contain only letters, digits, '-' and '_'. Common
+The `environment` name must contain only letters, digits, '-', '_', '/', '$', '{', '}' and spaces. Common
 names are `qa`, `staging`, and `production`, but you can use whatever name works
 with your workflow.
 
@@ -571,6 +565,35 @@ deploy to production:
 The `deploy to production` job will be marked as doing deployment to
 `production` environment.
 
+#### dynamic environments
+
+> [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6.
+
+`environment` can also represent a configuration hash with `name` and `url`.
+These parameters can use any of the defined CI [variables](#variables)
+(including predefined, secure variables and `.gitlab-ci.yml` variables).
+
+The common use case is to create dynamic environments for branches and use them
+as review apps.
+
+---
+
+**Example configurations**
+
+```
+deploy as review app:
+  stage: deploy
+  script: ...
+  environment:
+    name: review-apps/$CI_BUILD_REF_NAME
+    url: https://$CI_BUILD_REF_NAME.review.example.com/
+```
+
+The `deploy as review app` job will be marked as deployment to dynamically
+create the `review-apps/branch-name` environment.
+
+This environment should be accessible under `https://branch-name.review.example.com/`.
+
 ### artifacts
 
 >**Notes:**
@@ -638,8 +661,7 @@ be available for download in the GitLab UI.
 
 #### artifacts:name
 
->**Note:**
-Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
+> Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
 
 The `name` directive allows you to define the name of the created artifacts
 archive. That way, you can have a unique name for every archive which could be
@@ -702,8 +724,7 @@ job:
 
 #### artifacts:when
 
->**Note:**
-Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
+> Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
 
 `artifacts:when` is used to upload artifacts on build failure or despite the
 failure.
@@ -728,8 +749,7 @@ job:
 
 #### artifacts:expire_in
 
->**Note:**
-Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
+> Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
 
 `artifacts:expire_in` is used to delete uploaded artifacts after the specified
 time. By default, artifacts are stored on GitLab forever. `expire_in` allows you
@@ -764,8 +784,7 @@ job:
 
 ### dependencies
 
->**Note:**
-Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
+> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
 
 This feature should be used in conjunction with [`artifacts`](#artifacts) and
 allows you to define the artifacts to pass between different builds.
@@ -839,9 +858,8 @@ job:
 
 ## Git Strategy
 
->**Note:**
-Introduced in GitLab 8.9 as an experimental feature. May change in future
-releases or be removed completely.
+> Introduced in GitLab 8.9 as an experimental feature. May change in future
+  releases or be removed completely.
 
 You can set the `GIT_STRATEGY` used for getting recent application code. `clone`
 is slower, but makes sure you have a clean directory before every build. `fetch`
@@ -863,8 +881,7 @@ variables:
 
 ## Shallow cloning
 
->**Note:**
-Introduced in GitLab 8.9 as an experimental feature. May change in future
+> Introduced in GitLab 8.9 as an experimental feature. May change in future
 releases or be removed completely.
 
 You can specify the depth of fetching and cloning using `GIT_DEPTH`. This allows
@@ -894,8 +911,7 @@ variables:
 
 ## Hidden keys
 
->**Note:**
-Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
+> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
 
 Keys that start with a dot (`.`) will be not processed by GitLab CI. You can
 use this feature to ignore jobs, or use the
@@ -923,8 +939,7 @@ Read more about the various [YAML features](https://learnxinyminutes.com/docs/ya
 
 ### Anchors
 
->**Note:**
-Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
+> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
 
 YAML also has a handy feature called 'anchors', which let you easily duplicate
 content across your document. Anchors can be used to duplicate/inherit
@@ -1067,3 +1082,5 @@ Visit the [examples README][examples] to see a list of examples using GitLab
 CI with various languages.
 
 [examples]: ../examples/README.md
+[ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323
+[environment]: ../environments.md
diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md
index 047a0b08406941bcea0002b90f9fd39437c5ea59..d7740647a91c47f6864a29b346da56d7abb50859 100644
--- a/doc/container_registry/README.md
+++ b/doc/container_registry/README.md
@@ -78,9 +78,9 @@ delete them.
 > **Note:**
 This feature requires GitLab 8.8 and GitLab Runner 1.2.
 
-Make sure that your GitLab Runner is configured to allow building docker images.
-You have to check the [Using Docker Build documentation](../ci/docker/using_docker_build.md).
-Then see the CI documentation on [Using the GitLab Container Registry](../ci/docker/using_docker_build.md#using-the-gitlab-container-registry).
+Make sure that your GitLab Runner is configured to allow building Docker images by
+following the [Using Docker Build](../ci/docker/using_docker_build.md)
+and [Using the GitLab Container Registry documentation](../ci/docker/using_docker_build.md#using-the-gitlab-container-registry).
 
 ## Limitations
 
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index c2272ab0a2bb844af263a53de2c543fe142677b1..105e2f1242aea0d0183833ea4824221d7c290c48 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -137,3 +137,18 @@ end
 ```
 
 Here the final value of `sleep_real_time` will be `3`, _not_ `1`.
+
+## Tracking Custom Events
+
+Besides instrumenting code GitLab Performance Monitoring also supports tracking
+of custom events. This is primarily intended to be used for tracking business
+metrics such as the number of Git pushes, repository imports, and so on.
+
+To track a custom event simply call `Gitlab::Metrics.add_event` passing it an
+event name and a custom set of (optional) tags. For example:
+
+```ruby
+Gitlab::Metrics.add_event(:user_login, email: current_user.email)
+```
+
+Event names should be verbs such as `push_repository` and `remove_branch`.
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index b8fab3aaff7e376d5cf20b3d4aa978a033182e45..295eae0a88ea62958c23266e14ef0282d8a364f7 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -111,6 +111,28 @@ class MyMigration < ActiveRecord::Migration
 end
 ```
 
+
+## Integer column type
+
+By default, an integer column can hold up to a 4-byte (32-bit) number. That is
+a max value of 2,147,483,647. Be aware of this when creating a column that will
+hold file sizes in byte units. If you are tracking file size in bytes this
+restricts the maximum file size to just over 2GB.
+
+To allow an integer column to hold up to an 8-byte (64-bit) number, explicitly
+set the limit to 8-bytes. This will allow the column to hold a value up to
+9,223,372,036,854,775,807.
+
+Rails migration example:
+
+```
+add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
+
+# or
+
+add_column(:projects, :foo, :integer, default: 10, limit: 8)
+```
+
 ## Testing
 
 Make sure that your migration works with MySQL and PostgreSQL with data. An empty database does not guarantee that your migration is correct.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 9522c3e71708da8cfe0932b3ac0ae1cd805c4bf6..3ac813aa914a5ccacafc334b71e2b57d497c4047 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of
     cd /home/git
     sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
     cd gitlab-workhorse
-    sudo -u git -H git checkout v0.7.11
+    sudo -u git -H git checkout v0.8.2
     sudo -u git -H make
 
 ### Initialize Database and Activate Advanced Features
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 9799e0a37306e9c5bdb2e6986d7fe24c0a93899c..766a71199435d25f1ba073dd50d36eec9cc90a9c 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -86,7 +86,7 @@ if your available memory changes.
 
 Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
 
-## Gitlab Runner
+## GitLab Runner
 
 We strongly advise against installing GitLab Runner on the same machine you plan
 to install GitLab on. Depending on how you decide to configure GitLab Runner and
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 835af5443a329bd05f46fea2fdd5ee0c591d484f..3f4056dc4401703c9b648eab7e306e2f7fd936eb 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -79,6 +79,9 @@ gitlab_rails['backup_upload_connection'] = {
   'region' => 'eu-west-1',
   'aws_access_key_id' => 'AKIAKIAKI',
   'aws_secret_access_key' => 'secret123'
+  # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty
+  # ie. 'aws_access_key_id' => '',
+  # 'use_iam_profile' => 'true'
 }
 gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
 ```
@@ -95,6 +98,9 @@ For installations from source:
         region: eu-west-1
         aws_access_key_id: AKIAKIAKI
         aws_secret_access_key: 'secret123'
+        # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty
+        # ie. aws_access_key_id: ''
+        # use_iam_profile: 'true'
       # The remote 'directory' to store your backups. For S3, this would be the bucket name.
       remote_directory: 'my.s3.bucket'
       # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md
index c8ca42f97bce74908841461f98094b1cb190ce92..686c7e8e7b5c6a67a603f6ae92f7250eebf36218 100644
--- a/doc/update/8.11-to-8.12.md
+++ b/doc/update/8.11-to-8.12.md
@@ -70,7 +70,7 @@ sudo -u git -H git checkout 8-12-stable-ee
 ```bash
 cd /home/git/gitlab-shell
 sudo -u git -H git fetch --all --tags
-sudo -u git -H git checkout v3.4.0
+sudo -u git -H git checkout v3.5.0
 ```
 
 ### 6. Update gitlab-workhorse
@@ -82,7 +82,7 @@ GitLab 8.1.
 ```bash
 cd /home/git/gitlab-workhorse
 sudo -u git -H git fetch --all
-sudo -u git -H git checkout v0.7.11
+sudo -u git -H git checkout v0.8.2
 sudo -u git -H make
 ```
 
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 1498cb361c8718d28768129e705729c74bc2c21a..f1b75298180723e3ca526e87f806ebcafa2841d2 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -63,7 +63,7 @@ The following table depicts the various user permission levels in a project.
 | Force push to protected branches [^2] |         |            |             |          |        |
 | Remove protected branches [^2]        |         |            |             |          |        |
 
-[^1]: If **Allow guest to access builds** is enabled in CI settings
+[^1]: If **Public pipelines** is enabled in **Project Settings > CI/CD Pipelines**
 [^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
 
 ## Group
diff --git a/doc/user/project/builds/artifacts.md b/doc/user/project/builds/artifacts.md
index c93ae1c369ca8892ff9ddf2c4ea910f0eb58baba..88f1863dddb7b2a13032fe30bb0cd923bd477b97 100644
--- a/doc/user/project/builds/artifacts.md
+++ b/doc/user/project/builds/artifacts.md
@@ -101,4 +101,36 @@ inside GitLab that make that possible.
 
     ![Build artifacts browser](img/build_artifacts_browser.png)
 
+## Downloading the latest build artifacts
+
+It is possible to download the latest artifacts of a build via a well known URL
+so you can use it for scripting purposes.
+
+The structure of the URL is the following:
+
+```
+https://example.com/<namespace>/<project>/builds/artifacts/<ref>/download?job=<job_name>
+```
+
+For example, to download the latest artifacts of the job named `rspec 6 20` of
+the `master` branch of the `gitlab-ce` project that belongs to the `gitlab-org`
+namespace, the URL would be:
+
+```
+https://gitlab.com/gitlab-org/gitlab-ce/builds/artifacts/master/download?job=rspec+6+20
+```
+
+The latest builds are also exposed in the UI in various places. Specifically,
+look for the download button in:
+
+- the main project's page
+- the branches page
+- the tags page
+
+If the latest build has failed to upload the artifacts, you can see that
+information in the UI.
+
+![Latest artifacts button](img/build_latest_artifacts_browser.png)
+
+
 [gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
diff --git a/doc/user/project/builds/img/build_latest_artifacts_browser.png b/doc/user/project/builds/img/build_latest_artifacts_browser.png
new file mode 100644
index 0000000000000000000000000000000000000000..d8e9071958c388be09f725813c6012e36779ddd3
Binary files /dev/null and b/doc/user/project/builds/img/build_latest_artifacts_browser.png differ
diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md
index f79535d1542120cb968086edea8d473d118be512..5af9a5d049c0518c81ba6007073a8335a1d2f9ba 100644
--- a/doc/user/project/merge_requests.md
+++ b/doc/user/project/merge_requests.md
@@ -93,6 +93,9 @@ A merge request contains all the history from a repository, plus the additional
 commits added to the branch associated with the merge request. Here's a few
 tricks to checkout a merge request locally.
 
+Please note that you can checkout a merge request locally even if the source
+project is a fork (even a private fork) of the target project.
+
 #### Checkout locally by adding a git alias
 
 Add the following alias to your `~/.gitconfig`:
diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
index 2b1c5ddd5623476c566fd77170431f4e155a84b6..a6aa4b47835cb3be86bce7ee9c2f7cca7b97188d 100644
--- a/doc/user/project/merge_requests/versions.md
+++ b/doc/user/project/merge_requests/versions.md
@@ -12,6 +12,12 @@ can select an older one from version dropdown.
 
 ![Merge Request Versions](img/versions.png)
 
+You can also compare the merge request version with older one to see what is
+changed since then.
+
+Please note that comments are disabled while viewing outdated merge versions
+or comparing to versions other than base.
+
 ---
 
 >**Note:**
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 08ff89ce6aeee7c2b128074278b8c50728c92cd9..445c0ee833307973cbf95ba15963ea3cc7272566 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -3,8 +3,8 @@
 >**Notes:**
 >
 >  - [Introduced][ce-3050] in GitLab 8.9.
->  - Importing will not be possible if the import instance version is lower
->    than that of the exporter.
+>  - Importing will not be possible if the import instance version differs from
+>    that of the exporter.
 >  - For existing installations, the project import option has to be enabled in
 >    application settings (`/admin/application_settings`) under 'Import sources'.
 >    You will have to be an administrator to enable and use the import functionality.
@@ -17,6 +17,20 @@
 Existing projects running on any GitLab instance or GitLab.com can be exported
 with all their related data and be moved into a new GitLab instance.
 
+## Version history
+
+| GitLab version | Import/Export version |
+| -------- | -------- |
+| 8.12.0 to current  | 0.1.4    |
+| 8.10.3   | 0.1.3    |
+| 8.10.0   | 0.1.2    |
+| 8.9.5    | 0.1.1    |
+| 8.9.0    | 0.1.0    |
+ 
+ > The table reflects what GitLab version we updated the Import/Export version at.
+ > For instance, 8.10.3 and 8.11 will have the same Import/Export version (0.1.3)
+ > and the exports between them will be compatible.
+
 ## Exported contents
 
 The following items will be exported:
diff --git a/doc/workflow/importing/img/import_projects_from_github_importer.png b/doc/workflow/importing/img/import_projects_from_github_importer.png
index b6ed8dd692aa5140cdae9839c55869139573efd3..2082de06f47e9a27592d8dbac4f473879b3e6481 100644
Binary files a/doc/workflow/importing/img/import_projects_from_github_importer.png and b/doc/workflow/importing/img/import_projects_from_github_importer.png differ
diff --git a/doc/workflow/importing/img/import_projects_from_github_new_project_page.png b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png
index c8f35a50f48dcfdc60c1cfd0fec986936b821894..6e91c430a33c22c288e0b57608ed5b0968fb885c 100644
Binary files a/doc/workflow/importing/img/import_projects_from_github_new_project_page.png and b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png differ
diff --git a/doc/workflow/importing/img/import_projects_from_github_select_auth_method.png b/doc/workflow/importing/img/import_projects_from_github_select_auth_method.png
new file mode 100644
index 0000000000000000000000000000000000000000..c11863ab10c57d456c302e250f7c7df810857e66
Binary files /dev/null and b/doc/workflow/importing/img/import_projects_from_github_select_auth_method.png differ
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index 306caabf6e6fb1dcd0524f90817d65a5dc18a750..dd38fe0bc0129cd9b494a40cbc11175624134684 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -1,53 +1,113 @@
 # Import your project from GitHub to GitLab
 
+Import your projects from GitHub to GitLab with minimal effort.
+
+## Overview
+
 >**Note:**
-In order to enable the GitHub import setting, you may also want to
-enable the [GitHub integration][gh-import] in your GitLab instance. This
-configuration is optional, you will be able import your GitHub repositories
-with a Personal Access Token.
+If you are an administrator you can enable the [GitHub integration][gh-import]
+in your GitLab instance sitewide. This configuration is optional, users will be
+able import their GitHub repositories with a [personal access token][gh-token].
 
-At its current state, GitHub importer can import:
+- At its current state, GitHub importer can import:
+  - the repository description (GitLab 7.7+)
+  - the Git repository data (GitLab 7.7+)
+  - the issues (GitLab 7.7+)
+  - the pull requests (GitLab 8.4+)
+  - the wiki pages (GitLab 8.4+)
+  - the milestones (GitLab 8.7+)
+  - the labels (GitLab 8.7+)
+  - the release note descriptions (GitLab 8.12+)
+- References to pull requests and issues are preserved (GitLab 8.7+)
+- Repository public access is retained. If a repository is private in GitHub
+  it will be created as private in GitLab as well.
 
-- the repository description (introduced in GitLab 7.7)
-- the git repository data (introduced in GitLab 7.7)
-- the issues (introduced in GitLab 7.7)
-- the pull requests (introduced in GitLab 8.4)
-- the wiki pages (introduced in GitLab 8.4)
-- the milestones (introduced in GitLab 8.7)
-- the labels (introduced in GitLab 8.7)
+## How it works
 
-With GitLab 8.7+, references to pull requests and issues are preserved.
+When issues/pull requests are being imported, the GitHub importer tries to find
+the GitHub author/assignee in GitLab's database using the GitHub ID. For this
+to work, the GitHub author/assignee should have signed in beforehand in GitLab
+and [**associated their GitHub account**][social sign-in]. If the user is not
+found in GitLab's database, the project creator (most of the times the current
+user that started the import process) is set as the author, but a reference on
+the issue about the original GitHub author is kept.
 
-The importer page is visible when you [create a new project][new-project].
-Click on the **GitHub** link and, if you are logged in via the GitHub
-integration, you will be redirected to GitHub for permission to access your
-projects. After accepting, you'll be automatically redirected to the importer.
+The importer will create any new namespaces (groups) if they don't exist or in
+the case the namespace is taken, the repository will be imported under the user's
+namespace that started the import process.
 
-If you are not using the GitHub integration, you can still perform a one-off
-authorization with GitHub to access your projects.
+## Importing your GitHub repositories
 
-Alternatively, you can also enter a GitHub Personal Access Token. Once you enter
-your token, you'll be taken to the importer.
+The importer page is visible when you create a new project.
 
 ![New project page on GitLab](img/import_projects_from_github_new_project_page.png)
 
----
+Click on the **GitHub** link and the import authorization process will start.
+There are two ways to authorize access to your GitHub repositories:
 
-While at the GitHub importer page, you can see the import statuses of your
-GitHub projects. Those that are being imported will show a _started_ status,
-those already imported will be green, whereas those that are not yet imported
-have an **Import** button on the right side of the table. If you want, you can
-import all your GitHub projects in one go by hitting **Import all projects**
-in the upper left corner.
+1. [Using the GitHub integration][gh-integration] (if it's enabled by your
+   GitLab administrator). This is the preferred way as it's possible to
+   preserve the GitHub authors/assignees. Read more in the [How it works](#how-it-works)
+   section.
+1. [Using a personal access token][gh-token] provided by GitHub.
 
-![GitHub importer page](img/import_projects_from_github_importer.png)
+![Select authentication method](img/import_projects_from_github_select_auth_method.png)
+
+### Authorize access to your repositories using the GitHub integration
 
----
+If the [GitHub integration][gh-import] is enabled by your GitLab administrator,
+you can use it instead of the personal access token.
+
+1. First you may want to connect your GitHub account to GitLab in order for
+   the username mapping to be correct. Follow the [social sign-in] documentation
+   on how to do so.
+1. Once you connect GitHub, click the **List your GitHub repositories** button
+   and you will be redirected to GitHub for permission to access your projects.
+1. After accepting, you'll be automatically redirected to the importer.
+
+You can now go on and [select which repositories to import](#select-which-repositories-to-import).
+
+### Authorize access to your repositories using a personal access token
+
+>**Note:**
+For a proper author/assignee mapping for issues and pull requests, the
+[GitHub integration][gh-integration] should be used instead of the
+[personal access token][gh-token]. If the GitHub integration is enabled by your
+GitLab administrator, it should be the preferred method to import your repositories.
+Read more in the [How it works](#how-it-works) section.
 
-The importer will create any new namespaces if they don't exist or in the
-case the namespace is taken, the project will be imported on the user's
-namespace.
+If you are not using the GitHub integration, you can still perform a one-off
+authorization with GitHub to grant GitLab access your repositories:
+
+1. Go to <https://github.com/settings/tokens/new>.
+1. Enter a token description.
+1. Check the `repo` scope.
+1. Click **Generate token**.
+1. Copy the token hash.
+1. Go back to GitLab and provide the token to the GitHub importer.
+1. Hit the **List your GitHub repositories** button and wait while GitLab reads
+   your repositories' information. Once done, you'll be taken to the importer
+   page to select the repositories to import.
+
+### Select which repositories to import
+
+After you've authorized access to your GitHub repositories, you will be
+redirected to the GitHub importer page.
+
+From there, you can see the import statuses of your GitHub repositories.
+
+- Those that are being imported will show a _started_ status,
+- those already successfully imported will be green with a _done_ status,
+- whereas those that are not yet imported will have an **Import** button on the
+  right side of the table.
+
+If you want, you can import all your GitHub projects in one go by hitting
+**Import all projects** in the upper left corner.
+
+![GitHub importer page](img/import_projects_from_github_importer.png)
 
 [gh-import]: ../../integration/github.md "GitHub integration"
-[ee-gh]: http://docs.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
 [new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab"
+[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration
+[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token
+[social sign-in]: ../../profile/account/social_sign_in.md
diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb
index 03f87df7a60cf02657e813000516fea7dacef500..11dc7f580f03293119157ba1a3cd72744d8907de 100644
--- a/features/steps/admin/settings.rb
+++ b/features/steps/admin/settings.rb
@@ -33,6 +33,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
     page.check('Issue')
     page.check('Merge request')
     page.check('Build')
+    page.check('Pipeline')
     click_on 'Save'
   end
 
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 56b289495858ddde4c21dd73e202a3c760673341..df17b5626c694ba5f65c526a516810808cd4145d 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -31,7 +31,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I click link "Closed"' do
-    click_link "Closed"
+    page.within('.issues-state-filters') do
+      click_link "Closed"
+    end
   end
 
   step 'I should see merge request "Wiki Feature"' do
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index d02b469dac8bb25014fa3ea7c261e439e94553ac..29a97ccbd75ccdf0bd80db437db09dd9283f8a77 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -20,7 +20,7 @@ module API
 
           access_requesters = paginate(source.requesters.includes(:user))
 
-          present access_requesters.map(&:user), with: Entities::AccessRequester, access_requesters: access_requesters
+          present access_requesters.map(&:user), with: Entities::AccessRequester, source: source
         end
 
         # Request access to the group/project
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e14464c1b0d05847a67c8c22cd1181fb6a9f9e6d..74ca4728695f3bc26db80f096285f58c7d9307ab 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -45,11 +45,13 @@ module API
     mount ::API::Keys
     mount ::API::Labels
     mount ::API::LicenseTemplates
+    mount ::API::Lint
     mount ::API::Members
     mount ::API::MergeRequests
     mount ::API::Milestones
     mount ::API::Namespaces
     mount ::API::Notes
+    mount ::API::NotificationSettings
     mount ::API::Pipelines
     mount ::API::ProjectHooks
     mount ::API::ProjectSnippets
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 5e3c9563703ad0c25882cc9dec866328ca1eb208..dfbdd597d29e0bde6ac3cba9dc51ff44fd8d60d5 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -37,7 +37,7 @@ module API
       #   id (required) - The ID of a project
       #   sha (required) - The commit hash
       #   ref (optional) - The ref
-      #   state (required) - The state of the status. Can be: pending, running, success, error or failure
+      #   state (required) - The state of the status. Can be: pending, running, success, failed or canceled
       #   target_url (optional) - The target URL to associate with this status
       #   description (optional) - A short description of the status
       #   name or context (optional) - A string label to differentiate this status from the status of other systems. Default: "default"
@@ -46,7 +46,7 @@ module API
       post ':id/statuses/:sha' do
         authorize! :create_commit_status, user_project
         required_attributes! [:state]
-        attrs = attributes_for_keys [:ref, :target_url, :description, :context, :name]
+        attrs = attributes_for_keys [:target_url, :description]
         commit = @project.commit(params[:sha])
         not_found! 'Commit' unless commit
 
@@ -58,36 +58,38 @@ module API
         # the first found branch on that commit
 
         ref = params[:ref]
-        unless ref
-          branches = @project.repository.branch_names_contains(commit.sha)
-          not_found! 'References for commit' if branches.none?
-          ref = branches.first
-        end
+        ref ||= @project.repository.branch_names_contains(commit.sha).first
+        not_found! 'References for commit' unless ref
+
+        name = params[:name] || params[:context] || 'default'
 
         pipeline = @project.ensure_pipeline(ref, commit.sha, current_user)
 
-        name = params[:name] || params[:context]
-        status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
-        status ||= GenericCommitStatus.new(project: @project, pipeline: pipeline, user: current_user)
-        status.update(attrs)
+        status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
+          project: @project, pipeline: pipeline,
+          user: current_user, name: name, ref: ref)
+        status.attributes = attrs
 
-        case params[:state].to_s
-        when 'running'
-          status.run
-        when 'success'
-          status.success
-        when 'failed'
-          status.drop
-        when 'canceled'
-          status.cancel
-        else
-          status.status = params[:state].to_s
-        end
+        begin
+          case params[:state].to_s
+          when 'pending'
+            status.enqueue!
+          when 'running'
+            status.enqueue
+            status.run!
+          when 'success'
+            status.success!
+          when 'failed'
+            status.drop!
+          when 'canceled'
+            status.cancel!
+          else
+            render_api_error!('invalid state', 400)
+          end
 
-        if status.save
           present status, with: Entities::CommitStatus
-        else
-          render_validation_error!(status)
+        rescue StateMachines::InvalidTransition => e
+          render_api_error!(e.message, 400)
         end
       end
     end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 3faba79415b36aa0f2768bf547394dbdf9fb390a..92a6f29adb0b7840611ee1e8d802e4085ee61e2a 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -86,7 +86,8 @@ module API
       expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) }
 
       expose :created_at, :last_activity_at
-      expose :shared_runners_enabled, :lfs_enabled
+      expose :shared_runners_enabled
+      expose :lfs_enabled?, as: :lfs_enabled
       expose :creator_id
       expose :namespace
       expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
@@ -99,30 +100,33 @@ module API
         SharedGroup.represent(project.project_group_links.all, options)
       end
       expose :only_allow_merge_if_build_succeeds
+      expose :request_access_enabled
     end
 
     class Member < UserBasic
       expose :access_level do |user, options|
-        member = options[:member] || options[:members].find { |m| m.user_id == user.id }
+        member = options[:member] || options[:source].members.find_by(user_id: user.id)
         member.access_level
       end
       expose :expires_at do |user, options|
-        member = options[:member] || options[:members].find { |m| m.user_id == user.id }
+        member = options[:member] || options[:source].members.find_by(user_id: user.id)
         member.expires_at
       end
     end
 
     class AccessRequester < UserBasic
       expose :requested_at do |user, options|
-        access_requester = options[:access_requester] || options[:access_requesters].find { |m| m.user_id == user.id }
+        access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id)
         access_requester.requested_at
       end
     end
 
     class Group < Grape::Entity
       expose :id, :name, :path, :description, :visibility_level
+      expose :lfs_enabled?, as: :lfs_enabled
       expose :avatar_url
       expose :web_url
+      expose :request_access_enabled
     end
 
     class GroupDetail < Group
@@ -375,7 +379,7 @@ module API
       expose :access_level
       expose :notification_level do |member, options|
         if member.notification_setting
-          NotificationSetting.levels[member.notification_setting.level]
+          ::NotificationSetting.levels[member.notification_setting.level]
         end
       end
     end
@@ -386,6 +390,21 @@ module API
     class GroupAccess < MemberAccess
     end
 
+    class NotificationSetting < Grape::Entity
+      expose :level
+      expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do
+        ::NotificationSetting::EMAIL_EVENTS.each do |event|
+          expose event
+        end
+      end
+    end
+
+    class GlobalNotificationSetting < NotificationSetting
+      expose :notification_email do |notification_setting, options|
+        notification_setting.user.notification_email
+      end
+    end
+
     class ProjectService < Grape::Entity
       expose :id, :title, :created_at, :updated_at, :active
       expose :push_events, :issues_events, :merge_requests_events
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index d2df77238d59adb6f8fa79e356a0a6439a9359e8..953fa474e8811ccaea9e223b7a6f5d6d16e5f02d 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -23,17 +23,19 @@ module API
       # Create group. Available only for users who can create groups.
       #
       # Parameters:
-      #   name (required)             - The name of the group
-      #   path (required)             - The path of the group
-      #   description (optional)      - The description of the group
-      #   visibility_level (optional) - The visibility level of the group
+      #   name (required)                   - The name of the group
+      #   path (required)                   - The path of the group
+      #   description (optional)            - The description of the group
+      #   visibility_level (optional)       - The visibility level of the group
+      #   lfs_enabled (optional)            - Enable/disable LFS for the projects in this group
+      #   request_access_enabled (optional) - Allow users to request member access
       # Example Request:
       #   POST /groups
       post do
         authorize! :create_group
         required_attributes! [:name, :path]
 
-        attrs = attributes_for_keys [:name, :path, :description, :visibility_level]
+        attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled]
         @group = Group.new(attrs)
 
         if @group.save
@@ -47,17 +49,19 @@ module API
       # Update group. Available only for users who can administrate groups.
       #
       # Parameters:
-      #   id (required)               - The ID of a group
-      #   path (optional)             - The path of the group
-      #   description (optional)      - The description of the group
-      #   visibility_level (optional) - The visibility level of the group
+      #   id (required)                     - The ID of a group
+      #   path (optional)                   - The path of the group
+      #   description (optional)            - The description of the group
+      #   visibility_level (optional)       - The visibility level of the group
+      #   lfs_enabled (optional)            - Enable/disable LFS for the projects in this group
+      #   request_access_enabled (optional) - Allow users to request member access
       # Example Request:
       #   PUT /groups/:id
       put ':id' do
         group = find_group(params[:id])
         authorize! :admin_group, group
 
-        attrs = attributes_for_keys [:name, :path, :description, :visibility_level]
+        attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled]
 
         if ::Groups::UpdateService.new(group, current_user, attrs).execute
           present group, with: Entities::GroupDetail
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 6a20ba95a79a199e2ea2dd873bc5f8faa565fd2e..150875ed4f083a790de485eebbccb46a59e3479e 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -269,6 +269,10 @@ module API
       render_api_error!('304 Not Modified', 304)
     end
 
+    def no_content!
+      render_api_error!('204 No Content', 204)
+    end
+
     def render_validation_error!(model)
       if model.errors.any?
         render_api_error!(model.errors.messages || '400 Bad Request', 400)
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 6e6efece7c499e00b4d205690ade8c8debe5cdf7..1114fd21784241359505bd6306322a0fee91fc87 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -35,6 +35,14 @@ module API
             Project.find_with_namespace(project_path)
           end
         end
+
+        def ssh_authentication_abilities
+          [
+            :read_project,
+            :download_code,
+            :push_code
+          ]
+        end
       end
 
       post "/allowed" do
@@ -51,9 +59,9 @@ module API
 
         access =
           if wiki?
-            Gitlab::GitAccessWiki.new(actor, project, protocol)
+            Gitlab::GitAccessWiki.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
           else
-            Gitlab::GitAccess.new(actor, project, protocol)
+            Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
           end
 
         access_status = access.check(params[:action], params[:changes])
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 556684187d87f27c02b05d0ff7638694a4f79c03..c9689e6f8ef17b72219d67cef673bef542b67a0c 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -41,7 +41,8 @@ module API
         issues = current_user.issues.inc_notes_with_associations
         issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
         issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
-        issues.reorder(issuable_order_by => issuable_sort)
+        issues = issues.reorder(issuable_order_by => issuable_sort)
+
         present paginate(issues), with: Entities::Issue, current_user: current_user
       end
     end
@@ -73,7 +74,11 @@ module API
         params[:group_id] = group.id
         params[:milestone_title] = params.delete(:milestone)
         params[:label_name] = params.delete(:labels)
-        params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort]
+
+        if params[:order_by] || params[:sort]
+          # The Sortable concern takes 'created_desc', not 'created_at_desc' (for example)
+          params[:sort] = "#{issuable_order_by.sub('_at', '')}_#{issuable_sort}"
+        end
 
         issues = IssuesFinder.new(current_user, params).execute
 
@@ -113,7 +118,8 @@ module API
           issues = filter_issues_milestone(issues, params[:milestone])
         end
 
-        issues.reorder(issuable_order_by => issuable_sort)
+        issues = issues.reorder(issuable_order_by => issuable_sort)
+
         present paginate(issues), with: Entities::Issue, current_user: current_user
       end
 
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ae43a4a32376bcee210bb07c72ed683352707090
--- /dev/null
+++ b/lib/api/lint.rb
@@ -0,0 +1,21 @@
+module API
+  class Lint < Grape::API
+    namespace :ci do
+      desc 'Validation of .gitlab-ci.yml content'
+      params do
+        requires :content, type: String, desc: 'Content of .gitlab-ci.yml'
+      end
+      post '/lint' do
+        error = Ci::GitlabCiYamlProcessor.validation_message(params[:content])
+
+        status 200
+
+        if error.blank?
+          { status: 'valid', errors: [] }
+        else
+          { status: 'invalid', errors: [error] }
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 94c16710d9a5b5cf0fa1ef89c90836ba1a11ebc9..37f0a6512f426c67a122a0d877f0c5f05972efa8 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -18,11 +18,11 @@ module API
         get ":id/members" do
           source = find_source(source_type, params[:id])
 
-          members = source.members.includes(:user)
-          members = members.joins(:user).merge(User.search(params[:query])) if params[:query]
-          members = paginate(members)
+          users = source.users
+          users = users.merge(User.search(params[:query])) if params[:query]
+          users = paginate(users)
 
-          present members.map(&:user), with: Entities::Member, members: members
+          present users, with: Entities::Member, source: source
         end
 
         # Get a group/project member
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 8bfa998dc531eb86b162f511281584f344f64a89..c5c214d4d1339f876c2551c72b6888ff45c26aed 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -83,12 +83,12 @@ module API
             opts[:created_at] = params[:created_at]
           end
 
-          @note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+          note = ::Notes::CreateService.new(user_project, current_user, opts).execute
 
-          if @note.valid?
-            present @note, with: Entities::Note
+          if note.valid?
+            present note, with: Entities::const_get(note.class.name)
           else
-            not_found!("Note #{@note.errors.messages}")
+            not_found!("Note #{note.errors.messages}")
           end
         end
 
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a70a7e7107390bf5851947a22dc6d39d2d9ec259
--- /dev/null
+++ b/lib/api/notification_settings.rb
@@ -0,0 +1,97 @@
+module API
+  # notification_settings API
+  class NotificationSettings < Grape::API
+    before { authenticate! }
+
+    helpers ::API::Helpers::MembersHelpers
+
+    resource :notification_settings do
+      desc 'Get global notification level settings and email, defaults to Participate' do
+        detail 'This feature was introduced in GitLab 8.12'
+        success Entities::GlobalNotificationSetting
+      end
+      get do
+        notification_setting = current_user.global_notification_setting
+
+        present notification_setting, with: Entities::GlobalNotificationSetting
+      end
+
+      desc 'Update global notification level settings and email, defaults to Participate' do
+        detail 'This feature was introduced in GitLab 8.12'
+        success Entities::GlobalNotificationSetting
+      end
+      params do
+        optional :level, type: String, desc: 'The global notification level'
+        optional :notification_email, type: String, desc: 'The email address to send notifications'
+        NotificationSetting::EMAIL_EVENTS.each do |event|
+          optional event, type: Boolean, desc: 'Enable/disable this notification'
+        end
+      end
+      put do
+        notification_setting = current_user.global_notification_setting
+
+        begin
+          notification_setting.transaction do
+            new_notification_email = params.delete(:notification_email)
+            declared_params = declared(params, include_missing: false).to_h
+
+            current_user.update(notification_email: new_notification_email) if new_notification_email
+            notification_setting.update(declared_params)
+          end
+        rescue ArgumentError => e # catch level enum error
+          render_api_error! e.to_s, 400
+        end
+
+        render_validation_error! current_user
+        render_validation_error! notification_setting
+        present notification_setting, with: Entities::GlobalNotificationSetting
+      end
+    end
+
+    %w[group project].each do |source_type|
+      resource source_type.pluralize do
+        desc "Get #{source_type} level notification level settings, defaults to Global" do
+          detail 'This feature was introduced in GitLab 8.12'
+          success Entities::NotificationSetting
+        end
+        params do
+          requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME'
+        end
+        get ":id/notification_settings" do
+          source = find_source(source_type, params[:id])
+
+          notification_setting = current_user.notification_settings_for(source)
+
+          present notification_setting, with: Entities::NotificationSetting
+        end
+
+        desc "Update #{source_type} level notification level settings, defaults to Global" do
+          detail 'This feature was introduced in GitLab 8.12'
+          success Entities::NotificationSetting
+        end
+        params do
+          requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME'
+          optional :level, type: String, desc: "The #{source_type} notification level"
+          NotificationSetting::EMAIL_EVENTS.each do |event|
+            optional event, type: Boolean, desc: 'Enable/disable this notification'
+          end
+        end
+        put ":id/notification_settings" do
+          source = find_source(source_type, params.delete(:id))
+          notification_setting = current_user.notification_settings_for(source)
+
+          begin
+            declared_params = declared(params, include_missing: false).to_h
+
+            notification_setting.update(declared_params)
+          rescue ArgumentError => e # catch level enum error
+            render_api_error! e.to_s, 400
+          end
+
+          render_validation_error! notification_setting
+          present notification_setting, with: Entities::NotificationSetting
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 2aae75c471d337c6919e4506f5511800c1e8c5be..2a0c8e1f2c0abe9a469d23e27e8dcb445af382e2 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -13,11 +13,14 @@ module API
       params do
         optional :page,     type: Integer, desc: 'Page number of the current request'
         optional :per_page, type: Integer, desc: 'Number of items per page'
+        optional :scope,    type: String, values: ['running', 'branches', 'tags'],
+                            desc: 'Either running, branches, or tags'
       end
       get ':id/pipelines' do
         authorize! :read_pipeline, user_project
 
-        present paginate(user_project.pipelines), with: Entities::Pipeline
+        pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope])
+        present paginate(pipelines), with: Entities::Pipeline
       end
 
       desc 'Gets a specific pipeline for the project' do
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 4033f597859d3c87b3ac205996f490b0bae9cdb0..5eb83c2c8f827ed7239ad918ebae0f76a77cfad1 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -91,8 +91,8 @@ module API
       # Create new project
       #
       # Parameters:
-      #   name (required) - name for new project
-      #   description (optional) - short project description
+      #   name (required)                   - name for new project
+      #   description (optional)            - short project description
       #   issues_enabled (optional)
       #   merge_requests_enabled (optional)
       #   builds_enabled (optional)
@@ -100,33 +100,35 @@ module API
       #   snippets_enabled (optional)
       #   container_registry_enabled (optional)
       #   shared_runners_enabled (optional)
-      #   namespace_id (optional) - defaults to user namespace
-      #   public (optional) - if true same as setting visibility_level = 20
-      #   visibility_level (optional) - 0 by default
+      #   namespace_id (optional)           - defaults to user namespace
+      #   public (optional)                 - if true same as setting visibility_level = 20
+      #   visibility_level (optional)       - 0 by default
       #   import_url (optional)
       #   public_builds (optional)
       #   lfs_enabled (optional)
+      #   request_access_enabled (optional) - Allow users to request member access
       # Example Request
       #   POST /projects
       post do
         required_attributes! [:name]
-        attrs = attributes_for_keys [:name,
-                                     :path,
+        attrs = attributes_for_keys [:builds_enabled,
+                                     :container_registry_enabled,
                                      :description,
+                                     :import_url,
                                      :issues_enabled,
+                                     :lfs_enabled,
                                      :merge_requests_enabled,
-                                     :builds_enabled,
-                                     :wiki_enabled,
-                                     :snippets_enabled,
-                                     :container_registry_enabled,
-                                     :shared_runners_enabled,
+                                     :name,
                                      :namespace_id,
+                                     :only_allow_merge_if_build_succeeds,
+                                     :path,
                                      :public,
-                                     :visibility_level,
-                                     :import_url,
                                      :public_builds,
-                                     :only_allow_merge_if_build_succeeds,
-                                     :lfs_enabled]
+                                     :request_access_enabled,
+                                     :shared_runners_enabled,
+                                     :snippets_enabled,
+                                     :visibility_level,
+                                     :wiki_enabled]
         attrs = map_public_to_visibility_level(attrs)
         @project = ::Projects::CreateService.new(current_user, attrs).execute
         if @project.saved?
@@ -143,10 +145,10 @@ module API
       # Create new project for a specified user.  Only available to admin users.
       #
       # Parameters:
-      #   user_id (required) - The ID of a user
-      #   name (required) - name for new project
-      #   description (optional) - short project description
-      #   default_branch (optional) - 'master' by default
+      #   user_id (required)                - The ID of a user
+      #   name (required)                   - name for new project
+      #   description (optional)            - short project description
+      #   default_branch (optional)         - 'master' by default
       #   issues_enabled (optional)
       #   merge_requests_enabled (optional)
       #   builds_enabled (optional)
@@ -154,31 +156,33 @@ module API
       #   snippets_enabled (optional)
       #   container_registry_enabled (optional)
       #   shared_runners_enabled (optional)
-      #   public (optional) - if true same as setting visibility_level = 20
+      #   public (optional)                 - if true same as setting visibility_level = 20
       #   visibility_level (optional)
       #   import_url (optional)
       #   public_builds (optional)
       #   lfs_enabled (optional)
+      #   request_access_enabled (optional) - Allow users to request member access
       # Example Request
       #   POST /projects/user/:user_id
       post "user/:user_id" do
         authenticated_as_admin!
         user = User.find(params[:user_id])
-        attrs = attributes_for_keys [:name,
-                                     :description,
+        attrs = attributes_for_keys [:builds_enabled,
                                      :default_branch,
+                                     :description,
+                                     :import_url,
                                      :issues_enabled,
+                                     :lfs_enabled,
                                      :merge_requests_enabled,
-                                     :builds_enabled,
-                                     :wiki_enabled,
-                                     :snippets_enabled,
-                                     :shared_runners_enabled,
+                                     :name,
+                                     :only_allow_merge_if_build_succeeds,
                                      :public,
-                                     :visibility_level,
-                                     :import_url,
                                      :public_builds,
-                                     :only_allow_merge_if_build_succeeds,
-                                     :lfs_enabled]
+                                     :request_access_enabled,
+                                     :shared_runners_enabled,
+                                     :snippets_enabled,
+                                     :visibility_level,
+                                     :wiki_enabled]
         attrs = map_public_to_visibility_level(attrs)
         @project = ::Projects::CreateService.new(user, attrs).execute
         if @project.saved?
@@ -242,22 +246,23 @@ module API
       # Example Request
       #   PUT /projects/:id
       put ':id' do
-        attrs = attributes_for_keys [:name,
-                                     :path,
-                                     :description,
+        attrs = attributes_for_keys [:builds_enabled,
+                                     :container_registry_enabled,
                                      :default_branch,
+                                     :description,
                                      :issues_enabled,
+                                     :lfs_enabled,
                                      :merge_requests_enabled,
-                                     :builds_enabled,
-                                     :wiki_enabled,
-                                     :snippets_enabled,
-                                     :container_registry_enabled,
-                                     :shared_runners_enabled,
+                                     :name,
+                                     :only_allow_merge_if_build_succeeds,
+                                     :path,
                                      :public,
-                                     :visibility_level,
                                      :public_builds,
-                                     :only_allow_merge_if_build_succeeds,
-                                     :lfs_enabled]
+                                     :request_access_enabled,
+                                     :shared_runners_enabled,
+                                     :snippets_enabled,
+                                     :visibility_level,
+                                     :wiki_enabled]
         attrs = map_public_to_visibility_level(attrs)
         authorize_admin_project
         authorize! :rename_project, user_project if attrs[:name].present?
@@ -428,18 +433,9 @@ module API
       # Example Request:
       #   GET /projects/search/:query
       get "/search/:query" do
-        ids = current_user.authorized_projects.map(&:id)
-        visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ]
-        projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%")
-        sort = params[:sort] == 'desc' ? 'desc' : 'asc'
-
-        projects = case params["order_by"]
-                   when 'id' then projects.order("id #{sort}")
-                   when 'name' then projects.order("name #{sort}")
-                   when 'created_at' then projects.order("created_at #{sort}")
-                   when 'last_activity_at' then projects.order("last_activity_at #{sort}")
-                   else projects
-                   end
+        search_service = Search::GlobalService.new(current_user, search: params[:query]).execute
+        projects = search_service.objects('projects', params[:page])
+        projects = projects.reorder(project_order_by => project_sort)
 
         present paginate(projects), with: Entities::Project
       end
diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb
index 2e2c8da311e433fccf970748061ce99366c1d028..e7a1ec8457de2e5c8f4337599f3a6ace1b26b48f 100644
--- a/lib/banzai/filter/wiki_link_filter/rewriter.rb
+++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb
@@ -31,6 +31,7 @@ module Banzai
         def apply_relative_link_rules!
           if @uri.relative? && @uri.path.present?
             link = ::File.join(@wiki_base_path, @uri.path)
+            link = "#{link}##{@uri.fragment}" if @uri.fragment
             @uri = Addressable::URI.parse(link)
           end
         end
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 9f3b582a263c0a9d5aa35f1570bf787778742184..59f85416ee563a296f0b56e86c1a590ea1d20109 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -12,7 +12,7 @@ module Ci
         #   POST /builds/register
         post "register" do
           authenticate_runner!
-          update_runner_last_contact
+          update_runner_last_contact(save: false)
           update_runner_info
           required_attributes! [:token]
           not_found! unless current_runner.active?
@@ -27,7 +27,7 @@ module Ci
           else
             Gitlab::Metrics.add_event(:build_not_found)
 
-            not_found!
+            build_not_found!
           end
         end
 
@@ -101,6 +101,7 @@ module Ci
         #   POST /builds/:id/artifacts/authorize
         post ":id/artifacts/authorize" do
           require_gitlab_workhorse!
+          Gitlab::Workhorse.verify_api_request!(headers)
           not_allowed! unless Gitlab.config.artifacts.enabled
           build = Ci::Build.find_by_id(params[:id])
           not_found! unless build
@@ -113,7 +114,8 @@ module Ci
           end
 
           status 200
-          { TempPath: ArtifactUploader.artifacts_upload_path }
+          content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+          Gitlab::Workhorse.artifact_upload_ok
         end
 
         # Upload artifacts to build - Runners only
diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb
index 3f5bdaba3f5d0f757e23411ce83c72282fe54d52..66c05773b68451bbd438855369d385702f89afa6 100644
--- a/lib/ci/api/entities.rb
+++ b/lib/ci/api/entities.rb
@@ -15,6 +15,15 @@ module Ci
         expose :filename, :size
       end
 
+      class BuildOptions < Grape::Entity
+        expose :image
+        expose :services
+        expose :artifacts
+        expose :cache
+        expose :dependencies
+        expose :after_script
+      end
+
       class Build < Grape::Entity
         expose :id, :ref, :tag, :sha, :status
         expose :name, :token, :stage
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 199d62d9b8a1c2f08f2182fc1d1444b117f11ae5..23353c6288572f3567e25e47552b27944bea5d56 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -3,7 +3,7 @@ module Ci
     module Helpers
       BUILD_TOKEN_HEADER = "HTTP_BUILD_TOKEN"
       BUILD_TOKEN_PARAM = :token
-      UPDATE_RUNNER_EVERY = 60
+      UPDATE_RUNNER_EVERY = 40 * 60
 
       def authenticate_runners!
         forbidden! unless runner_registration_token_valid?
@@ -14,19 +14,37 @@ module Ci
       end
 
       def authenticate_build_token!(build)
-        token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s
-        forbidden! unless token && build.valid_token?(token)
+        forbidden! unless build_token_valid?(build)
       end
 
       def runner_registration_token_valid?
-        params[:token] == current_application_settings.runners_registration_token
+        ActiveSupport::SecurityUtils.variable_size_secure_compare(
+          params[:token],
+          current_application_settings.runners_registration_token)
+      end
+
+      def build_token_valid?(build)
+        token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s
+
+        # We require to also check `runners_token` to maintain compatibility with old version of runners
+        token && (build.valid_token?(token) || build.project.valid_runners_token?(token))
       end
 
-      def update_runner_last_contact
+      def update_runner_last_contact(save: true)
         # Use a random threshold to prevent beating DB updates
+        # it generates a distribution between: [40m, 80m]
         contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY)
         if current_runner.contacted_at.nil? || Time.now - current_runner.contacted_at >= contacted_at_max_age
-          current_runner.update_attributes(contacted_at: Time.now)
+          current_runner.contacted_at = Time.now
+          current_runner.save if current_runner.changed? && save
+        end
+      end
+
+      def build_not_found!
+        if headers['User-Agent'].match(/gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /)
+          no_content!
+        else
+          not_found!
         end
       end
 
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 47efd5bd9f264eb3685d0f07e252bff49dedb8f5..0369e80312a0042d4aa537e3ed46ec06bc972cbc 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -55,29 +55,36 @@ module Ci
       {
         stage_idx: @stages.index(job[:stage]),
         stage: job[:stage],
-        ##
-        # Refactoring note:
-        #  - before script behaves differently than after script
-        #  - after script returns an array of commands
-        #  - before script should be a concatenated command
-        commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
+        commands: job[:commands],
         tag_list: job[:tags] || [],
         name: job[:name].to_s,
         allow_failure: job[:allow_failure] || false,
         when: job[:when] || 'on_success',
-        environment: job[:environment],
+        environment: job[:environment_name],
         yaml_variables: yaml_variables(name),
         options: {
-          image: job[:image] || @image,
-          services: job[:services] || @services,
+          image: job[:image],
+          services: job[:services],
           artifacts: job[:artifacts],
-          cache: job[:cache] || @cache,
+          cache: job[:cache],
           dependencies: job[:dependencies],
-          after_script: job[:after_script] || @after_script,
+          after_script: job[:after_script],
+          environment: job[:environment],
         }.compact
       }
     end
 
+    def self.validation_message(content)
+      return 'Please provide content of .gitlab-ci.yml' if content.blank?
+
+      begin
+        Ci::GitlabCiYamlProcessor.new(content)
+        nil
+      rescue ValidationError, Psych::SyntaxError => e
+        e.message
+      end
+    end
+
     private
 
     def initial_parsing
diff --git a/lib/ci/mask_secret.rb b/lib/ci/mask_secret.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3da04edde70bcbf3a441b660826b90b48a6a56ea
--- /dev/null
+++ b/lib/ci/mask_secret.rb
@@ -0,0 +1,9 @@
+module Ci::MaskSecret
+  class << self
+    def mask(value, token)
+      return value unless value.present? && token.present?
+
+      value.gsub(token, 'x' * token.length)
+    end
+  end
+end
diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7b1533d0d321e49c710dcdb0289552d2cd2b4516
--- /dev/null
+++ b/lib/expand_variables.rb
@@ -0,0 +1,17 @@
+module ExpandVariables
+  class << self
+    def expand(value, variables)
+      # Convert hash array to variables
+      if variables.is_a?(Array)
+        variables = variables.reduce({}) do |hash, variable|
+          hash[variable[:key]] = variable[:value]
+          hash
+        end
+      end
+
+      value.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
+        variables[$1 || $2]
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 91f0270818a3f2568dbcb2edcf9a810f74ab4f33..0a0f1c3b17b97dcc919e4b87b4c481ac56cfccbf 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,21 +1,21 @@
 module Gitlab
   module Auth
-    Result = Struct.new(:user, :type)
+    class MissingPersonalTokenError < StandardError; end
 
     class << self
       def find_for_git_client(login, password, project:, ip:)
         raise "Must provide an IP for rate limiting" if ip.nil?
 
-        result = Result.new
+        result =
+          service_request_check(login, password, project) ||
+          build_access_token_check(login, password) ||
+          user_with_password_for_git(login, password) ||
+          oauth_access_token_check(login, password) ||
+          personal_access_token_check(login, password) ||
+          Gitlab::Auth::Result.new
 
-        if valid_ci_request?(login, password, project)
-          result.type = :ci
-        else
-          result = populate_result(login, password)
-        end
+        rate_limit!(ip, success: result.success?, login: login)
 
-        success = result.user.present? || [:ci, :missing_personal_token].include?(result.type)
-        rate_limit!(ip, success: success, login: login)
         result
       end
 
@@ -57,44 +57,31 @@ module Gitlab
 
       private
 
-      def valid_ci_request?(login, password, project)
+      def service_request_check(login, password, project)
         matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login)
 
-        return false unless project && matched_login.present?
+        return unless project && matched_login.present?
 
         underscored_service = matched_login['service'].underscore
 
-        if underscored_service == 'gitlab_ci'
-          project && project.valid_build_token?(password)
-        elsif Service.available_services_names.include?(underscored_service)
+        if Service.available_services_names.include?(underscored_service)
           # We treat underscored_service as a trusted input because it is included
           # in the Service.available_services_names whitelist.
           service = project.public_send("#{underscored_service}_service")
 
-          service && service.activated? && service.valid_token?(password)
-        end
-      end
-
-      def populate_result(login, password)
-        result =
-          user_with_password_for_git(login, password) ||
-          oauth_access_token_check(login, password) ||
-          personal_access_token_check(login, password)
-
-        if result
-          result.type = nil unless result.user
-
-          if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap
-            result.type = :missing_personal_token
+          if service && service.activated? && service.valid_token?(password)
+            Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities)
           end
         end
-
-        result || Result.new
       end
 
       def user_with_password_for_git(login, password)
         user = find_with_user_password(login, password)
-        Result.new(user, :gitlab_or_ldap) if user
+        return unless user
+
+        raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
+
+        Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
       end
 
       def oauth_access_token_check(login, password)
@@ -102,7 +89,7 @@ module Gitlab
           token = Doorkeeper::AccessToken.by_token(password)
           if token && token.accessible?
             user = User.find_by(id: token.resource_owner_id)
-            Result.new(user, :oauth)
+            Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities)
           end
         end
       end
@@ -111,9 +98,52 @@ module Gitlab
         if login && password
           user = User.find_by_personal_access_token(password)
           validation = User.by_login(login)
-          Result.new(user, :personal_token) if user == validation
+          Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities) if user.present? && user == validation
+        end
+      end
+
+      def build_access_token_check(login, password)
+        return unless login == 'gitlab-ci-token'
+        return unless password
+
+        build = ::Ci::Build.running.find_by_token(password)
+        return unless build
+        return unless build.project.builds_enabled?
+
+        if build.user
+          # If user is assigned to build, use restricted credentials of user
+          Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
+        else
+          # Otherwise use generic CI credentials (backward compatibility)
+          Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities)
         end
       end
+
+      public
+
+      def build_authentication_abilities
+        [
+          :read_project,
+          :build_download_code,
+          :build_read_container_image,
+          :build_create_container_image
+        ]
+      end
+
+      def read_authentication_abilities
+        [
+          :read_project,
+          :download_code,
+          :read_container_image
+        ]
+      end
+
+      def full_authentication_abilities
+        read_authentication_abilities + [
+          :push_code,
+          :create_container_image
+        ]
+      end
     end
   end
 end
diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bf625649cbf440d52bf6c9f79d5795a423262b2d
--- /dev/null
+++ b/lib/gitlab/auth/result.rb
@@ -0,0 +1,13 @@
+module Gitlab
+  module Auth
+    Result = Struct.new(:actor, :project, :type, :authentication_abilities) do
+      def ci?
+        type == :ci
+      end
+
+      def success?
+        actor.present? || type == :ci
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 839a4fa30d5de358fc17b7f1ac53ec712a424f02..79eac66b364e094c78720811b23fd9c65823259e 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -6,7 +6,12 @@ module Gitlab
 
     KeyAdder = Struct.new(:io) do
       def add_key(id, key)
-        key.gsub!(/[[:space:]]+/, ' ').strip!
+        key = Gitlab::Shell.strip_key(key)
+        # Newline and tab are part of the 'protocol' used to transmit id+key to the other end
+        if key.include?("\t") || key.include?("\n")
+          raise Error.new("Invalid key: #{key.inspect}")
+        end
+
         io.puts("#{id}\t#{key}")
       end
     end
@@ -16,6 +21,10 @@ module Gitlab
         @version_required ||= File.read(Rails.root.
                                         join('GITLAB_SHELL_VERSION')).strip
       end
+
+      def strip_key(key)
+        key.split(/ /)[0, 2].join(' ')
+      end
     end
 
     # Init new repository
@@ -107,7 +116,7 @@ module Gitlab
     #
     def add_key(key_id, key_content)
       Gitlab::Utils.system_silent([gitlab_shell_keys_path,
-                                   'add-key', key_id, key_content])
+                                   'add-key', key_id, self.class.strip_key(key_content)])
     end
 
     # Batch-add keys to authorized_keys
@@ -195,7 +204,7 @@ module Gitlab
     # Create (if necessary) and link the secret token file
     def generate_and_link_secret_token
       secret_file = Gitlab.config.gitlab_shell.secret_file
-      unless File.exist? secret_file
+      unless File.size?(secret_file)
         # Generate a new token of 16 random hexadecimal characters and store it in secret_file.
         token = SecureRandom.hex(16)
         File.write(secret_file, token)
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 7beaecd1cf065637cf09a672e8ac843bce4bfbae..f4b5097adb1f4c18d915ed781378c3ff157d22f3 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -21,7 +21,7 @@ module Gitlab
 
       private
 
-      def gl_user_id(project, bitbucket_id)
+      def gitlab_user_id(project, bitbucket_id)
         if bitbucket_id
           user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
           (user && user.id) || project.creator_id
@@ -74,7 +74,7 @@ module Gitlab
             description: body,
             title: issue["title"],
             state: %w(resolved invalid duplicate wontfix closed).include?(issue["status"]) ? 'closed' : 'opened',
-            author_id: gl_user_id(project, reporter)
+            author_id: gitlab_user_id(project, reporter)
           )
         end
       rescue ActiveRecord::RecordInvalid => e
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 4b32eb966aa050c48c3afe1b6f83a276f0f372ad..cb1065223d4d65b9de3602a5dbc8df63a977137f 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -23,6 +23,7 @@ module Gitlab
       protected
 
       def protected_branch_checks
+        return unless @branch_name
         return unless project.protected_branch?(@branch_name)
 
         if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches)
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index ae82c0db3f1ce40f0bc4de001d4ce0e42e5e8d5c..bbfa6cf7d05ab66d2061315f1667ef7e978007bf 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -14,7 +14,7 @@ module Gitlab
         @config = Loader.new(config).load!
 
         @global = Node::Global.new(@config)
-        @global.process!
+        @global.compose!
       end
 
       def valid?
diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb
index 2de82d40c9dad753c8e1f4c850ec3b0255d4ab39..6b7ab2fdaf266b8b3497414c8ff5df6d2cb31aa6 100644
--- a/lib/gitlab/ci/config/node/configurable.rb
+++ b/lib/gitlab/ci/config/node/configurable.rb
@@ -23,9 +23,9 @@ module Gitlab
             end
           end
 
-          private
+          def compose!(deps = nil)
+            return unless valid?
 
-          def compose!
             self.class.nodes.each do |key, factory|
               factory
                 .value(@config[key])
@@ -33,6 +33,12 @@ module Gitlab
 
               @entries[key] = factory.create!
             end
+
+            yield if block_given?
+
+            @entries.each_value do |entry|
+              entry.compose!(deps)
+            end
           end
 
           class_methods do
diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb
index 0c782c422b583d706b4ab33a9764bc2969ca1ef5..8717eabf81eb28b7c2b42ecf542c37aa6f9491ed 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -20,11 +20,14 @@ module Gitlab
             @validator.validate(:new)
           end
 
-          def process!
+          def [](key)
+            @entries[key] || Node::Undefined.new
+          end
+
+          def compose!(deps = nil)
             return unless valid?
 
-            compose!
-            descendants.each(&:process!)
+            yield if block_given?
           end
 
           def leaf?
@@ -73,11 +76,6 @@ module Gitlab
           def self.validator
             Validator
           end
-
-          private
-
-          def compose!
-          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d388ab6b87908e52fcf5a6441c8629534cf6c08d
--- /dev/null
+++ b/lib/gitlab/ci/config/node/environment.rb
@@ -0,0 +1,68 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents an environment.
+        #
+        class Environment < Entry
+          include Validatable
+
+          ALLOWED_KEYS = %i[name url]
+
+          validations do
+            validate do
+              unless hash? || string?
+                errors.add(:config, 'should be a hash or a string')
+              end
+            end
+
+            validates :name, presence: true
+            validates :name,
+              type: {
+                with: String,
+                message: Gitlab::Regex.environment_name_regex_message }
+
+            validates :name,
+              format: {
+                with: Gitlab::Regex.environment_name_regex,
+                message: Gitlab::Regex.environment_name_regex_message }
+
+            with_options if: :hash? do
+              validates :config, allowed_keys: ALLOWED_KEYS
+
+              validates :url,
+                        length: { maximum: 255 },
+                        addressable_url: true,
+                        allow_nil: true
+            end
+          end
+
+          def hash?
+            @config.is_a?(Hash)
+          end
+
+          def string?
+            @config.is_a?(String)
+          end
+
+          def name
+            value[:name]
+          end
+
+          def url
+            value[:url]
+          end
+
+          def value
+            case @config
+            when String then { name: @config }
+            when Hash then @config
+            else {}
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb
index 707b052e6a8f15f8b15cbd63e0c18235ad9b7428..5387f29ad5946aa8f90cb1703caa2b9209cadffd 100644
--- a/lib/gitlab/ci/config/node/factory.rb
+++ b/lib/gitlab/ci/config/node/factory.rb
@@ -37,8 +37,8 @@ module Gitlab
             # See issue #18775.
             #
             if @value.nil?
-              Node::Undefined.new(
-                fabricate_undefined
+              Node::Unspecified.new(
+                fabricate_unspecified
               )
             else
               fabricate(@node, @value)
@@ -47,13 +47,13 @@ module Gitlab
 
           private
 
-          def fabricate_undefined
+          def fabricate_unspecified
             ##
             # If node has a default value we fabricate concrete node
             # with default value.
             #
             if @node.default.nil?
-              fabricate(Node::Null)
+              fabricate(Node::Undefined)
             else
               fabricate(@node, @node.default)
             end
diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb
index ccd539fb0037cb43d2d39f70e2f5dd357ae19334..2a2943c92886ce205053e7096a356331b938518b 100644
--- a/lib/gitlab/ci/config/node/global.rb
+++ b/lib/gitlab/ci/config/node/global.rb
@@ -36,15 +36,15 @@ module Gitlab
           helpers :before_script, :image, :services, :after_script,
                   :variables, :stages, :types, :cache, :jobs
 
-          private
-
-          def compose!
-            super
-
-            compose_jobs!
-            compose_deprecated_entries!
+          def compose!(_deps = nil)
+            super(self) do
+              compose_jobs!
+              compose_deprecated_entries!
+            end
           end
 
+          private
+
           def compose_jobs!
             factory = Node::Factory.new(Node::Jobs)
               .value(@config.except(*self.class.nodes.keys))
diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb
index e84737acbb98e2f70d96a6895bb9a908949e9e1a..603334d6793562c12866c4fceb627bc8b2c7aeab 100644
--- a/lib/gitlab/ci/config/node/job.rb
+++ b/lib/gitlab/ci/config/node/job.rb
@@ -13,7 +13,7 @@ module Gitlab
                             type stage when artifacts cache dependencies before_script
                             after_script variables environment]
 
-          attributes :tags, :allow_failure, :when, :environment, :dependencies
+          attributes :tags, :allow_failure, :when, :dependencies
 
           validations do
             validates :config, allowed_keys: ALLOWED_KEYS
@@ -29,58 +29,65 @@ module Gitlab
                 inclusion: { in: %w[on_success on_failure always manual],
                              message: 'should be on_success, on_failure, ' \
                                       'always or manual' }
-              validates :environment,
-                type: {
-                  with: String,
-                  message: Gitlab::Regex.environment_name_regex_message }
-              validates :environment,
-                format: {
-                  with: Gitlab::Regex.environment_name_regex,
-                  message: Gitlab::Regex.environment_name_regex_message }
 
               validates :dependencies, array_of_strings: true
             end
           end
 
-          node :before_script, Script,
+          node :before_script, Node::Script,
             description: 'Global before script overridden in this job.'
 
-          node :script, Commands,
+          node :script, Node::Commands,
             description: 'Commands that will be executed in this job.'
 
-          node :stage, Stage,
+          node :stage, Node::Stage,
             description: 'Pipeline stage this job will be executed into.'
 
-          node :type, Stage,
+          node :type, Node::Stage,
             description: 'Deprecated: stage this job will be executed into.'
 
-          node :after_script, Script,
+          node :after_script, Node::Script,
             description: 'Commands that will be executed when finishing job.'
 
-          node :cache, Cache,
+          node :cache, Node::Cache,
             description: 'Cache definition for this job.'
 
-          node :image, Image,
+          node :image, Node::Image,
             description: 'Image that will be used to execute this job.'
 
-          node :services, Services,
+          node :services, Node::Services,
             description: 'Services that will be used to execute this job.'
 
-          node :only, Trigger,
+          node :only, Node::Trigger,
             description: 'Refs policy this job will be executed for.'
 
-          node :except, Trigger,
+          node :except, Node::Trigger,
             description: 'Refs policy this job will be executed for.'
 
-          node :variables, Variables,
+          node :variables, Node::Variables,
             description: 'Environment variables available for this job.'
 
-          node :artifacts, Artifacts,
+          node :artifacts, Node::Artifacts,
             description: 'Artifacts configuration for this job.'
 
+          node :environment, Node::Environment,
+               description: 'Environment configuration for this job.'
+
           helpers :before_script, :script, :stage, :type, :after_script,
                   :cache, :image, :services, :only, :except, :variables,
-                  :artifacts
+                  :artifacts, :commands, :environment
+
+          def compose!(deps = nil)
+            super do
+              if type_defined? && !stage_defined?
+                @entries[:stage] = @entries[:type]
+              end
+
+              @entries.delete(:type)
+            end
+
+            inherit!(deps)
+          end
 
           def name
             @metadata[:name]
@@ -90,12 +97,30 @@ module Gitlab
             @config.merge(to_hash.compact)
           end
 
+          def commands
+            (before_script_value.to_a + script_value.to_a).join("\n")
+          end
+
           private
 
+          def inherit!(deps)
+            return unless deps
+
+            self.class.nodes.each_key do |key|
+              global_entry = deps[key]
+              job_entry = @entries[key]
+
+              if global_entry.specified? && !job_entry.specified?
+                @entries[key] = global_entry
+              end
+            end
+          end
+
           def to_hash
             { name: name,
               before_script: before_script,
               script: script,
+              commands: commands,
               image: image,
               services: services,
               stage: stage,
@@ -103,19 +128,11 @@ module Gitlab
               only: only,
               except: except,
               variables: variables_defined? ? variables : nil,
+              environment: environment_defined? ? environment : nil,
+              environment_name: environment_defined? ? environment[:name] : nil,
               artifacts: artifacts,
               after_script: after_script }
           end
-
-          def compose!
-            super
-
-            if type_defined? && !stage_defined?
-              @entries[:stage] = @entries[:type]
-            end
-
-            @entries.delete(:type)
-          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb
index a1a26d4fd8f3b9baa546952b01b845d6d64382cd..d10e80d1a7d3482fa613000fba75217bfe11e5e4 100644
--- a/lib/gitlab/ci/config/node/jobs.rb
+++ b/lib/gitlab/ci/config/node/jobs.rb
@@ -26,19 +26,23 @@ module Gitlab
             name.to_s.start_with?('.')
           end
 
-          private
-
-          def compose!
-            @config.each do |name, config|
-              node = hidden?(name) ? Node::Hidden : Node::Job
-
-              factory = Node::Factory.new(node)
-                .value(config || {})
-                .metadata(name: name)
-                .with(key: name, parent: self,
-                      description: "#{name} job definition.")
+          def compose!(deps = nil)
+            super do
+              @config.each do |name, config|
+                node = hidden?(name) ? Node::Hidden : Node::Job
+
+                factory = Node::Factory.new(node)
+                  .value(config || {})
+                  .metadata(name: name)
+                  .with(key: name, parent: self,
+                        description: "#{name} job definition.")
+
+                @entries[name] = factory.create!
+              end
 
-              @entries[name] = factory.create!
+              @entries.each_value do |entry|
+                entry.compose!(deps)
+              end
             end
           end
         end
diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb
deleted file mode 100644
index 88a5f53f13c1a046e8754acad7b4052f992b40cc..0000000000000000000000000000000000000000
--- a/lib/gitlab/ci/config/node/null.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Gitlab
-  module Ci
-    class Config
-      module Node
-        ##
-        # This class represents an undefined node.
-        #
-        # Implements the Null Object pattern.
-        #
-        class Null < Entry
-          def value
-            nil
-          end
-
-          def valid?
-            true
-          end
-
-          def errors
-            []
-          end
-
-          def specified?
-            false
-          end
-
-          def relevant?
-            false
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb
index 45fef8c3ae55204c3d0a5a87b92de655e9d4cebe..33e78023539d0270d38e27bf79b72d2a54a4e539 100644
--- a/lib/gitlab/ci/config/node/undefined.rb
+++ b/lib/gitlab/ci/config/node/undefined.rb
@@ -3,15 +3,34 @@ module Gitlab
     class Config
       module Node
         ##
-        # This class represents an unspecified entry node.
+        # This class represents an undefined node.
         #
-        # It decorates original entry adding method that indicates it is
-        # unspecified.
+        # Implements the Null Object pattern.
         #
-        class Undefined < SimpleDelegator
+        class Undefined < Entry
+          def initialize(*)
+            super(nil)
+          end
+
+          def value
+            nil
+          end
+
+          def valid?
+            true
+          end
+
+          def errors
+            []
+          end
+
           def specified?
             false
           end
+
+          def relevant?
+            false
+          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/unspecified.rb b/lib/gitlab/ci/config/node/unspecified.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a7d1f6131b8722d5228723eec21ecf9194e9bb79
--- /dev/null
+++ b/lib/gitlab/ci/config/node/unspecified.rb
@@ -0,0 +1,19 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # This class represents an unspecified entry node.
+        #
+        # It decorates original entry adding method that indicates it is
+        # unspecified.
+        #
+        class Unspecified < SimpleDelegator
+          def specified?
+            false
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a210e76acaa41baa7658853232b208149b14b311
--- /dev/null
+++ b/lib/gitlab/ci/pipeline_duration.rb
@@ -0,0 +1,141 @@
+module Gitlab
+  module Ci
+    # # Introduction - total running time
+    #
+    # The problem this module is trying to solve is finding the total running
+    # time amongst all the jobs, excluding retries and pending (queue) time.
+    # We could reduce this problem down to finding the union of periods.
+    #
+    # So each job would be represented as a `Period`, which consists of
+    # `Period#first` as when the job started and `Period#last` as when the
+    # job was finished. A simple example here would be:
+    #
+    # * A (1, 3)
+    # * B (2, 4)
+    # * C (6, 7)
+    #
+    # Here A begins from 1, and ends to 3. B begins from 2, and ends to 4.
+    # C begins from 6, and ends to 7. Visually it could be viewed as:
+    #
+    #     0  1  2  3  4  5  6  7
+    #        AAAAAAA
+    #           BBBBBBB
+    #                       CCCC
+    #
+    # The union of A, B, and C would be (1, 4) and (6, 7), therefore the
+    # total running time should be:
+    #
+    #     (4 - 1) + (7 - 6) => 4
+    #
+    # # The Algorithm
+    #
+    # The algorithm used here for union would be described as follow.
+    # First we make sure that all periods are sorted by `Period#first`.
+    # Then we try to merge periods by iterating through the first period
+    # to the last period. The goal would be merging all overlapped periods
+    # so that in the end all the periods are discrete. When all periods
+    # are discrete, we're free to just sum all the periods to get real
+    # running time.
+    #
+    # Here we begin from A, and compare it to B. We could find that
+    # before A ends, B already started. That is `B.first <= A.last`
+    # that is `2 <= 3` which means A and B are overlapping!
+    #
+    # When we found that two periods are overlapping, we would need to merge
+    # them into a new period and disregard the old periods. To make a new
+    # period, we take `A.first` as the new first because remember? we sorted
+    # them, so `A.first` must be smaller or equal to `B.first`. And we take
+    # `[A.last, B.last].max` as the new last because we want whoever ended
+    # later. This could be broken into two cases:
+    #
+    #     0  1  2  3  4
+    #        AAAAAAA
+    #           BBBBBBB
+    #
+    # Or:
+    #
+    #     0  1  2  3  4
+    #        AAAAAAAAAA
+    #           BBBB
+    #
+    # So that we need to take whoever ends later. Back to our example,
+    # after merging and discard A and B it could be visually viewed as:
+    #
+    #     0  1  2  3  4  5  6  7
+    #        DDDDDDDDDD
+    #                       CCCC
+    #
+    # Now we could go on and compare the newly created D and the old C.
+    # We could figure out that D and C are not overlapping by checking
+    # `C.first <= D.last` is `false`. Therefore we need to keep both C
+    # and D. The example would end here because there are no more jobs.
+    #
+    # After having the union of all periods, we just need to sum the length
+    # of all periods to get total time.
+    #
+    #     (4 - 1) + (7 - 6) => 4
+    #
+    # That is 4 is the answer in the example.
+    module PipelineDuration
+      extend self
+
+      Period = Struct.new(:first, :last) do
+        def duration
+          last - first
+        end
+      end
+
+      def from_pipeline(pipeline)
+        status = %w[success failed running canceled]
+        builds = pipeline.builds.latest.
+          where(status: status).where.not(started_at: nil).order(:started_at)
+
+        from_builds(builds)
+      end
+
+      def from_builds(builds)
+        now = Time.now
+
+        periods = builds.map do |b|
+          Period.new(b.started_at, b.finished_at || now)
+        end
+
+        from_periods(periods)
+      end
+
+      # periods should be sorted by `first`
+      def from_periods(periods)
+        process_duration(process_periods(periods))
+      end
+
+      private
+
+      def process_periods(periods)
+        return periods if periods.empty?
+
+        periods.drop(1).inject([periods.first]) do |result, current|
+          previous = result.last
+
+          if overlap?(previous, current)
+            result[-1] = merge(previous, current)
+            result
+          else
+            result << current
+          end
+        end
+      end
+
+      def overlap?(previous, current)
+        current.first <= previous.last
+      end
+
+      def merge(previous, current)
+        Period.new(previous.first, [previous.last, current.last].max)
+      end
+
+      def process_duration(periods)
+        periods.sum(&:duration)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb
index 2d4d55daeeb998be824eed7385e62ac7b29f36fd..98e842cded36d2db71b0ef8dc1ce1a78aab8a568 100644
--- a/lib/gitlab/conflict/parser.rb
+++ b/lib/gitlab/conflict/parser.rb
@@ -18,7 +18,7 @@ module Gitlab
 
       def parse(text, our_path:, their_path:, parent_file: nil)
         raise UnmergeableFile if text.blank? # Typically a binary file
-        raise UnmergeableFile if text.length > 102400
+        raise UnmergeableFile if text.length > 200.kilobytes
 
         begin
           text.to_json
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index bd681f03173a41b71c0b552543371a91e8828cad..b164f5a2eeaa28a49929cd98ffbf09aafe3a3639 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -1,16 +1,16 @@
 module Gitlab
   class ContributionsCalendar
-    attr_reader :timestamps, :projects, :user
+    attr_reader :activity_dates, :projects, :user
 
     def initialize(projects, user)
       @projects = projects
       @user = user
     end
 
-    def timestamps
-      return @timestamps if @timestamps.present?
+    def activity_dates
+      return @activity_dates if @activity_dates.present?
 
-      @timestamps = {}
+      @activity_dates = {}
       date_from = 1.year.ago
 
       events = Event.reorder(nil).contributions.where(author_id: user.id).
@@ -19,18 +19,17 @@ module Gitlab
         select('date(created_at) as date, count(id) as total_amount').
         map(&:attributes)
 
-      dates = (1.year.ago.to_date..Date.today).to_a
+      activity_dates = (1.year.ago.to_date..Date.today).to_a
 
-      dates.each do |date|
-        date_id = date.to_time.to_i.to_s
+      activity_dates.each do |date|
         day_events = events.find { |day_events| day_events["date"] == date }
 
         if day_events
-          @timestamps[date_id] = day_events["total_amount"]
+          @activity_dates[date] = day_events["total_amount"]
         end
       end
 
-      @timestamps
+      @activity_dates
     end
 
     def events_by_date(date)
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 927f9dad20bee1c16f1d0104c1a395f7dc4a3b5a..0bd6e148ba8d5419bde0040c2b345d836f5ca11b 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -129,12 +129,14 @@ module Gitlab
       # column - The name of the column to add.
       # type - The column type (e.g. `:integer`).
       # default - The default value for the column.
+      # limit - Sets a column limit. For example, for :integer, the default is
+      #         4-bytes. Set `limit: 8` to allow 8-byte integers.
       # allow_null - When set to `true` the column will allow NULL values, the
       #              default is to not allow NULL values.
       #
       # This method can also take a block which is passed directly to the
       # `update_column_in_batches` method.
-      def add_column_with_default(table, column, type, default:, allow_null: false, &block)
+      def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false, &block)
         if transaction_open?
           raise 'add_column_with_default can not be run inside a transaction, ' \
             'you can disable transactions by calling disable_ddl_transaction! ' \
@@ -144,7 +146,11 @@ module Gitlab
         disable_statement_timeout
 
         transaction do
-          add_column(table, column, type, default: nil)
+          if limit
+            add_column(table, column, type, default: nil, limit: limit)
+          else
+            add_column(table, column, type, default: nil)
+          end
 
           # Changing the default before the update ensures any newly inserted
           # rows already use the proper default value.
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index 9b681e636c7fcc68171e4624a4b2f49d7d64639c..bd90d24a2ecd333477cf96e530215a935f562f39 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -17,11 +17,13 @@ module Gitlab
       def trigger(gl_id, oldrev, newrev, ref)
         return [true, nil] unless exists?
 
-        case name
-        when "pre-receive", "post-receive"
-          call_receive_hook(gl_id, oldrev, newrev, ref)
-        when "update"
-          call_update_hook(gl_id, oldrev, newrev, ref)
+        Bundler.with_clean_env do
+          case name
+          when "pre-receive", "post-receive"
+            call_receive_hook(gl_id, oldrev, newrev, ref)
+          when "update"
+            call_update_hook(gl_id, oldrev, newrev, ref)
+          end
         end
       end
 
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 1882eb8d0508c34b98096a99ea3553682c6df963..799794c0171e104596931d87bd175ebc9d7f38d7 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -5,12 +5,13 @@ module Gitlab
     DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
     PUSH_COMMANDS = %w{ git-receive-pack }
 
-    attr_reader :actor, :project, :protocol, :user_access
+    attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities
 
-    def initialize(actor, project, protocol)
+    def initialize(actor, project, protocol, authentication_abilities:)
       @actor    = actor
       @project  = project
       @protocol = protocol
+      @authentication_abilities = authentication_abilities
       @user_access = UserAccess.new(user, project: project)
     end
 
@@ -60,14 +61,26 @@ module Gitlab
     end
 
     def user_download_access_check
-      unless user_access.can_do_action?(:download_code)
+      unless user_can_download_code? || build_can_download_code?
         return build_status_object(false, "You are not allowed to download code from this project.")
       end
 
       build_status_object(true)
     end
 
+    def user_can_download_code?
+      authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
+    end
+
+    def build_can_download_code?
+      authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
+    end
+
     def user_push_access_check(changes)
+      unless authentication_abilities.include?(:push_code)
+        return build_status_object(false, "You are not allowed to upload code for this project.")
+      end
+
       if changes.blank?
         return build_status_object(true)
       end
diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb
index 72992baffd40df8abbbce6dc8512269d2e264772..8cacf4f49252999208bc76a524f26b80264c94d6 100644
--- a/lib/gitlab/github_import/base_formatter.rb
+++ b/lib/gitlab/github_import/base_formatter.rb
@@ -15,11 +15,16 @@ module Gitlab
 
       private
 
-      def gl_user_id(github_id)
+      def gitlab_user_id(github_id)
         User.joins(:identities).
           find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s).
           try(:id)
       end
+
+      def gitlab_author_id
+        return @gitlab_author_id if defined?(@gitlab_author_id)
+        @gitlab_author_id = gitlab_user_id(raw_data.user.id)
+      end
     end
   end
 end
diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb
index 2c1b94ef2cd781c1288d48917169994c1a82dfd6..2bddcde2b7cc3cf706f9a0ab7b5385eada19ae19 100644
--- a/lib/gitlab/github_import/comment_formatter.rb
+++ b/lib/gitlab/github_import/comment_formatter.rb
@@ -21,7 +21,7 @@ module Gitlab
       end
 
       def author_id
-        gl_user_id(raw_data.user.id) || project.creator_id
+        gitlab_author_id || project.creator_id
       end
 
       def body
@@ -52,7 +52,11 @@ module Gitlab
       end
 
       def note
-        formatter.author_line(author) + body
+        if gitlab_author_id
+          body
+        else
+          formatter.author_line(author) + body
+        end
       end
 
       def type
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 4fdc2f46be094aec6d6486d11222c8ec25c27025..d35ee2a1c65c2d040712148fe4adc5a1a3c6217c 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -24,6 +24,7 @@ module Gitlab
         import_issues
         import_pull_requests
         import_wiki
+        import_releases
         handle_errors
 
         true
@@ -133,8 +134,7 @@ module Gitlab
 
         if issue.labels.count > 0
           label_ids = issue.labels
-            .map { |raw| LabelFormatter.new(project, raw).attributes }
-            .map { |attrs| Label.find_by(attrs).try(:id) }
+            .map { |attrs| project.labels.find_by(title: attrs.name).try(:id) }
             .compact
 
           issuable.update_attribute(:label_ids, label_ids)
@@ -178,6 +178,18 @@ module Gitlab
           errors << { type: :wiki, errors: e.message }
         end
       end
+
+      def import_releases
+        releases = client.releases(repo, per_page: 100)
+        releases.each do |raw|
+          begin
+            gh_release = ReleaseFormatter.new(project, raw)
+            gh_release.create! if gh_release.valid?
+          rescue => e
+            errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb
index 07edbe37a1399a9d507d7bb870f3eec16e112913..77621de9f4c895cf1ce3669026c0d87e0412dff7 100644
--- a/lib/gitlab/github_import/issue_formatter.rb
+++ b/lib/gitlab/github_import/issue_formatter.rb
@@ -40,7 +40,7 @@ module Gitlab
 
       def assignee_id
         if assigned?
-          gl_user_id(raw_data.assignee.id)
+          gitlab_user_id(raw_data.assignee.id)
         end
       end
 
@@ -49,7 +49,7 @@ module Gitlab
       end
 
       def author_id
-        gl_user_id(raw_data.user.id) || project.creator_id
+        gitlab_author_id || project.creator_id
       end
 
       def body
@@ -57,7 +57,11 @@ module Gitlab
       end
 
       def description
-        @formatter.author_line(author) + body
+        if gitlab_author_id
+          body
+        else
+          formatter.author_line(author) + body
+        end
       end
 
       def milestone
diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb
index 9f18244e7d7a67ade7b56edcf184e8ea31d38ec8..2cad7fca88e429117c53ee74631fc37c094c001c 100644
--- a/lib/gitlab/github_import/label_formatter.rb
+++ b/lib/gitlab/github_import/label_formatter.rb
@@ -13,6 +13,12 @@ module Gitlab
         Label
       end
 
+      def create!
+        project.labels.find_or_create_by!(title: title) do |label|
+          label.color = color
+        end
+      end
+
       private
 
       def color
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index d9d436d749059267c6b62f59c49a96d0137fa35d..1408683100fee10fd07876ed1b804b4239b8ea32 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -68,7 +68,7 @@ module Gitlab
 
       def assignee_id
         if assigned?
-          gl_user_id(raw_data.assignee.id)
+          gitlab_user_id(raw_data.assignee.id)
         end
       end
 
@@ -77,7 +77,7 @@ module Gitlab
       end
 
       def author_id
-        gl_user_id(raw_data.user.id) || project.creator_id
+        gitlab_author_id || project.creator_id
       end
 
       def body
@@ -85,7 +85,11 @@ module Gitlab
       end
 
       def description
-        formatter.author_line(author) + body
+        if gitlab_author_id
+          body
+        else
+          formatter.author_line(author) + body
+        end
       end
 
       def milestone
diff --git a/lib/gitlab/github_import/release_formatter.rb b/lib/gitlab/github_import/release_formatter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..73d643b00ad67cb5f3765f184275787ee9e9cbdc
--- /dev/null
+++ b/lib/gitlab/github_import/release_formatter.rb
@@ -0,0 +1,23 @@
+module Gitlab
+  module GithubImport
+    class ReleaseFormatter < BaseFormatter
+      def attributes
+        {
+          project: project,
+          tag: raw_data.tag_name,
+          description: raw_data.body,
+          created_at: raw_data.created_at,
+          updated_at: raw_data.created_at
+        }
+      end
+
+      def klass
+        Release
+      end
+
+      def valid?
+        !raw_data.draft
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
index 46d40f75be6b93fa503ec47e9b4253dd555c87ed..e44d7934fda6f0603b25ab557637e5b120b49e93 100644
--- a/lib/gitlab/gitlab_import/importer.rb
+++ b/lib/gitlab/gitlab_import/importer.rb
@@ -41,7 +41,8 @@ module Gitlab
               title: issue["title"],
               state: issue["state"],
               updated_at: issue["updated_at"],
-              author_id: gl_user_id(project, issue["author"]["id"])
+              author_id: gitlab_user_id(project, issue["author"]["id"]),
+              confidential: issue["confidential"]
             )
           end
         end
@@ -51,7 +52,7 @@ module Gitlab
 
       private
 
-      def gl_user_id(project, gitlab_id)
+      def gitlab_user_id(project, gitlab_id)
         user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'gitlab'", gitlab_id.to_s)
         (user && user.id) || project.creator_id
       end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index bb562bdcd2c0fb8df35d3b3a98c4d18058c1e27a..181e288a01404f2d872d3f589faa9464f928ab54 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -2,7 +2,8 @@ module Gitlab
   module ImportExport
     extend self
 
-    VERSION = '0.1.3'
+    # For every version update, the version history in import_export.md has to be kept up to date.
+    VERSION = '0.1.4'
     FILENAME_LIMIT = 50
 
     def export_path(relative_path:)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index c2e8a1ca5dd2e289e58392f143c90b7ea8472243..925a952156ff151a7cb4a833b55c502de3bf0379 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -35,7 +35,9 @@ project_tree:
   - :deploy_keys
   - :services
   - :hooks
-  - :protected_branches
+  - protected_branches:
+    - :merge_access_levels
+    - :push_access_levels
   - :labels
   - milestones:
     - :events
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index b0726268ca617ce6f6807eeb5afef80d38f8da10..354ccd64696e4eded28c97ee7a3275600546e037 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -7,7 +7,9 @@ module Gitlab
                     variables: 'Ci::Variable',
                     triggers: 'Ci::Trigger',
                     builds: 'Ci::Build',
-                    hooks: 'ProjectHook' }.freeze
+                    hooks: 'ProjectHook',
+                    merge_access_levels: 'ProtectedBranch::MergeAccessLevel',
+                    push_access_levels: 'ProtectedBranch::PushAccessLevel' }.freeze
 
       USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze
 
@@ -17,6 +19,8 @@ module Gitlab
 
       EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze
 
+      FINDER_ATTRIBUTES = %w[title project_id].freeze
+
       def self.create(*args)
         new(*args).create
       end
@@ -149,7 +153,7 @@ module Gitlab
       end
 
       def parsed_relation_hash
-        @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
+        @parsed_relation_hash ||= @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
       end
 
       def set_st_diffs
@@ -161,14 +165,30 @@ module Gitlab
         # Otherwise always create the record, skipping the extra SELECT clause.
         @existing_or_new_object ||= begin
           if EXISTING_OBJECT_CHECK.include?(@relation_name)
-            existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id'))
-            existing_object.assign_attributes(parsed_relation_hash)
+            events = parsed_relation_hash.delete('events')
+
+            unless events.blank?
+              existing_object.assign_attributes(events: events)
+            end
+
             existing_object
           else
             relation_class.new(parsed_relation_hash)
           end
         end
       end
+
+      def existing_object
+        @existing_object ||=
+          begin
+            finder_hash = parsed_relation_hash.slice(*FINDER_ATTRIBUTES)
+            existing_object = relation_class.find_or_create_by(finder_hash)
+            # Done in two steps, as MySQL behaves differently than PostgreSQL using
+            # the +find_or_create_by+ method and does not return the ID the second time.
+            existing_object.update(parsed_relation_hash)
+            existing_object
+          end
+      end
     end
   end
 end
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index 6d9379acf25869458600edb6d4daf552a22fbd93..d1e33ea86784f0960796d6a67774000619ae5d0f 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -22,10 +22,6 @@ module Gitlab
 
       private
 
-      def repos_path
-        Gitlab.config.gitlab_shell.repos_path
-      end
-
       def path_to_repo
         @project.repository.path_to_repo
       end
diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb
index de3fe6d822e0273ac15d4295be47245365247635..fc08082fc862cf59ce36037daec1dc3c3ea6b210 100644
--- a/lib/gitlab/import_export/version_checker.rb
+++ b/lib/gitlab/import_export/version_checker.rb
@@ -24,8 +24,8 @@ module Gitlab
       end
 
       def verify_version!(version)
-        if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version)
-          raise Gitlab::ImportExport::Error.new("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
+        if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
+          raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
         else
           true
         end
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index 9a5bcfb5c9bab9f8df436219f30f8a95a58b903a..82cb8cef754d242e6b2b1881a6956aa1a9450547 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -23,31 +23,7 @@ module Gitlab
       end
 
       def users(field, value, limit = nil)
-        if field.to_sym == :dn
-          options = {
-            base: value,
-            scope: Net::LDAP::SearchScope_BaseObject
-          }
-        else
-          options = {
-            base: config.base,
-            filter: Net::LDAP::Filter.eq(field, value)
-          }
-        end
-
-        if config.user_filter.present?
-          user_filter = Net::LDAP::Filter.construct(config.user_filter)
-
-          options[:filter] = if options[:filter]
-                               Net::LDAP::Filter.join(options[:filter], user_filter)
-                             else
-                               user_filter
-                             end
-        end
-
-        if limit.present?
-          options.merge!(size: limit)
-        end
+        options = user_options(field, value, limit)
 
         entries = ldap_search(options).select do |entry|
           entry.respond_to? config.uid
@@ -90,6 +66,42 @@ module Gitlab
         Rails.logger.warn("LDAP search timed out after #{config.timeout} seconds")
         []
       end
+
+      private
+
+      def user_options(field, value, limit)
+        options = { attributes: user_attributes }
+        options[:size] = limit if limit
+
+        if field.to_sym == :dn
+          options[:base] = value
+          options[:scope] = Net::LDAP::SearchScope_BaseObject
+          options[:filter] = user_filter
+        else
+          options[:base] = config.base
+          options[:filter] = user_filter(Net::LDAP::Filter.eq(field, value))
+        end
+
+        options
+      end
+
+      def user_filter(filter = nil)
+        if config.user_filter.present?
+          user_filter = Net::LDAP::Filter.construct(config.user_filter)
+        end
+
+        if user_filter && filter
+          Net::LDAP::Filter.join(filter, user_filter)
+        elsif user_filter
+          user_filter
+        else
+          filter
+        end
+      end
+
+      def user_attributes
+        %W(#{config.uid} cn mail dn)
+      end
     end
   end
 end
diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb
index a0fd41161a56e56addca8a00a1d1995f8c0be48c..cc74bb29087c7fd56cc0d7d07b3f57131fd5af53 100644
--- a/lib/gitlab/popen.rb
+++ b/lib/gitlab/popen.rb
@@ -18,18 +18,18 @@ module Gitlab
         FileUtils.mkdir_p(path)
       end
 
-      @cmd_output = ""
-      @cmd_status = 0
+      cmd_output = ""
+      cmd_status = 0
       Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
         yield(stdin) if block_given?
         stdin.close
 
-        @cmd_output << stdout.read
-        @cmd_output << stderr.read
-        @cmd_status = wait_thr.value.exitstatus
+        cmd_output << stdout.read
+        cmd_output << stderr.read
+        cmd_status = wait_thr.value.exitstatus
       end
 
-      [@cmd_output, @cmd_status]
+      [cmd_output, cmd_status]
     end
   end
 end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index ffad5e17c78394f621e9a4514b0b7a32d31ebef6..776bbcbb5d09e8bbf45f776c3898a3e12956fe66 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -44,7 +44,7 @@ module Gitlab
     end
 
     def file_name_regex_message
-      "can contain only letters, digits, '_', '-', '@' and '.'. "
+      "can contain only letters, digits, '_', '-', '@' and '.'."
     end
 
     def file_path_regex
@@ -52,7 +52,7 @@ module Gitlab
     end
 
     def file_path_regex_message
-      "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'. "
+      "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'."
     end
 
     def directory_traversal_regex
@@ -60,7 +60,7 @@ module Gitlab
     end
 
     def directory_traversal_regex_message
-      "cannot include directory traversal. "
+      "cannot include directory traversal."
     end
 
     def archive_formats_regex
@@ -96,11 +96,11 @@ module Gitlab
     end
 
     def environment_name_regex
-      @environment_name_regex ||= /\A[a-zA-Z0-9_-]+\z/.freeze
+      @environment_name_regex ||= /\A[a-zA-Z0-9_\\\/\${}. -]+\z/.freeze
     end
 
     def environment_name_regex_message
-      "can contain only letters, digits, '-' and '_'."
+      "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.' and spaces"
     end
   end
 end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index c6826a09bd285cf5e9b543f425460a22ebf84c18..60aae541d467fa6f74eb3c349cf736964c1ea700 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -1,19 +1,38 @@
 require 'base64'
 require 'json'
+require 'securerandom'
 
 module Gitlab
   class Workhorse
     SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'
     VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'
+    INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'
+    INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'
+
+    # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
+    # bytes https://tools.ietf.org/html/rfc4868#section-2.6
+    SECRET_LENGTH = 32
 
     class << self
       def git_http_ok(repository, user)
         {
-          'GL_ID' => Gitlab::GlId.gl_id(user),
-          'RepoPath' => repository.path_to_repo,
+          GL_ID: Gitlab::GlId.gl_id(user),
+          RepoPath: repository.path_to_repo,
         }
       end
 
+      def lfs_upload_ok(oid, size)
+        {
+          StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload",
+          LfsOid: oid,
+          LfsSize: size,
+        }
+      end
+
+      def artifact_upload_ok
+        { TempPath: ArtifactUploader.artifacts_upload_path }
+      end
+
       def send_git_blob(repository, blob)
         params = {
           'RepoPath' => repository.path_to_repo,
@@ -81,6 +100,35 @@ module Gitlab
         path.readable? ? path.read.chomp : 'unknown'
       end
 
+      def secret
+        @secret ||= begin
+          bytes = Base64.strict_decode64(File.read(secret_path).chomp)
+          raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH
+          bytes
+        end
+      end
+      
+      def write_secret
+        bytes = SecureRandom.random_bytes(SECRET_LENGTH)
+        File.open(secret_path, 'w:BINARY', 0600) do |f| 
+          f.chmod(0600)
+          f.write(Base64.strict_encode64(bytes))
+        end
+      end
+      
+      def verify_api_request!(request_headers)
+        JWT.decode(
+          request_headers[INTERNAL_API_REQUEST_HEADER],
+          secret,
+          true,
+          { iss: 'gitlab-workhorse', verify_iss: true, algorithm: 'HS256' },
+        )
+      end
+
+      def secret_path
+        Rails.root.join('.gitlab_workhorse_secret')
+      end
+      
       protected
 
       def encode(hash)
diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake
new file mode 100644
index 0000000000000000000000000000000000000000..609dfaa48e3f33eacd36e54e86a0f548c8df23c2
--- /dev/null
+++ b/lib/tasks/haml-lint.rake
@@ -0,0 +1,5 @@
+unless Rails.env.production?
+  require 'haml_lint/rake_task'
+
+  HamlLint::RakeTask.new
+end
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index bc6e4d940611423a83dc9b664099dc11b4c1fa99..fb4d8463981abc0b3e9e70c779f48f6fa0600960 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -10,6 +10,15 @@ then
   exit 1
 fi
 
+# Ensure that the CHANGELOG does not contain duplicate versions
+DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^v [0-9.]+' CHANGELOG | sed 's| (unreleased)||' | sort | uniq -d)
+if [ "${DUPLICATE_CHANGELOG_VERSIONS}" != "" ]
+then
+  echo '✖ ERROR: Duplicate versions in CHANGELOG:' >&2
+  echo "${DUPLICATE_CHANGELOG_VERSIONS}" >&2
+  exit 1
+fi
+
 echo "✔ Linting passed"
 exit 0
 
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 16929767ddf7250f6f70e79d6ef4edd187357d38..90419368f2218eba318a7a019e003bfa3737ca7f 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -370,6 +370,12 @@ describe Projects::IssuesController do
         expect(response).to have_http_status(302)
         expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now
       end
+
+      it 'delegates the update of the todos count cache to TodoService' do
+        expect_any_instance_of(TodoService).to receive(:destroy_issue).with(issue, owner).once
+
+        delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+      end
     end
   end
 
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index a219400d75fcc7b26d74abbdc216920ff081f85b..94c9edc91fe2b455229c132ae9d1322ea8b493fd 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -320,6 +320,12 @@ describe Projects::MergeRequestsController do
         expect(response).to have_http_status(302)
         expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
       end
+
+      it 'delegates the update of the todos count cache to TodoService' do
+        expect_any_instance_of(TodoService).to receive(:destroy_merge_request).with(merge_request, owner).once
+
+        delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+      end
     end
   end
 
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index ffe0641ddd78932daed3f78012dfc1094c4af861..b0f740f48f7000201fe0f4721df0596a893c9681 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -181,6 +181,25 @@ describe ProjectsController do
       expect(response).to have_http_status(302)
       expect(response).to redirect_to(dashboard_projects_path)
     end
+
+    context "when the project is forked" do
+      let(:project)      { create(:project) }
+      let(:fork_project) { create(:project, forked_from_project: project) }
+      let(:merge_request) do
+        create(:merge_request,
+          source_project: fork_project,
+          target_project: project)
+      end
+
+      it "closes all related merge requests" do
+        project.merge_requests << merge_request
+        sign_in(admin)
+
+        delete :destroy, namespace_id: fork_project.namespace.path, id: fork_project.path
+
+        expect(merge_request.reload.state).to eq('closed')
+      end
+    end
   end
 
   describe "POST #toggle_star" do
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 4e9bfb0c69b747d717b458fd8e5a1042b625887a..8f27e616c3e2bda480cf74aa0375b39ff49e3c9d 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -136,6 +136,29 @@ describe SessionsController do
         post(:create, { user: user_params }, { otp_user_id: user.id })
       end
 
+      context 'remember_me field' do
+        it 'sets a remember_user_token cookie when enabled' do
+          allow(U2fRegistration).to receive(:authenticate).and_return(true)
+          allow(controller).to receive(:find_user).and_return(user)
+          expect(controller).
+            to receive(:remember_me).with(user).and_call_original
+
+          authenticate_2fa_u2f(remember_me: '1', login: user.username, device_response: "{}")
+
+          expect(response.cookies['remember_user_token']).to be_present
+        end
+
+        it 'does nothing when disabled' do
+          allow(U2fRegistration).to receive(:authenticate).and_return(true)
+          allow(controller).to receive(:find_user).and_return(user)
+          expect(controller).not_to receive(:remember_me)
+
+          authenticate_2fa_u2f(remember_me: '0', login: user.username, device_response: "{}")
+
+          expect(response.cookies['remember_user_token']).to be_nil
+        end
+      end
+
       it "creates an audit log record" do
         allow(U2fRegistration).to receive(:authenticate).and_return(true)
         expect { authenticate_2fa_u2f(login: user.username, device_response: "{}") }.to change { SecurityEvent.count }.by(1)
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index 83fccad679f8de9e1973ca56b63e072a191b9105..3372e5ab685708553720cc96252d9b1b4d232b05 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: runner_projects
-#
-#  id         :integer          not null, primary key
-#  runner_id  :integer          not null
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#
-
 FactoryGirl.define do
   factory :ci_runner_project, class: Ci::RunnerProject do
     runner_id 1
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index 5b645fab32ee5993ff00a67a1a0bd9390be69338..e3b73e29987514dc89ef26e7f9ae512151bde743 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: runners
-#
-#  id           :integer          not null, primary key
-#  token        :string(255)
-#  created_at   :datetime
-#  updated_at   :datetime
-#  description  :string(255)
-#  contacted_at :datetime
-#  active       :boolean          default(TRUE), not null
-#  is_shared    :boolean          default(FALSE)
-#  name         :string(255)
-#  version      :string(255)
-#  revision     :string(255)
-#  platform     :string(255)
-#  architecture :string(255)
-#
-
 FactoryGirl.define do
   factory :ci_runner, class: Ci::Runner do
     sequence :description do |n|
@@ -30,5 +11,9 @@ FactoryGirl.define do
     trait :shared do
       is_shared true
     end
+
+    trait :inactive do
+      active false
+    end
   end
 end
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
index 856a8e725eb94b56a5d9464ad101ee50c60b95af..6653f0bb5c3897578b8b411798f4f476ac2e1c5d 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_variables
-#
-#  id                   :integer          not null, primary key
-#  project_id           :integer          not null
-#  key                  :string(255)
-#  value                :text
-#  encrypted_value      :text
-#  encrypted_value_salt :string(255)
-#  encrypted_value_iv   :string(255)
-#  gl_project_id        :integer
-#
-
 FactoryGirl.define do
   factory :ci_variable, class: Ci::Variable do
     sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index 90788f30ac9b8dfdbfd3affa4acce6af55ec862f..8820d527c6195601538bd354c526e148a753dac2 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -1,10 +1,11 @@
 FactoryGirl.define do
   factory :event do
+    project
+    author factory: :user
+
     factory :closed_issue_event do
-      project
       action { Event::CLOSED }
       target factory: :closed_issue
-      author factory: :user
     end
   end
 end
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index debb86d997f48773b6a323ea08d9228e6f80f883..2044ebec09a6185d42c3610cb47587b8082aac92 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: group_members
-#
-#  id                 :integer          not null, primary key
-#  group_access       :integer          not null
-#  group_id           :integer          not null
-#  user_id            :integer          not null
-#  created_at         :datetime
-#  updated_at         :datetime
-#  notification_level :integer          default(3), not null
-#
-
 FactoryGirl.define do
   factory :group_member do
     access_level { GroupMember::OWNER }
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 2c0a2dd94ca1bfa60ac75eda28d6d49557cf8323..2b4670be4689ae6fc8c03e0d31b1c22060f82706 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -1,4 +1,8 @@
 FactoryGirl.define do
+  sequence :issue_created_at do |n|
+    4.hours.ago + ( 2 * n ).seconds
+  end
+
   factory :issue do
     title
     author
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 83e38095febed99cdbce6c06bb20168917117518..6919002dedcd439a03f34a9a077916b7272071cd 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -28,6 +28,11 @@ FactoryGirl.define do
           diff_refs: noteable.diff_refs
         )
       end
+
+      trait :resolved do
+        resolved_at { Time.now }
+        resolved_by { create(:user) }
+      end
     end
 
     factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index c6c2e2095dfdc93694b103f46ab4a56d53171ddd..19941978c5f565c0291600d50ee5beed72dc84e6 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
 
 describe 'Issue Boards', feature: true, js: true do
   include WaitForAjax
+  include WaitForVueResource
 
   let(:project) { create(:empty_project, :public) }
   let(:user)    { create(:user) }
@@ -93,15 +94,8 @@ describe 'Issue Boards', feature: true, js: true do
     end
 
     it 'shows issues in lists' do
-      page.within(find('.board:nth-child(2)')) do
-        expect(page.find('.board-header')).to have_content('2')
-        expect(page).to have_selector('.card', count: 2)
-      end
-
-      page.within(find('.board:nth-child(3)')) do
-        expect(page.find('.board-header')).to have_content('2')
-        expect(page).to have_selector('.card', count: 2)
-      end
+      wait_for_board_cards(2, 2)
+      wait_for_board_cards(3, 2)
     end
 
     it 'shows confidential issues with icon' do
@@ -187,13 +181,13 @@ describe 'Issue Boards', feature: true, js: true do
         expect(page).to have_content('Showing 20 of 56 issues')
 
         evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
-        wait_for_vue_resource(spinner: false)
+        wait_for_vue_resource
 
         expect(page).to have_selector('.card', count: 40)
         expect(page).to have_content('Showing 40 of 56 issues')
 
         evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
-        wait_for_vue_resource(spinner: false)
+        wait_for_vue_resource
 
         expect(page).to have_selector('.card', count: 56)
         expect(page).to have_content('Showing all issues')
@@ -202,37 +196,33 @@ describe 'Issue Boards', feature: true, js: true do
 
     context 'backlog' do
       it 'shows issues in backlog with no labels' do
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('6')
-          expect(page).to have_selector('.card', count: 6)
-        end
+        wait_for_board_cards(1, 6)
       end
 
       it 'moves issue from backlog into list' do
         drag_to(list_to_index: 1)
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('5')
-          expect(page).to have_selector('.card', count: 5)
-        end
-
         wait_for_vue_resource
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('3')
-          expect(page).to have_selector('.card', count: 3)
-        end
+        wait_for_board_cards(1, 5)
+        wait_for_board_cards(2, 3)
       end
     end
 
     context 'done' do
       it 'shows list of done issues' do
-        expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1)
+        wait_for_board_cards(4, 1)
+        wait_for_ajax
       end
 
       it 'moves issue to done' do
         drag_to(list_from_index: 0, list_to_index: 3)
 
+        wait_for_board_cards(1, 5)
+        wait_for_board_cards(2, 2)
+        wait_for_board_cards(3, 2)
+        wait_for_board_cards(4, 2)
+
+        expect(find('.board:nth-child(1)')).not_to have_content(issue9.title)
         expect(find('.board:nth-child(4)')).to have_selector('.card', count: 2)
         expect(find('.board:nth-child(4)')).to have_content(issue9.title)
         expect(find('.board:nth-child(4)')).not_to have_content(planning.title)
@@ -241,8 +231,12 @@ describe 'Issue Boards', feature: true, js: true do
       it 'removes all of the same issue to done' do
         drag_to(list_from_index: 1, list_to_index: 3)
 
-        expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1)
-        expect(find('.board:nth-child(3)')).to have_selector('.card', count: 1)
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 1)
+        wait_for_board_cards(3, 1)
+        wait_for_board_cards(4, 2)
+
+        expect(find('.board:nth-child(2)')).not_to have_content(issue6.title)
         expect(find('.board:nth-child(4)')).to have_content(issue6.title)
         expect(find('.board:nth-child(4)')).not_to have_content(planning.title)
       end
@@ -252,6 +246,11 @@ describe 'Issue Boards', feature: true, js: true do
       it 'changes position of list' do
         drag_to(list_from_index: 1, list_to_index: 2, selector: '.board-header')
 
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 2)
+        wait_for_board_cards(3, 2)
+        wait_for_board_cards(4, 1)
+
         expect(find('.board:nth-child(2)')).to have_content(development.title)
         expect(find('.board:nth-child(2)')).to have_content(planning.title)
       end
@@ -259,8 +258,11 @@ describe 'Issue Boards', feature: true, js: true do
       it 'issue moves between lists' do
         drag_to(list_from_index: 1, card_index: 1, list_to_index: 2)
 
-        expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1)
-        expect(find('.board:nth-child(3)')).to have_selector('.card', count: 3)
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 1)
+        wait_for_board_cards(3, 3)
+        wait_for_board_cards(4, 1)
+
         expect(find('.board:nth-child(3)')).to have_content(issue6.title)
         expect(find('.board:nth-child(3)').all('.card').last).not_to have_content(development.title)
       end
@@ -268,8 +270,11 @@ describe 'Issue Boards', feature: true, js: true do
       it 'issue moves between lists' do
         drag_to(list_from_index: 2, list_to_index: 1)
 
-        expect(find('.board:nth-child(2)')).to have_selector('.card', count: 3)
-        expect(find('.board:nth-child(3)')).to have_selector('.card', count: 1)
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 3)
+        wait_for_board_cards(3, 1)
+        wait_for_board_cards(4, 1)
+
         expect(find('.board:nth-child(2)')).to have_content(issue7.title)
         expect(find('.board:nth-child(2)').all('.card').first).not_to have_content(planning.title)
       end
@@ -277,8 +282,12 @@ describe 'Issue Boards', feature: true, js: true do
       it 'issue moves from done' do
         drag_to(list_from_index: 3, list_to_index: 1)
 
-        expect(find('.board:nth-child(2)')).to have_selector('.card', count: 3)
         expect(find('.board:nth-child(2)')).to have_content(issue8.title)
+
+        wait_for_board_cards(1, 6)
+        wait_for_board_cards(2, 3)
+        wait_for_board_cards(3, 2)
+        wait_for_board_cards(4, 0)
       end
 
       context 'issue card' do
@@ -341,10 +350,7 @@ describe 'Issue Boards', feature: true, js: true do
         end
 
         it 'moves issues from backlog into new list' do
-          page.within(find('.board', match: :first)) do
-            expect(page.find('.board-header')).to have_content('6')
-            expect(page).to have_selector('.card', count: 6)
-          end
+          wait_for_board_cards(1, 6)
 
           click_button 'Create new list'
           wait_for_ajax
@@ -355,10 +361,7 @@ describe 'Issue Boards', feature: true, js: true do
 
           wait_for_vue_resource
 
-          page.within(find('.board', match: :first)) do
-            expect(page.find('.board-header')).to have_content('5')
-            expect(page).to have_selector('.card', count: 5)
-          end
+          wait_for_board_cards(1, 5)
         end
       end
     end
@@ -372,22 +375,14 @@ describe 'Issue Boards', feature: true, js: true do
           page.within '.dropdown-menu-author' do
             click_link(user2.name)
           end
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
 
           expect(find('.js-author-search')).to have_content(user2.name)
         end
 
         wait_for_vue_resource
-
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
       end
 
       it 'filters by assignee' do
@@ -398,22 +393,15 @@ describe 'Issue Boards', feature: true, js: true do
           page.within '.dropdown-menu-assignee' do
             click_link(user.name)
           end
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
 
           expect(find('.js-assignee-search')).to have_content(user.name)
         end
 
         wait_for_vue_resource
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
       end
 
       it 'filters by milestone' do
@@ -424,22 +412,16 @@ describe 'Issue Boards', feature: true, js: true do
           page.within '.milestone-filter' do
             click_link(milestone.title)
           end
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
 
           expect(find('.js-milestone-select')).to have_content(milestone.title)
         end
 
         wait_for_vue_resource
-
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
+        wait_for_board_cards(1, 0)
+        wait_for_board_cards(2, 1)
+        wait_for_board_cards(3, 0)
+        wait_for_board_cards(4, 0)
       end
 
       it 'filters by label' do
@@ -449,22 +431,14 @@ describe 'Issue Boards', feature: true, js: true do
 
           page.within '.dropdown-menu-labels' do
             click_link(testing.title)
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             find('.dropdown-menu-close').click
           end
         end
 
         wait_for_vue_resource
-
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
       end
 
       it 'infinite scrolls list with label filter' do
@@ -478,7 +452,7 @@ describe 'Issue Boards', feature: true, js: true do
 
           page.within '.dropdown-menu-labels' do
             click_link(testing.title)
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             find('.dropdown-menu-close').click
           end
         end
@@ -509,24 +483,17 @@ describe 'Issue Boards', feature: true, js: true do
 
           page.within(find('.dropdown-menu-labels')) do
             click_link(testing.title)
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             click_link(bug.title)
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             find('.dropdown-menu-close').click
           end
         end
 
         wait_for_vue_resource
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
       end
 
       it 'filters by no label' do
@@ -536,22 +503,17 @@ describe 'Issue Boards', feature: true, js: true do
 
           page.within '.dropdown-menu-labels' do
             click_link("No Label")
-            wait_for_vue_resource(spinner: false)
+            wait_for_vue_resource
             find('.dropdown-menu-close').click
           end
         end
 
         wait_for_vue_resource
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('5')
-          expect(page).to have_selector('.card', count: 5)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 5)
+        wait_for_board_cards(2, 0)
+        wait_for_board_cards(3, 0)
+        wait_for_board_cards(4, 1)
       end
 
       it 'filters by clicking label button on issue' do
@@ -559,20 +521,13 @@ describe 'Issue Boards', feature: true, js: true do
           expect(page).to have_selector('.card', count: 6)
           expect(find('.card', match: :first)).to have_content(bug.title)
           click_button(bug.title)
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
         end
 
         wait_for_vue_resource
 
-        page.within(find('.board', match: :first)) do
-          expect(page.find('.board-header')).to have_content('1')
-          expect(page).to have_selector('.card', count: 1)
-        end
-
-        page.within(find('.board:nth-child(2)')) do
-          expect(page.find('.board-header')).to have_content('0')
-          expect(page).to have_selector('.card', count: 0)
-        end
+        wait_for_board_cards(1, 1)
+        wait_for_empty_boards((2..4))
 
         page.within('.labels-filter') do
           expect(find('.dropdown-toggle-text')).to have_content(bug.title)
@@ -584,7 +539,7 @@ describe 'Issue Boards', feature: true, js: true do
           page.within(find('.card', match: :first)) do
             click_button(bug.title)
           end
-          wait_for_vue_resource(spinner: false)
+          wait_for_vue_resource
 
           expect(page).to have_selector('.card', count: 1)
         end
@@ -648,13 +603,16 @@ describe 'Issue Boards', feature: true, js: true do
     wait_for_vue_resource
   end
 
-  def wait_for_vue_resource(spinner: true)
-    Timeout.timeout(Capybara.default_max_wait_time) do
-      loop until page.evaluate_script('Vue.activeResources').zero?
+  def wait_for_board_cards(board_number, expected_cards)
+    page.within(find(".board:nth-child(#{board_number})")) do
+      expect(page.find('.board-header')).to have_content(expected_cards.to_s)
+      expect(page).to have_selector('.card', count: expected_cards)
     end
+  end
 
-    if spinner
-      expect(find('.boards-list')).not_to have_selector('.fa-spinner')
+  def wait_for_empty_boards(board_numbers)
+    board_numbers.each do |board|
+      wait_for_board_cards(board, 0)
     end
   end
 end
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7ef68e9eb8d2137627b74f8e4299946218c524c3
--- /dev/null
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+describe 'Issue Boards shortcut', feature: true, js: true do
+  include WaitForVueResource
+
+  let(:project) { create(:empty_project) }
+
+  before do
+    project.create_board
+    project.board.lists.create(list_type: :backlog)
+    project.board.lists.create(list_type: :done)
+
+    login_as :admin
+
+    visit namespace_project_path(project.namespace, project)
+  end
+
+  it 'takes user to issue board index' do
+    find('body').native.send_keys('gl')
+    expect(page).to have_selector('.boards-list')
+
+    wait_for_vue_resource
+  end
+end
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd5fbaf2af45b54730b1a09e688fba3e24c7a865
--- /dev/null
+++ b/spec/features/calendar_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+feature 'Contributions Calendar', js: true, feature: true do
+  include WaitForAjax
+
+  let(:contributed_project) { create(:project, :public) }
+
+  before do
+    login_as :user
+
+    issue_params = { title: 'Bug in old browser' }
+    Issues::CreateService.new(contributed_project, @user, issue_params).execute
+
+    # Push code contribution
+    push_params = {
+      project: contributed_project,
+      action: Event::PUSHED,
+      author_id: @user.id,
+      data: { commit_count: 3 }
+    }
+
+    Event.create(push_params)
+
+    visit @user.username
+    wait_for_ajax
+  end
+
+  it 'displays calendar', js: true do
+    expect(page).to have_css('.js-contrib-calendar')
+  end
+
+  it 'displays calendar activity log', js: true do
+    expect(find('.content_list .event-note')).to have_content "Bug in old browser"
+  end
+
+  it 'displays calendar activity square color', js: true do
+    expect(page).to have_selector('.user-contrib-cell[fill=\'#acd5f2\']', count: 1)
+  end
+end
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index fcd41b38413112748114d42fd628de10a680be05..4309a726917052eff1e9ab93b1342ef46d1d1945 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -150,7 +150,7 @@ feature 'Environments', feature: true do
 
       context 'for invalid name' do
         before do
-          fill_in('Name', with: 'name with spaces')
+          fill_in('Name', with: 'name,with,commas')
           click_on 'Save'
         end
 
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 0e9f814044e702f20b83fe705c7ea1ef71a43290..69fda27cc613d51c2a556206a1bf127592218f29 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -101,7 +101,7 @@ describe 'Filter issues', feature: true do
       expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label')
     end
 
-    it 'filters by no label' do
+    it 'filters by a label' do
       find('.dropdown-menu-labels a', text: label.title).click
       page.within '.labels-filter' do
         expect(page).to have_content label.title
@@ -109,7 +109,7 @@ describe 'Filter issues', feature: true do
       expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
     end
 
-    it 'filters by wont fix labels' do
+    it "filters by `won't fix` and another label" do
       find('.dropdown-menu-labels a', text: label.title).click
       page.within '.labels-filter' do
         expect(page).to have_content wontfix.title
@@ -117,6 +117,33 @@ describe 'Filter issues', feature: true do
       end
       expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title)
     end
+
+    it "filters by `won't fix` label followed by another label after page load" do
+      find('.dropdown-menu-labels a', text: wontfix.title).click
+      # Close label dropdown to load
+      find('body').click
+      expect(find('.filtered-labels')).to have_content(wontfix.title)
+
+      find('.js-label-select').click
+      wait_for_ajax
+      find('.dropdown-menu-labels a', text: label.title).click
+      # Close label dropdown to load
+      find('body').click
+      expect(find('.filtered-labels')).to have_content(label.title)
+
+      find('.js-label-select').click
+      wait_for_ajax
+      expect(find('.dropdown-menu-labels li', text: wontfix.title)).to have_css('.is-active')
+      expect(find('.dropdown-menu-labels li', text: label.title)).to have_css('.is-active')
+    end
+
+    it "selects and unselects `won't fix`" do
+      find('.dropdown-menu-labels a', text: wontfix.title).click
+      find('.dropdown-menu-labels a', text: wontfix.title).click
+      # Close label dropdown to load
+      find('body').click
+      expect(page).not_to have_css('.filtered-labels')
+    end
   end
 
   describe 'Filter issues for assignee and label from issues#index' do
diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..41f218eaa8b4e7294010066fbd12d1b866a718dd
--- /dev/null
+++ b/spec/features/issues/reset_filters_spec.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+feature 'Issues filter reset button', feature: true, js: true do
+  include WaitForAjax
+  include IssueHelpers
+
+  let!(:project)    { create(:project, :public) }
+  let!(:user)        { create(:user)}
+  let!(:milestone)  { create(:milestone, project: project) }
+  let!(:bug)        { create(:label, project: project, name: 'bug')}
+  let!(:issue1)     { create(:issue, project: project, milestone: milestone, author: user, assignee: user, title: 'Feature')}
+  let!(:issue2)     { create(:labeled_issue, project: project, labels: [bug], title: 'Bugfix1')}
+
+  before do
+    project.team << [user, :developer]
+  end
+
+  context 'when a milestone filter has been applied' do
+    it 'resets the milestone filter' do
+      visit_issues(project, milestone_title: milestone.title)
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when a label filter has been applied' do
+    it 'resets the label filter' do
+      visit_issues(project, label_name: bug.name)
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when a text search has been conducted' do
+    it 'resets the text search filter' do
+      visit_issues(project, issue_search: 'Bug')
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when author filter has been applied' do
+    it 'resets the author filter' do
+      visit_issues(project, author_id: user.id)
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when assignee filter has been applied' do
+    it 'resets the assignee filter' do
+      visit_issues(project, assignee_id: user.id)
+      expect(page).to have_css('.issue', count: 1)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  context 'when all filters have been applied' do
+    it 'resets all filters' do
+      visit_issues(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, issue_search: 'Bug')
+      expect(page).to have_css('.issue', count: 0)
+
+      reset_filters
+      expect(page).to have_css('.issue', count: 2)
+    end
+  end
+
+  def reset_filters
+    find('.reset-filters').click
+  end
+end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 2883e3926940aef814ab89057fad03a8778b6f76..105629c485a8ec29273e62e305b3b470b170f563 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -1,6 +1,7 @@
 require 'rails_helper'
 
 feature 'Issues > User uses slash commands', feature: true, js: true do
+  include SlashCommandsHelpers
   include WaitForAjax
 
   it_behaves_like 'issuable record that supports slash commands in its description and notes', :issue do
@@ -17,14 +18,15 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
       visit namespace_project_issue_path(project.namespace, project, issue)
     end
 
+    after do
+      wait_for_ajax
+    end
+
     describe 'adding a due date from note' do
       let(:issue) { create(:issue, project: project) }
 
       it 'does not create a note, and sets the due date accordingly' do
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/due 2016-08-28"
-          click_button 'Comment'
-        end
+        write_note("/due 2016-08-28")
 
         expect(page).not_to have_content '/due 2016-08-28'
         expect(page).to have_content 'Your commands have been executed!'
@@ -41,10 +43,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
       it 'does not create a note, and removes the due date accordingly' do
         expect(issue.due_date).to eq Date.new(2016, 8, 28)
 
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/remove_due_date"
-          click_button 'Comment'
-        end
+        write_note("/remove_due_date")
 
         expect(page).not_to have_content '/remove_due_date'
         expect(page).to have_content 'Your commands have been executed!'
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index d744d0eb6aff5f60f0b5ec7408202c4325f34f1f..22359c8f93824583e7f5ed84dcfd6963a128c555 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -144,7 +144,7 @@ describe 'Issues', feature: true do
       visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id)
 
       expect(page).to have_content 'foobar'
-      expect(page.all('.issue-no-comments').first.text).to eq "0"
+      expect(page.all('.no-comments').first.text).to eq "0"
     end
   end
 
diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb
index 577c910f11b1fd186074b6f0fe6b8282163c98f8..9e759de3752b3d0bbd56069233a0ec9db6195a30 100644
--- a/spec/features/merge_requests/merge_request_versions_spec.rb
+++ b/spec/features/merge_requests/merge_request_versions_spec.rb
@@ -11,8 +11,8 @@ feature 'Merge Request versions', js: true, feature: true do
   end
 
   it 'show the latest version of the diff' do
-    page.within '.mr-version-switch' do
-      expect(page).to have_content 'Version: latest'
+    page.within '.mr-version-dropdown' do
+      expect(page).to have_content 'latest version'
     end
 
     expect(page).to have_content '8 changed files'
@@ -20,18 +20,49 @@ feature 'Merge Request versions', js: true, feature: true do
 
   describe 'switch between versions' do
     before do
-      page.within '.mr-version-switch' do
+      page.within '.mr-version-dropdown' do
         find('.btn-link').click
-        click_link '6f6d7e7e'
+        click_link 'version 1'
       end
     end
 
     it 'should show older version' do
-      page.within '.mr-version-switch' do
-        expect(page).to have_content 'Version: 6f6d7e7e'
+      page.within '.mr-version-dropdown' do
+        expect(page).to have_content 'version 1'
       end
 
       expect(page).to have_content '5 changed files'
     end
+
+    it 'show the message about disabled comments' do
+      expect(page).to have_content 'Comments are disabled'
+    end
+  end
+
+  describe 'compare with older version' do
+    before do
+      page.within '.mr-version-compare-dropdown' do
+        find('.btn-link').click
+        click_link 'version 1'
+      end
+    end
+
+    it 'should has correct value in the compare dropdown' do
+      page.within '.mr-version-compare-dropdown' do
+        expect(page).to have_content 'version 1'
+      end
+    end
+
+    it 'show the message about disabled comments' do
+      expect(page).to have_content 'Comments are disabled'
+    end
+
+    it 'show diff between new and old version' do
+      expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
+    end
+
+    it 'show diff between new and old version' do
+      expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
+    end
   end
 end
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b56fdfe56113e6bd62a82c48ece4ff946271bac7
--- /dev/null
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -0,0 +1,132 @@
+require 'rails_helper'
+
+feature 'Multiple merge requests updating from merge_requests#index', feature: true do
+  include WaitForAjax
+
+  let!(:user)    { create(:user)}
+  let!(:project) { create(:project) }
+  let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+
+  before do
+    project.team << [user, :master]
+    login_as(user)
+  end
+
+  context 'status', js: true do
+    describe 'close merge request' do
+      before do
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it 'closes merge request' do
+        change_status('Closed')
+
+        expect(page).to have_selector('.merge-request', count: 0)
+      end
+    end
+
+    describe 'reopen merge request' do
+      before do
+        merge_request.close
+        visit namespace_project_merge_requests_path(project.namespace, project, state: 'closed')
+      end
+
+      it 'reopens merge request' do
+        change_status('Open')
+
+        expect(page).to have_selector('.merge-request', count: 0)
+      end
+    end
+  end
+
+  context 'assignee', js: true do
+    describe 'set assignee' do
+      before do
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it "updates merge request with assignee" do
+        change_assignee(user.name)
+
+        page.within('.merge-request .controls') do
+          expect(find('.author_link')["title"]).to have_content(user.name)
+        end
+      end
+    end
+
+    describe 'remove assignee' do
+      before do
+        merge_request.assignee = user
+        merge_request.save
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it "removes assignee from the merge request" do
+        change_assignee('Unassigned')
+
+        expect(find('.merge-request .controls')).not_to have_css('.author_link')
+      end
+    end
+  end
+
+  context 'milestone', js: true do
+    let(:milestone)  { create(:milestone, project: project) }
+
+    describe 'set milestone' do
+      before do
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it "updates merge request with milestone" do
+        change_milestone(milestone.title)
+
+        expect(find('.merge-request')).to have_content milestone.title
+      end
+    end
+
+    describe 'unset milestone' do
+      before do
+        merge_request.milestone = milestone
+        merge_request.save
+        visit namespace_project_merge_requests_path(project.namespace, project)
+      end
+
+      it "removes milestone from the merge request" do
+        change_milestone("No Milestone")
+
+        expect(find('.merge-request')).not_to have_content milestone.title
+      end
+    end
+  end
+
+  def change_status(text)
+    find('#check_all_issues').click
+    find('.js-issue-status').click
+    find('.dropdown-menu-status a', text: text).click
+    click_update_merge_requests_button
+  end
+
+  def change_assignee(text)
+    find('#check_all_issues').click
+    find('.js-update-assignee').click
+    wait_for_ajax
+
+    page.within '.dropdown-menu-user' do
+      click_link text
+    end
+
+    click_update_merge_requests_button
+  end
+
+  def change_milestone(text)
+    find('#check_all_issues').click
+    find('.issues_bulk_update .js-milestone-select').click
+    find('.dropdown-menu-milestone a', text: text).click
+    click_update_merge_requests_button
+  end
+
+  def click_update_merge_requests_button
+    find('.update_selected_issues').click
+    wait_for_ajax
+  end
+end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index d9ef0d180742e5ae0797f6e35f33c1d4455dde76..22d9d1b9fd518b8d14d9a520a7fac86c99f42609 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -1,6 +1,7 @@
 require 'rails_helper'
 
 feature 'Merge Requests > User uses slash commands', feature: true, js: true do
+  include SlashCommandsHelpers
   include WaitForAjax
 
   let(:user) { create(:user) }
@@ -20,11 +21,12 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
       visit namespace_project_merge_request_path(project.namespace, project, merge_request)
     end
 
+    after do
+      wait_for_ajax
+    end
+
     it 'does not recognize the command nor create a note' do
-      page.within('.js-main-target-form') do
-        fill_in 'note[note]', with: "/due 2016-08-28"
-        click_button 'Comment'
-      end
+      write_note("/due 2016-08-28")
 
       expect(page).not_to have_content '/due 2016-08-28'
     end
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index c43661e56813ae8a9135266d5c6a57abd3578de6..b8c838bf7ab0fee8ccc7a11436e3372dbaa1966c 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -3,9 +3,8 @@ require 'rails_helper'
 feature 'Milestone', feature: true do
   include WaitForAjax
 
-  let(:project) { create(:project, :public) }
+  let(:project) { create(:empty_project, :public) }
   let(:user)   { create(:user) }
-  let(:milestone) { create(:milestone, project: project, title: 8.7) }
 
   before do
     project.team << [user, :master]
@@ -13,7 +12,7 @@ feature 'Milestone', feature: true do
   end
 
   feature 'Create a milestone' do
-    scenario 'shows an informative message for a new issue' do
+    scenario 'shows an informative message for a new milestone' do
       visit new_namespace_project_milestone_path(project.namespace, project)
       page.within '.milestone-form' do
         fill_in "milestone_title", with: '8.7'
@@ -26,10 +25,26 @@ feature 'Milestone', feature: true do
 
   feature 'Open a milestone with closed issues' do
     scenario 'shows an informative message' do
+      milestone = create(:milestone, project: project, title: 8.7)
+
       create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed")
       visit namespace_project_milestone_path(project.namespace, project, milestone)
 
       expect(find('.alert-success')).to have_content('All issues for this milestone are closed. You may close this milestone now.')
     end
   end
+
+  feature 'Open a milestone with an existing title' do
+    scenario 'displays validation message' do
+      milestone = create(:milestone, project: project, title: 8.7)
+
+      visit new_namespace_project_milestone_path(project.namespace, project)
+      page.within '.milestone-form' do
+        fill_in "milestone_title", with: milestone.title
+      end
+      find('input[name="commit"]').click
+
+      expect(find('.alert-danger')).to have_content('Title has already been taken')
+    end
+  end
 end
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3b20d38c520cd603205096fcdd9f8a2ce9a8984b
--- /dev/null
+++ b/spec/features/profiles/keys_spec.rb
@@ -0,0 +1,18 @@
+require 'rails_helper'
+
+describe 'Profile > SSH Keys', feature: true do
+  let(:user) { create(:user) }
+
+  before do
+    login_as(user)
+    visit profile_keys_path
+  end
+
+  describe 'User adds an SSH key' do
+    it 'auto-populates the title', js: true do
+      fill_in('Key', with: attributes_for(:key).fetch(:key))
+
+      expect(find_field('Title').value).to eq 'dummy@gitlab.com'
+    end
+  end
+end
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index 0405830057084ddcb96d074b97ad98cd781b936c..92028c1936102665d38529103de74ad3c5eda124 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -33,7 +33,11 @@ feature 'Download buttons in branches page', feature: true do
       end
 
       scenario 'shows download artifacts button' do
-        expect(page).to have_link "Download '#{build.name}'"
+        href = latest_succeeded_namespace_project_artifacts_path(
+          project.namespace, project, 'binary-encoding/download',
+          job: 'build')
+
+        expect(page).to have_link "Download '#{build.name}'", href: href
       end
     end
   end
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 1b14945bf0a7fbd951f239a05abe3752c3725719..d26a0caf0368e3f10b26bb8a6080b7304c3fcf33 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -1,32 +1,46 @@
 require 'spec_helper'
 
 describe 'Branches', feature: true do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :public) }
   let(:repository) { project.repository }
 
-  before do
-    login_as :user
-    project.team << [@user, :developer]
-  end
+  context 'logged in' do
+    before do
+      login_as :user
+      project.team << [@user, :developer]
+    end
 
-  describe 'Initial branches page' do
-    it 'shows all the branches' do
-      visit namespace_project_branches_path(project.namespace, project)
+    describe 'Initial branches page' do
+      it 'shows all the branches' do
+        visit namespace_project_branches_path(project.namespace, project)
 
-      repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
-      expect(page).to have_content("Protected branches can be managed in project settings")
+        repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
+        expect(page).to have_content("Protected branches can be managed in project settings")
+      end
+    end
+
+    describe 'Find branches' do
+      it 'shows filtered branches', js: true do
+        visit namespace_project_branches_path(project.namespace, project)
+
+        fill_in 'branch-search', with: 'fix'
+        find('#branch-search').native.send_keys(:enter)
+
+        expect(page).to have_content('fix')
+        expect(find('.all-branches')).to have_selector('li', count: 1)
+      end
     end
   end
 
-  describe 'Find branches' do
-    it 'shows filtered branches', js: true do
+  context 'logged out' do
+    before do
       visit namespace_project_branches_path(project.namespace, project)
+    end
 
-      fill_in 'branch-search', with: 'fix'
-      find('#branch-search').native.send_keys(:enter)
-
-      expect(page).to have_content('fix')
-      expect(find('.all-branches')).to have_selector('li', count: 1)
+    it 'does not show merge request button' do
+      page.within first('.all-branches li') do
+        expect(page).not_to have_content 'Merge Request'
+      end
     end
   end
 end
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index be5cebcd7c9f9657c255fff604fa161d76a20ee5..d7c29a7e07449cc29cbd795f41d52d02ca9f33ac 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -34,7 +34,11 @@ feature 'Download buttons in files tree', feature: true do
       end
 
       scenario 'shows download artifacts button' do
-        expect(page).to have_link "Download '#{build.name}'"
+        href = latest_succeeded_namespace_project_artifacts_path(
+          project.namespace, project, "#{project.default_branch}/download",
+          job: 'build')
+
+        expect(page).to have_link "Download '#{build.name}'", href: href
       end
     end
   end
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index e14b27057048b57aaec1f53dcde44a3291876147..d04bdea0fe4b177e54770a1c25f83fd841f5a4bc 100644
Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index 4a83740621a698129f07e2e1f8e7c947c0bebe4c..f76c4fe8b57fc6d058ecd30c502ae925675f29c8 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -13,10 +13,12 @@ feature 'issuable templates', feature: true, js: true do
 
   context 'user creates an issue using templates' do
     let(:template_content) { 'this is a test "bug" template' }
+    let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) }
     let(:issue) { create(:issue, author: user, assignee: user, project: project) }
 
     background do
       project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false)
+      project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false)
       visit edit_namespace_project_issue_path project.namespace, project, issue
       fill_in :'issue[title]', with: 'test issue title'
     end
@@ -27,6 +29,17 @@ feature 'issuable templates', feature: true, js: true do
       preview_template
       save_changes
     end
+
+    it 'updates height of markdown textarea' do
+      start_height = page.evaluate_script('$(".markdown-area").outerHeight()')
+
+      select_template 'test'
+      wait_for_ajax
+
+      end_height = page.evaluate_script('$(".markdown-area").outerHeight()')
+      
+      expect(end_height).not_to eq(start_height)
+    end
   end
 
   context 'user creates a merge request using templates' do
@@ -51,7 +64,7 @@ feature 'issuable templates', feature: true, js: true do
     let(:template_content) { 'this is a test "feature-proposal" template' }
     let(:fork_user) { create(:user) }
     let(:fork_project) { create(:project, :public) }
-    let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project) }
+    let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) }
 
     background do
       logout
@@ -59,16 +72,20 @@ feature 'issuable templates', feature: true, js: true do
       fork_project.team << [fork_user, :master]
       create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
       login_as fork_user
-      fork_project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false)
-      visit edit_namespace_project_merge_request_path fork_project.namespace, fork_project, merge_request
+      project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false)
+      visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
       fill_in :'merge_request[title]', with: 'test merge request title'
     end
 
-    scenario 'user selects "feature-proposal" template' do
-      select_template 'feature-proposal'
-      wait_for_ajax
-      preview_template
-      save_changes
+    context 'feature proposal template' do
+      context 'template exists in target project' do
+        scenario 'user selects template' do
+          select_template 'feature-proposal'
+          wait_for_ajax
+          preview_template
+          save_changes
+        end
+      end
     end
   end
 
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
index b26c0ea7a1487d3a9225eef9adf66c5eee93d337..227ccf9459c3e5728ca47bba445b3bceea13f13c 100644
--- a/spec/features/projects/main/download_buttons_spec.rb
+++ b/spec/features/projects/main/download_buttons_spec.rb
@@ -33,7 +33,11 @@ feature 'Download buttons in project main page', feature: true do
       end
 
       scenario 'shows download artifacts button' do
-        expect(page).to have_link "Download '#{build.name}'"
+        href = latest_succeeded_namespace_project_artifacts_path(
+          project.namespace, project, "#{project.default_branch}/download",
+          job: 'build')
+
+        expect(page).to have_link "Download '#{build.name}'", href: href
       end
     end
   end
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index 6e0022c179fa855c3cb7f159e9730c8d15b53f16..dd93d25c2c6b8e15e57749a2767447a8f8c76579 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -34,7 +34,11 @@ feature 'Download buttons in tags page', feature: true do
       end
 
       scenario 'shows download artifacts button' do
-        expect(page).to have_link "Download '#{build.name}'"
+        href = latest_succeeded_namespace_project_artifacts_path(
+          project.namespace, project, "#{tag}/download",
+          job: 'build')
+
+        expect(page).to have_link "Download '#{build.name}'", href: href
       end
     end
   end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index e00d85904d5da844cd41b7c5730f22ad1293404d..2242cb6236a602d1b025bfde7f76d216c1580912 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -57,7 +57,7 @@ feature 'Project', feature: true do
 
   describe 'removal', js: true do
     let(:user)    { create(:user) }
-    let(:project) { create(:project, namespace: user.namespace) }
+    let(:project) { create(:project, namespace: user.namespace, name: 'project1') }
 
     before do
       login_with(user)
@@ -65,8 +65,12 @@ feature 'Project', feature: true do
       visit edit_namespace_project_path(project.namespace, project)
     end
 
-    it 'removes project' do
+    it 'removes a project' do
       expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1)
+      expect(page).to have_content "Project 'project1' will be deleted."
+      expect(Project.all.count).to be_zero
+      expect(project.issues).to be_empty
+      expect(project.merge_requests).to be_empty
     end
   end
 
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb
index 83cf306437d781fe5343ffda636db3aed3fab213..b9e66243d84def29c102d1612973bcd8b2e612b7 100644
--- a/spec/features/todos/todos_filtering_spec.rb
+++ b/spec/features/todos/todos_filtering_spec.rb
@@ -29,8 +29,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
       fill_in 'Search projects', with: project_1.name_with_namespace
       click_link project_1.name_with_namespace
     end
+
     wait_for_ajax
-    expect('.prepend-top-default').not_to have_content project_2.name_with_namespace
+
+    expect(page).to     have_content project_1.name_with_namespace
+    expect(page).not_to have_content project_2.name_with_namespace
   end
 
   it 'filters by author' do
@@ -39,8 +42,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
       fill_in 'Search authors', with: user_1.name
       click_link user_1.name
     end
+
     wait_for_ajax
-    expect('.prepend-top-default').not_to have_content user_2.name
+
+    expect(find('.todos-list')).to     have_content user_1.name
+    expect(find('.todos-list')).not_to have_content user_2.name
   end
 
   it 'filters by type' do
@@ -48,8 +54,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
     within '.dropdown-menu-type' do
       click_link 'Issue'
     end
+
     wait_for_ajax
-    expect('.prepend-top-default').not_to have_content ' merge request !'
+
+    expect(find('.todos-list')).to     have_content issue.to_reference
+    expect(find('.todos-list')).not_to have_content merge_request.to_reference
   end
 
   it 'filters by action' do
@@ -57,7 +66,10 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
     within '.dropdown-menu-action' do
       click_link 'Assigned'
     end
+
     wait_for_ajax
-    expect('.prepend-top-default').not_to have_content ' mentioned '
+
+    expect(find('.todos-list')).to     have_content ' assigned you '
+    expect(find('.todos-list')).not_to have_content ' mentioned '
   end
 end
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index a46e48c76ed6eb69bf122edd06cb5492a31c2edb..ff6933dc8d90971ad8e6b27188ec434f1953409d 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -156,6 +156,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
 
     describe "when 2FA via OTP is disabled" do
       it "allows logging in with the U2F device" do
+        user.update_attribute(:otp_required_for_login, false)
         login_with(user)
 
         @u2f_device.respond_to_u2f_authentication
@@ -181,6 +182,19 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
       end
     end
 
+    it 'persists remember_me value via hidden field' do
+      login_with(user, remember: true)
+
+      @u2f_device.respond_to_u2f_authentication
+      click_on "Login Via U2F Device"
+      expect(page.body).to match('We heard back from your U2F device')
+
+      within 'div#js-authenticate-u2f' do
+        field = first('input#user_remember_me', visible: false)
+        expect(field.value).to eq '1'
+      end
+    end
+
     describe "when a given U2F device has already been registered by another user" do
       describe "but not the current user" do
         it "does not allow logging in with that particular device" do
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
index 356a8d668b03a19b0b42b279a33a598d7e138934..f00abd82fea72b844dac859160bfd77ffd955496 100644
--- a/spec/features/users/snippets_spec.rb
+++ b/spec/features/users/snippets_spec.rb
@@ -19,7 +19,10 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
     end
 
     context 'clicking on the link to the second page' do
-      before { click_link('2') }
+      before do
+        click_link('2')
+        wait_for_ajax
+      end
 
       it 'shows the remaining snippets' do
         expect(page.all('.snippets-list-holder .snippet-row').count).to eq(5)
diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b0811d134faa2ccc60ba70fb2bda82cbb13d72c3
--- /dev/null
+++ b/spec/finders/pipelines_finder_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe PipelinesFinder do
+  let(:project) { create(:project) }
+
+  let!(:tag_pipeline)    { create(:ci_pipeline, project: project, ref: 'v1.0.0') }
+  let!(:branch_pipeline) { create(:ci_pipeline, project: project) }
+
+  subject { described_class.new(project).execute(params) }
+
+  describe "#execute" do
+    context 'when a scope is passed' do
+      context 'when scope is nil' do
+        let(:params) { { scope: nil } }
+
+        it 'selects all pipelines' do
+          expect(subject.count).to be 2
+          expect(subject).to include tag_pipeline
+          expect(subject).to include branch_pipeline
+        end
+      end
+
+      context 'when selecting branches' do
+        let(:params) { { scope: 'branches' } }
+
+        it 'excludes tags' do
+          expect(subject).not_to include tag_pipeline
+          expect(subject).to     include branch_pipeline
+        end
+      end
+
+      context 'when selecting tags' do
+        let(:params) { { scope: 'tags' } }
+
+        it 'excludes branches' do
+          expect(subject).to     include tag_pipeline
+          expect(subject).not_to include branch_pipeline
+        end
+      end
+    end
+
+    # Scoping to running will speed up the test as it doesn't hit the FS
+    let(:params) { { scope: 'running' } }
+
+    it 'orders in descending order on ID' do
+      feature_pipeline = create(:ci_pipeline, project: project, ref: 'feature')
+
+      expected_ids = [feature_pipeline.id, branch_pipeline.id, tag_pipeline.id].sort.reverse
+      expect(subject.map(&:id)).to eq expected_ids
+    end
+  end
+end
diff --git a/spec/helpers/git_helper_spec.rb b/spec/helpers/git_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9b1ef1e05a2daaae5e6520238cdcf03b5d148569
--- /dev/null
+++ b/spec/helpers/git_helper_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+describe GitHelper do
+  describe '#short_sha' do
+    let(:short_sha) { helper.short_sha('d4e043f6c20749a3ab3f4b8e23f2a8979f4b9100') }
+
+    it { expect(short_sha).to eq('d4e043f6') }
+  end
+end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 0807534720a9b69a6b1812692b5de048bd2710dc..233d00534e507446288b4c577f43e5b6be49c1ba 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -18,4 +18,67 @@ describe GroupsHelper do
       expect(group_icon(group.path)).to match('group_avatar.png')
     end
   end
+
+  describe 'group_lfs_status' do
+    let(:group) { create(:group) }
+    let!(:project) { create(:empty_project, namespace_id: group.id) }
+
+    before do
+      allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+    end
+
+    context 'only one project in group' do
+      before do
+        group.update_attribute(:lfs_enabled, true)
+      end
+
+      it 'returns all projects as enabled' do
+        expect(group_lfs_status(group)).to include('Enabled for all projects')
+      end
+
+      it 'returns all projects as disabled' do
+        project.update_attribute(:lfs_enabled, false)
+
+        expect(group_lfs_status(group)).to include('Enabled for 0 out of 1 project')
+      end
+    end
+
+    context 'more than one project in group' do
+      before do
+        create(:empty_project, namespace_id: group.id)
+      end
+
+      context 'LFS enabled in group' do
+        before do
+          group.update_attribute(:lfs_enabled, true)
+        end
+
+        it 'returns both projects as enabled' do
+          expect(group_lfs_status(group)).to include('Enabled for all projects')
+        end
+
+        it 'returns only one as enabled' do
+          project.update_attribute(:lfs_enabled, false)
+
+          expect(group_lfs_status(group)).to include('Enabled for 1 out of 2 projects')
+        end
+      end
+
+      context 'LFS disabled in group' do
+        before do
+          group.update_attribute(:lfs_enabled, false)
+        end
+
+        it 'returns both projects as disabled' do
+          expect(group_lfs_status(group)).to include('Disabled for all projects')
+        end
+
+        it 'returns only one as disabled' do
+          project.update_attribute(:lfs_enabled, true)
+
+          expect(group_lfs_status(group)).to include('Disabled for 1 out of 2 projects')
+        end
+      end
+    end
+  end
 end
diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb
deleted file mode 100644
index e4d18d8bfc6f2421286ed9b7a37a382b41c8760d..0000000000000000000000000000000000000000
--- a/spec/helpers/nav_helper_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'spec_helper'
-
-# Specs in this file have access to a helper object that includes
-# the NavHelper. For example:
-#
-# describe NavHelper do
-#   describe "string concat" do
-#     it "concats two strings with spaces" do
-#       expect(helper.concat_strings("this","that")).to eq("this that")
-#     end
-#   end
-# end
-describe NavHelper do
-  describe '#nav_menu_collapsed?' do
-    it 'returns true when the nav is collapsed in the cookie' do
-      helper.request.cookies[:collapsed_nav] = 'true'
-      expect(helper.nav_menu_collapsed?).to eq true
-    end
-
-    it 'returns false when the nav is not collapsed in the cookie' do
-      helper.request.cookies[:collapsed_nav] = 'false'
-      expect(helper.nav_menu_collapsed?).to eq false
-    end
-  end
-end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 284b58d8d5cad0ee1ecc1c919ba022c7e6071659..70032e7df949d822681003c8bba4e6153db0fa56 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -174,4 +174,48 @@ describe ProjectsHelper do
       end
     end
   end
+
+  describe "#project_feature_access_select" do
+    let(:project) { create(:empty_project, :public) }
+    let(:user)    { create(:user) }
+
+    context "when project is internal or public" do
+      it "shows all options" do
+        helper.instance_variable_set(:@project, project)
+        result = helper.project_feature_access_select(:issues_access_level)
+        expect(result).to include("Disabled")
+        expect(result).to include("Only team members")
+        expect(result).to include("Everyone with access")
+      end
+    end
+
+    context "when project is private" do
+      before { project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
+
+      it "shows only allowed options" do
+        helper.instance_variable_set(:@project, project)
+        result = helper.project_feature_access_select(:issues_access_level)
+        expect(result).to include("Disabled")
+        expect(result).to include("Only team members")
+        expect(result).not_to include("Everyone with access")
+      end
+    end
+
+    context "when project moves from public to private" do
+      before do
+        project.project_feature.update_attributes(issues_access_level: ProjectFeature::ENABLED)
+        project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+      end
+
+      it "shows the highest allowed level selected" do
+        helper.instance_variable_set(:@project, project)
+        result = helper.project_feature_access_select(:issues_access_level)
+
+        expect(result).to include("Disabled")
+        expect(result).to include("Only team members")
+        expect(result).not_to include("Everyone with access")
+        expect(result).to have_selector('option[selected]', text: "Only team members")
+      end
+    end
+  end
 end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index b0bb991539b38ca0fb189c96739f72aafec045c6..c5b5aa8c445d3c0bf682d0c60125279b63256c2a 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -6,6 +6,38 @@ describe SearchHelper do
     str
   end
 
+  describe 'parsing result' do
+    let(:project) { create(:project) }
+    let(:repository) { project.repository }
+    let(:results) { repository.search_files('feature', 'master') }
+    let(:search_result) { results.first }
+
+    subject { helper.parse_search_result(search_result) }
+
+    it "returns a valid OpenStruct object" do
+      is_expected.to be_an OpenStruct
+      expect(subject.filename).to eq('CHANGELOG')
+      expect(subject.basename).to eq('CHANGELOG')
+      expect(subject.ref).to eq('master')
+      expect(subject.startline).to eq(186)
+      expect(subject.data.lines[2]).to eq("  - Feature: Replace teams with group membership\n")
+    end
+
+    context "when filename has extension" do
+      let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
+
+      it { expect(subject.filename).to eq('CONTRIBUTE.md') }
+      it { expect(subject.basename).to eq('CONTRIBUTE') }
+    end
+
+    context "when file under directory" do
+      let(:search_result) { "master:a/b/c.md:5:a b c\n" }
+
+      it { expect(subject.filename).to eq('a/b/c.md') }
+      it { expect(subject.basename).to eq('a/b/c') }
+    end
+  end
+
   describe 'search_autocomplete_source' do
     context "with no current user" do
       before do
@@ -32,6 +64,10 @@ describe SearchHelper do
         expect(search_autocomplete_opts("adm").size).to eq(1)
       end
 
+      it "does not allow regular expression in search term" do
+        expect(search_autocomplete_opts("(webhooks|api)").size).to eq(0)
+      end
+
       it "includes the user's groups" do
         create(:group).add_owner(user)
         expect(search_autocomplete_opts("gro").size).to eq(1)
diff --git a/spec/helpers/sidekiq_helper_spec.rb b/spec/helpers/sidekiq_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d60839b78ecc782a76760e821a7717134596c7ab
--- /dev/null
+++ b/spec/helpers/sidekiq_helper_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe SidekiqHelper do
+  describe 'parse_sidekiq_ps' do
+    it 'parses line with time' do
+      line = '55137	10,0	2,1	S+	2:30pm	sidekiq 4.1.4 gitlab [0 of 25 busy]   '
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['55137', '10,0', '2,1', 'S+', '2:30pm', 'sidekiq 4.1.4 gitlab [0 of 25 busy]'])
+    end
+
+    it 'parses line with date' do
+      line = '55137	10,0	2,1	S+	Aug 4	sidekiq 4.1.4 gitlab [0 of 25 busy]   '
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['55137', '10,0', '2,1', 'S+', 'Aug 4', 'sidekiq 4.1.4 gitlab [0 of 25 busy]'])
+    end
+
+    it 'parses line with two digit date' do
+      line = '55137	10,0	2,1	S+	Aug 04	sidekiq 4.1.4 gitlab [0 of 25 busy]   '
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['55137', '10,0', '2,1', 'S+', 'Aug 04', 'sidekiq 4.1.4 gitlab [0 of 25 busy]'])
+    end
+
+    it 'parses line with dot as float separator' do
+      line = '55137	10.0	2.1	S+	2:30pm	sidekiq 4.1.4 gitlab [0 of 25 busy]   '
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['55137', '10.0', '2.1', 'S+', '2:30pm', 'sidekiq 4.1.4 gitlab [0 of 25 busy]'])
+    end
+
+    it 'does fail gracefully on line not matching the format' do
+      line = '55137	10.0	2.1	S+	2:30pm	something'
+      parts = helper.parse_sidekiq_ps(line)
+
+      expect(parts).to eq(['?', '?', '?', '?', '?', '?'])
+    end
+  end
+end
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index c1c12b57b532bbc91f3865ab002bcbbe6aa3e6a1..019ce3b07020b45223dd7bc3a7b9eb4a2000bc19 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,13 +1,7 @@
 
 /*= require awards_handler */
-
-
 /*= require jquery */
-
-
 /*= require jquery.cookie */
-
-
 /*= require ./fixtures/emoji_menu */
 
 (function() {
@@ -33,6 +27,7 @@
     return setTimeout(function() {
       assertFn();
       return done();
+    // Maybe jasmine.clock here?
     }, 333);
   };
 
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index 4c52ecd903d6c1b9431423b10003682650972999..13babb5bfdbd5080020a9b33f429a9b7a6c9df49 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -8,6 +8,7 @@
     beforeEach(function() {
       fixture.load('behaviors/quick_submit.html');
       $('form').submit(function(e) {
+        // Prevent a form submit from moving us off the testing page
         return e.preventDefault();
       });
       return this.spies = {
@@ -38,6 +39,8 @@
       expect($('input[type=submit]')).toBeDisabled();
       return expect($('button[type=submit]')).toBeDisabled();
     });
+    // We cannot stub `navigator.userAgent` for CI's `rake teaspoon` task, so we'll
+    // only run the tests that apply to the current platform
     if (navigator.userAgent.match(/Macintosh/)) {
       it('responds to Meta+Enter', function() {
         $('input.quick-submit-input').trigger(keydownEvent());
diff --git a/spec/javascripts/fixtures/comments.html.haml b/spec/javascripts/fixtures/comments.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..cc1f8f15c2188e7705bd56b0b7d1693d7c801a31
--- /dev/null
+++ b/spec/javascripts/fixtures/comments.html.haml
@@ -0,0 +1,21 @@
+.flash-container.timeline-content
+.timeline-icon.hidden-xs.hidden-sm
+  %a.author_link
+    %img
+.timeline-content.timeline-content-form
+  %form.new-note.js-quick-submit.common-note-form.gfm-form.js-main-target-form
+    .md-area
+      .md-header
+      .md-write-holder
+        .zen-backdrop.div-dropzone-wrapper
+          .div-dropzone-wrapper
+            .div-dropzone.dz-clickable
+              %textarea.note-textarea.js-note-text.js-gfm-input.js-autosize.markdown-area
+    .note-form-actions.clearfix
+      %input.btn.btn-nr.btn-create.append-right-10.comment-btn.js-comment-button{ type: 'submit' }
+      %a.btn.btn-nr.btn-reopen.btn-comment.js-note-target-reopen
+        Reopen issue
+      %a.btn.btn-nr.btn-close.btn-comment.js-note-target-close
+        Close issue
+      %a.btn.btn-cancel.js-note-discard
+        Discard draft
\ No newline at end of file
diff --git a/spec/javascripts/fixtures/u2f/authenticate.html.haml b/spec/javascripts/fixtures/u2f/authenticate.html.haml
index 859e79a6c9ede2cfd1b3090588b987ce8872b18d..779d6429a5fe63fbfb25c3d6e5ab02980b9c75c2 100644
--- a/spec/javascripts/fixtures/u2f/authenticate.html.haml
+++ b/spec/javascripts/fixtures/u2f/authenticate.html.haml
@@ -1 +1 @@
-= render partial: "u2f/authenticate", locals: { new_user_session_path: "/users/sign_in" }
+= render partial: "u2f/authenticate", locals: { new_user_session_path: "/users/sign_in", params: {}, resource_name: "user" }
diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
index 82ee1954a597e44a337c68e50ae398b08acf889b..d5401fbb0d1aba8c35577542c0b3291023e5bee4 100644
--- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
@@ -7,7 +7,7 @@ describe("ContributorsGraph", function () {
      expect(ContributorsGraph.prototype.x_domain).toEqual(20)
     })
   })
-  
+
   describe("#set_y_domain", function () {
     it("sets the y_domain", function () {
       ContributorsGraph.set_y_domain([{commits: 30}])
@@ -89,7 +89,7 @@ describe("ContributorsGraph", function () {
 })
 
 describe("ContributorsMasterGraph", function () {
-  
+
   // TODO: fix or remove
   //describe("#process_dates", function () {
     //it("gets and parses dates", function () {
@@ -103,7 +103,7 @@ describe("ContributorsMasterGraph", function () {
       //expect(graph.get_dates).toHaveBeenCalledWith(data)
       //expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get")
     //})
-  //}) 
+  //})
 
   describe("#get_dates", function () {
     it("plucks the date field from data collection", function () {
@@ -124,5 +124,5 @@ describe("ContributorsMasterGraph", function () {
     })
   })
 
-  
+
 })
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index dc6231ebb38469ab22b38d64057775ad5ab469e5..33690c7a5f31371b8a91a62e98fa0708b46b53be 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,7 +1,5 @@
 
 /*= require lib/utils/text_utility */
-
-
 /*= require issue */
 
 (function() {
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 25d3f5b6c04d73fa0bc4499d1e166b0e1173784f..f09596bd36d30f55c3cca13e8fda2e504b8eb790 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -1,7 +1,5 @@
 
 /*= require jquery-ui/autocomplete */
-
-
 /*= require new_branch_form */
 
 (function() {
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 14dc6bfdfdeb865eedb1fb2ebbea00681b5387ef..a588f403dd50cec79aca0e55bb10184f5850b238 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -1,8 +1,7 @@
-
 /*= require notes */
-
-
+/*= require autosize */
 /*= require gl_form */
+/*= require lib/utils/text_utility */
 
 (function() {
   window.gon || (window.gon = {});
@@ -12,29 +11,63 @@
   };
 
   describe('Notes', function() {
-    return describe('task lists', function() {
+    describe('task lists', function() {
       fixture.preload('issue_note.html');
+
       beforeEach(function() {
         fixture.load('issue_note.html');
         $('form').on('submit', function(e) {
-          return e.preventDefault();
+          e.preventDefault();
         });
-        return this.notes = new Notes();
+        this.notes = new Notes();
       });
+
       it('modifies the Markdown field', function() {
         $('input[type=checkbox]').attr('checked', true).trigger('change');
-        return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
+        expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
       });
-      return it('submits the form on tasklist:changed', function() {
-        var submitted;
-        submitted = false;
+
+      it('submits the form on tasklist:changed', function() {
+        var submitted = false;
         $('form').on('submit', function(e) {
           submitted = true;
-          return e.preventDefault();
+          e.preventDefault();
         });
+
         $('.js-task-list-field').trigger('tasklist:changed');
-        return expect(submitted).toBe(true);
+        expect(submitted).toBe(true);
+      });
+    });
+
+    describe('comments', function() {
+      var commentsTemplate = 'comments.html';
+      var textarea = '.js-note-text';
+      fixture.preload(commentsTemplate);
+
+      beforeEach(function() {
+        fixture.load(commentsTemplate);
+        this.notes = new Notes();
+
+        this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update');
+        spyOn(this.notes, 'renderNote').and.stub();
+
+        $(textarea).data('autosave', {
+          reset: function() {}
+        });
+
+        $('form').on('submit', function(e) {
+          e.preventDefault();
+          $('.js-main-target-form').trigger('ajax:success');
+        });
       });
+
+      it('autosizes after comment submission', function() {
+        $(textarea).text('This is an example comment note');
+        expect(this.autoSizeSpy).not.toHaveBeenTriggered();
+
+        $('.js-comment-button').click();
+        expect(this.autoSizeSpy).toHaveBeenTriggered();
+      })
     });
   });
 
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index ffe49828492f2f649e60f67544e54631f7217e64..51eb12b41d4a8bab0160028d4a64a59e619ab001 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -1,22 +1,10 @@
 
 /*= require bootstrap */
-
-
 /*= require select2 */
-
-
 /*= require lib/utils/type_utility */
-
-
 /*= require gl_dropdown */
-
-
 /*= require api */
-
-
 /*= require project_select */
-
-
 /*= require project */
 
 (function() {
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 38b3b2653ecaa4e38e84900af291f61b806de949..c937a4706f7d9f7d16d9382a54763163b8d55564 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -1,10 +1,6 @@
 
 /*= require right_sidebar */
-
-
 /*= require jquery */
-
-
 /*= require jquery.cookie */
 
 (function() {
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 324f5152780af71348dd786b931e8e60e6da5b8f..00d9fc1302af24a72c2980bc78f0a87e2dda5fa5 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -1,19 +1,9 @@
 
 /*= require gl_dropdown */
-
-
 /*= require search_autocomplete */
-
-
 /*= require jquery */
-
-
 /*= require lib/utils/common_utils */
-
-
 /*= require lib/utils/type_utility */
-
-
 /*= require fuzzaldrin-plus */
 
 (function() {
@@ -43,6 +33,8 @@
 
   groupName = 'Gitlab Org';
 
+  // Add required attributes to body before starting the test.
+  // section would be dashboard|group|project
   addBodyAttributes = function(section) {
     var $body;
     if (section == null) {
@@ -64,6 +56,7 @@
     }
   };
 
+  // Mock `gl` object in window for dashboard specific page. App code will need it.
   mockDashboardOptions = function() {
     window.gl || (window.gl = {});
     return window.gl.dashboardOptions = {
@@ -72,6 +65,7 @@
     };
   };
 
+  // Mock `gl` object in window for project specific page. App code will need it.
   mockProjectOptions = function() {
     window.gl || (window.gl = {});
     return window.gl.projectOptions = {
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 7b6b55fe545c385fc2e71a862dd4bbc677732412..04ccf246052843d62e20842349ca1781e5f38cff 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -10,6 +10,7 @@
     });
     return describe('#replyWithSelectedText', function() {
       var stubSelection;
+      // Stub window.getSelection to return the provided String.
       stubSelection = function(text) {
         return window.getSelection = function() {
           return text;
diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js
index 7d91ed0f85582d114558f4b077a54fd710188a07..8801c29788700419fc6d254c65a1e8f17f95fae7 100644
--- a/spec/javascripts/spec_helper.js
+++ b/spec/javascripts/spec_helper.js
@@ -1,21 +1,41 @@
-
+// PhantomJS (Teaspoons default driver) doesn't have support for
+// Function.prototype.bind, which has caused confusion.  Use this polyfill to
+// avoid the confusion.
 /*= require support/bind-poly */
 
-
+// You can require your own javascript files here. By default this will include
+// everything in application, however you may get better load performance if you
+// require the specific files that are being used in the spec that tests them.
 /*= require jquery */
-
-
 /*= require jquery.turbolinks */
-
-
 /*= require bootstrap */
-
-
 /*= require underscore */
 
-
+// Teaspoon includes some support files, but you can use anything from your own
+// support path too.
+// require support/jasmine-jquery-1.7.0
+// require support/jasmine-jquery-2.0.0
 /*= require support/jasmine-jquery-2.1.0 */
 
+// require support/sinon
+// require support/your-support-file
+// Deferring execution
+// If you're using CommonJS, RequireJS or some other asynchronous library you can
+// defer execution. Call Teaspoon.execute() after everything has been loaded.
+// Simple example of a timeout:
+// Teaspoon.defer = true
+// setTimeout(Teaspoon.execute, 1000)
+// Matching files
+// By default Teaspoon will look for files that match
+// _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your spec path
+// and it'll be included in the default suite automatically. If you want to
+// customize suites, check out the configuration in teaspoon_env.rb
+// Manifest
+// If you'd rather require your spec files manually (to control order for
+// instance) you can disable the suite matcher in the configuration and use this
+// file as a manifest.
+// For more information: http://github.com/modeset/teaspoon
+
 (function() {
 
 
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index e008ce956adb5ad65a247c329eb0534e336485fe..7ce3884f8443f24d697bf47087d220e4e3253ee3 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -1,16 +1,8 @@
 
 /*= require u2f/authenticate */
-
-
 /*= require u2f/util */
-
-
 /*= require u2f/error */
-
-
 /*= require u2f */
-
-
 /*= require ./mock_u2f_device */
 
 (function() {
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index 21c5266c60e72f1c0ddd9265e04161cf5127a4ac..01d6b7a8961f847ed437b2a60472553fdb766bf6 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -1,16 +1,8 @@
 
 /*= require u2f/register */
-
-
 /*= require u2f/util */
-
-
 /*= require u2f/error */
-
-
 /*= require u2f */
-
-
 /*= require ./mock_u2f_device */
 
 (function() {
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 3d680ec8ea3f49da8e083f057db5d37bfdcbb339..0c1266800d78477bcc059cb38e3ddd0354d28b5b 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -14,8 +14,10 @@
             return true;
           }
         };
+      // Stub Dropzone.forElement(...).enable()
       });
       this.zen = new ZenMode();
+      // Set this manually because we can't actually scroll the window
       return this.zen.scroll_position = 456;
     });
     describe('on enter', function() {
@@ -60,7 +62,7 @@
     return $('a.js-zen-enter').click();
   };
 
-  exitZen = function() {
+  exitZen = function() { // Ohmmmmmmm
     return $('a.js-zen-leave').click();
   };
 
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 51c89ac4889f0adf0bf3bc078de60299d9b3f510..ac9bde6baf16e9c0a9bee91680974963d7f696c6 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -127,6 +127,13 @@ describe Banzai::Pipeline::WikiPipeline do
 
               expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page.md\"")
             end
+
+            it 'rewrites links with anchor' do
+              markdown = '[Link to Header](start-page#title)'
+              output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+              expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/start-page#title\"")
+            end
           end
 
           describe "when creating root links" do
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index be51d942af7c140d00f5e5459e7fece59469b04c..6dedd25e9d321fb50c0f9365b472b1b7770c392f 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -754,6 +754,20 @@ module Ci
         it 'does return production' do
           expect(builds.size).to eq(1)
           expect(builds.first[:environment]).to eq(environment)
+          expect(builds.first[:options]).to include(environment: { name: environment })
+        end
+      end
+
+      context 'when hash is specified' do
+        let(:environment) do
+          { name: 'production',
+            url: 'http://production.gitlab.com' }
+        end
+
+        it 'does return production and URL' do
+          expect(builds.size).to eq(1)
+          expect(builds.first[:environment]).to eq(environment[:name])
+          expect(builds.first[:options]).to include(environment: environment)
         end
       end
 
@@ -770,15 +784,16 @@ module Ci
         let(:environment) { 1 }
 
         it 'raises error' do
-          expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
+          expect { builds }.to raise_error(
+            'jobs:deploy_to_production:environment config should be a hash or a string')
         end
       end
 
       context 'is not a valid string' do
-        let(:environment) { 'production staging' }
+        let(:environment) { 'production:staging' }
 
         it 'raises error' do
-          expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
+          expect { builds }.to raise_error("jobs:deploy_to_production:environment name #{Gitlab::Regex.environment_name_regex_message}")
         end
       end
     end
@@ -1250,5 +1265,40 @@ EOT
         end
       end
     end
+
+    describe "#validation_message" do
+      context "when the YAML could not be parsed" do
+        it "returns an error about invalid configutaion" do
+          content = YAML.dump("invalid: yaml: test")
+
+          expect(GitlabCiYamlProcessor.validation_message(content))
+            .to eq "Invalid configuration format"
+        end
+      end
+
+      context "when the tags parameter is invalid" do
+        it "returns an error about invalid tags" do
+          content = YAML.dump({ rspec: { script: "test", tags: "mysql" } })
+
+          expect(GitlabCiYamlProcessor.validation_message(content))
+            .to eq "jobs:rspec tags should be an array of strings"
+        end
+      end
+
+      context "when YAML content is empty" do
+        it "returns an error about missing content" do
+          expect(GitlabCiYamlProcessor.validation_message(''))
+            .to eq "Please provide content of .gitlab-ci.yml"
+        end
+      end
+
+      context "when the YAML is valid" do
+        it "does not return any errors" do
+          content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+
+          expect(GitlabCiYamlProcessor.validation_message(content)).to be_nil
+        end
+      end
+    end
   end
 end
diff --git a/spec/lib/ci/mask_secret_spec.rb b/spec/lib/ci/mask_secret_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..518de76911c6716e88e3f248907f34555f0f372d
--- /dev/null
+++ b/spec/lib/ci/mask_secret_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Ci::MaskSecret, lib: true do
+  subject { described_class }
+
+  describe '#mask' do
+    it 'masks exact number of characters' do
+      expect(subject.mask('token', 'oke')).to eq('txxxn')
+    end
+
+    it 'masks multiple occurrences' do
+      expect(subject.mask('token token token', 'oke')).to eq('txxxn txxxn txxxn')
+    end
+
+    it 'does not mask if not found' do
+      expect(subject.mask('token', 'not')).to eq('token')
+    end
+  end
+end
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..90bc7dad3799e42e3d8149a13a42fd34b1526ad9
--- /dev/null
+++ b/spec/lib/expand_variables_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe ExpandVariables do
+  describe '#expand' do
+    subject { described_class.expand(value, variables) }
+
+    tests = [
+      { value: 'key',
+        result: 'key',
+        variables: []
+      },
+      { value: 'key$variable',
+        result: 'key',
+        variables: []
+      },
+      { value: 'key$variable',
+        result: 'keyvalue',
+        variables: [
+          { key: 'variable', value: 'value' }
+        ]
+      },
+      { value: 'key${variable}',
+        result: 'keyvalue',
+        variables: [
+          { key: 'variable', value: 'value' }
+        ]
+      },
+      { value: 'key$variable$variable2',
+        result: 'keyvalueresult',
+        variables: [
+          { key: 'variable', value: 'value' },
+          { key: 'variable2', value: 'result' },
+        ]
+      },
+      { value: 'key${variable}${variable2}',
+        result: 'keyvalueresult',
+        variables: [
+          { key: 'variable', value: 'value' },
+          { key: 'variable2', value: 'result' }
+        ]
+      },
+      { value: 'key$variable2$variable',
+        result: 'keyresultvalue',
+        variables: [
+          { key: 'variable', value: 'value' },
+          { key: 'variable2', value: 'result' },
+        ]
+      },
+      { value: 'key${variable2}${variable}',
+        result: 'keyresultvalue',
+        variables: [
+          { key: 'variable', value: 'value' },
+          { key: 'variable2', value: 'result' }
+        ]
+      },
+      { value: 'review/$CI_BUILD_REF_NAME',
+        result: 'review/feature/add-review-apps',
+        variables: [
+          { key: 'CI_BUILD_REF_NAME', value: 'feature/add-review-apps' }
+        ]
+      },
+    ]
+
+    tests.each do |test|
+      context "#{test[:value]} resolves to #{test[:result]}" do
+        let(:value) { test[:value] }
+        let(:variables) { test[:variables] }
+
+        it { is_expected.to eq(test[:result]) }
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 7c23e02d05af9d4af91cad4b37376e5c69757841..8807a68a0a2e1d712537e17d30a7ae05999e2453 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -4,15 +4,53 @@ describe Gitlab::Auth, lib: true do
   let(:gl_auth) { described_class }
 
   describe 'find_for_git_client' do
-    it 'recognizes CI' do
-      token = '123'
+    context 'build token' do
+      subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') }
+
+      context 'for running build' do
+        let!(:build) { create(:ci_build, :running) }
+        let(:project) { build.project }
+
+        before do
+          expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token')
+        end
+
+        it 'recognises user-less build' do
+          expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities))
+        end
+
+        it 'recognises user token' do
+          build.update(user: create(:user))
+
+          expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities))
+        end
+      end
+
+      (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
+        context "for #{build_status} build" do
+          let!(:build) { create(:ci_build, status: build_status) }
+          let(:project) { build.project }
+
+          before do
+            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token')
+          end
+
+          it 'denies authentication' do
+            expect(subject).to eq(Gitlab::Auth::Result.new)
+          end
+        end
+      end
+    end
+
+    it 'recognizes other ci services' do
       project = create(:empty_project)
-      project.update_attributes(runners_token: token)
+      project.create_drone_ci_service(active: true)
+      project.drone_ci_service.update(token: 'token')
 
       ip = 'ip'
 
-      expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token')
-      expect(gl_auth.find_for_git_client('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci))
+      expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'drone-ci-token')
+      expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities))
     end
 
     it 'recognizes master passwords' do
@@ -20,7 +58,7 @@ describe Gitlab::Auth, lib: true do
       ip = 'ip'
 
       expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
-      expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap))
+      expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
     end
 
     it 'recognizes OAuth tokens' do
@@ -30,7 +68,7 @@ describe Gitlab::Auth, lib: true do
       ip = 'ip'
 
       expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2')
-      expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth))
+      expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
     end
 
     it 'returns double nil for invalid credentials' do
@@ -92,4 +130,30 @@ describe Gitlab::Auth, lib: true do
       end
     end
   end
+
+  private
+
+  def build_authentication_abilities
+    [
+      :read_project,
+      :build_download_code,
+      :build_read_container_image,
+      :build_create_container_image
+    ]
+  end
+
+  def read_authentication_abilities
+    [
+      :read_project,
+      :download_code,
+      :read_container_image
+    ]
+  end
+
+  def full_authentication_abilities
+    read_authentication_abilities + [
+      :push_code,
+      :create_container_image
+    ]
+  end
 end
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index 6e5ba21138210ba34d9212c30d6fc5bae044229e..07407f212aadaaef6f582bca01cd90774a003b72 100644
--- a/spec/lib/gitlab/backend/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -1,4 +1,5 @@
 require 'spec_helper'
+require 'stringio'
 
 describe Gitlab::Shell, lib: true do
   let(:project) { double('Project', id: 7, path: 'diaspora') }
@@ -44,15 +45,38 @@ describe Gitlab::Shell, lib: true do
     end
   end
 
+  describe '#add_key' do
+    it 'removes trailing garbage' do
+      allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+      expect(Gitlab::Utils).to receive(:system_silent).with(
+        [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
+      )
+
+      gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+    end
+  end
+
   describe Gitlab::Shell::KeyAdder, lib: true do
     describe '#add_key' do
-      it 'normalizes space characters in the key' do
-        io = spy
+      it 'removes trailing garbage' do
+        io = spy(:io)
         adder = described_class.new(io)
 
-        adder.add_key('key-42', "sha-rsa foo\tbar\tbaz")
+        adder.add_key('key-42', "ssh-rsa foo bar\tbaz")
+
+        expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
+      end
+
+      it 'raises an exception if the key contains a tab' do
+        expect do
+          described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar")
+        end.to raise_error(Gitlab::Shell::Error)
+      end
 
-        expect(io).to have_received(:puts).with("key-42\tsha-rsa foo bar baz")
+      it 'raises an exception if the key contains a newline' do
+        expect do
+          described_class.new(StringIO.new).add_key('key-42', "ssh-rsa foobar\nssh-rsa pawned")
+        end.to raise_error(Gitlab::Shell::Error)
       end
     end
   end
diff --git a/spec/lib/gitlab/ci/config/node/cache_spec.rb b/spec/lib/gitlab/ci/config/node/cache_spec.rb
index 50f619ce26e6c1f96d79aa7f644828e93d4bd3ce..e251210949cf7cf572c2607eb3fb96bc125e35e7 100644
--- a/spec/lib/gitlab/ci/config/node/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/cache_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Cache do
   let(:entry) { described_class.new(config) }
 
   describe 'validations' do
-    before { entry.process! }
+    before { entry.compose! }
 
     context 'when entry config value is correct' do
       let(:config) do
diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..df453223da731f7f64109393860fda9a24df5eb3
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb
@@ -0,0 +1,155 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Environment do
+  let(:entry) { described_class.new(config) }
+
+  before { entry.compose! }
+
+  context 'when configuration is a string' do
+    let(:config) { 'production' }
+
+    describe '#string?' do
+      it 'is string configuration' do
+        expect(entry).to be_string
+      end
+    end
+
+    describe '#hash?' do
+      it 'is not hash configuration' do
+        expect(entry).not_to be_hash
+      end
+    end
+
+    describe '#valid?' do
+      it 'is valid' do
+        expect(entry).to be_valid
+      end
+    end
+
+    describe '#value' do
+      it 'returns valid hash' do
+        expect(entry.value).to eq(name: 'production')
+      end
+    end
+
+    describe '#name' do
+      it 'returns environment name' do
+        expect(entry.name).to eq 'production'
+      end
+    end
+
+    describe '#url' do
+      it 'returns environment url' do
+        expect(entry.url).to be_nil
+      end
+    end
+  end
+
+  context 'when configuration is a hash' do
+    let(:config) do
+      { name: 'development', url: 'https://example.gitlab.com' }
+    end
+
+    describe '#string?' do
+      it 'is not string configuration' do
+        expect(entry).not_to be_string
+      end
+    end
+
+    describe '#hash?' do
+      it 'is hash configuration' do
+        expect(entry).to be_hash
+      end
+    end
+
+    describe '#valid?' do
+      it 'is valid' do
+        expect(entry).to be_valid
+      end
+    end
+
+    describe '#value' do
+      it 'returns valid hash' do
+        expect(entry.value).to eq config
+      end
+    end
+
+    describe '#name' do
+      it 'returns environment name' do
+        expect(entry.name).to eq 'development'
+      end
+    end
+
+    describe '#url' do
+      it 'returns environment url' do
+        expect(entry.url).to eq 'https://example.gitlab.com'
+      end
+    end
+  end
+
+  context 'when variables are used for environment' do
+    let(:config) do
+      { name: 'review/$CI_BUILD_REF_NAME',
+        url: 'https://$CI_BUILD_REF_NAME.review.gitlab.com' }
+    end
+
+    describe '#valid?' do
+      it 'is valid' do
+        expect(entry).to be_valid
+      end
+    end
+  end
+
+  context 'when configuration is invalid' do
+    context 'when configuration is an array' do
+      let(:config) { ['env'] }
+
+      describe '#valid?' do
+        it 'is not valid' do
+          expect(entry).not_to be_valid
+        end
+      end
+
+      describe '#errors' do
+        it 'contains error about invalid type' do
+          expect(entry.errors)
+            .to include 'environment config should be a hash or a string'
+        end
+      end
+    end
+
+    context 'when environment name is not present' do
+      let(:config) { { url: 'https://example.gitlab.com' } }
+
+      describe '#valid?' do
+        it 'is not valid' do
+          expect(entry).not_to be_valid
+        end
+      end
+
+      describe '#errors?' do
+        it 'contains error about missing environment name' do
+          expect(entry.errors)
+            .to include "environment name can't be blank"
+        end
+      end
+    end
+
+    context 'when invalid URL is used' do
+      let(:config) { { name: 'test', url: 'invalid-example.gitlab.com' } }
+
+      describe '#valid?' do
+        it 'is not valid' do
+          expect(entry).not_to be_valid
+        end
+      end
+
+      describe '#errors?' do
+        it 'contains error about invalid URL' do
+          expect(entry.errors)
+            .to include "environment url must be a valid url"
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb
index d26185ba585c32a1606369cf60e3dee8228e23cc..a699089c56384add4c64d72f7c39d5259403f1db 100644
--- a/spec/lib/gitlab/ci/config/node/factory_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb
@@ -65,7 +65,8 @@ describe Gitlab::Ci::Config::Node::Factory do
           .value(nil)
           .create!
 
-        expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
+        expect(entry)
+          .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified
       end
     end
 
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index 2f87d270b36a4773fbbba4d47e2f6d2dd5236d19..12232ff7e2ff9384b7fa0a9da6747a97faf20f5a 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Config::Node::Global do
   end
 
   context 'when hash is valid' do
-    context 'when all entries defined' do
+    context 'when some entries defined' do
       let(:hash) do
         { before_script: ['ls', 'pwd'],
           image: 'ruby:2.2',
@@ -24,11 +24,11 @@ describe Gitlab::Ci::Config::Node::Global do
           stages: ['build', 'pages'],
           cache: { key: 'k', untracked: true, paths: ['public/'] },
           rspec: { script: %w[rspec ls] },
-          spinach: { script: 'spinach' } }
+          spinach: { before_script: [], variables: {}, script: 'spinach' } }
       end
 
-      describe '#process!' do
-        before { global.process! }
+      describe '#compose!' do
+        before { global.compose! }
 
         it 'creates nodes hash' do
           expect(global.descendants).to be_an Array
@@ -59,7 +59,7 @@ describe Gitlab::Ci::Config::Node::Global do
         end
       end
 
-      context 'when not processed' do
+      context 'when not composed' do
         describe '#before_script' do
           it 'returns nil' do
             expect(global.before_script).to be nil
@@ -73,8 +73,14 @@ describe Gitlab::Ci::Config::Node::Global do
         end
       end
 
-      context 'when processed' do
-        before { global.process! }
+      context 'when composed' do
+        before { global.compose! }
+
+        describe '#errors' do
+          it 'has no errors' do
+            expect(global.errors).to be_empty
+          end
+        end
 
         describe '#before_script' do
           it 'returns correct script' do
@@ -137,10 +143,24 @@ describe Gitlab::Ci::Config::Node::Global do
             expect(global.jobs).to eq(
               rspec: { name: :rspec,
                        script: %w[rspec ls],
-                       stage: 'test' },
+                       before_script: ['ls', 'pwd'],
+                       commands: "ls\npwd\nrspec\nls",
+                       image: 'ruby:2.2',
+                       services: ['postgres:9.1', 'mysql:5.5'],
+                       stage: 'test',
+                       cache: { key: 'k', untracked: true, paths: ['public/'] },
+                       variables: { VAR: 'value' },
+                       after_script: ['make clean'] },
               spinach: { name: :spinach,
+                         before_script: [],
                          script: %w[spinach],
-                         stage: 'test' }
+                         commands: 'spinach',
+                         image: 'ruby:2.2',
+                         services: ['postgres:9.1', 'mysql:5.5'],
+                         stage: 'test',
+                         cache: { key: 'k', untracked: true, paths: ['public/'] },
+                         variables: {},
+                         after_script: ['make clean'] },
             )
           end
         end
@@ -148,17 +168,20 @@ describe Gitlab::Ci::Config::Node::Global do
     end
 
     context 'when most of entires not defined' do
-      let(:hash) { { cache: { key: 'a' }, rspec: { script: %w[ls] } } }
-      before { global.process! }
+      before { global.compose! }
+
+      let(:hash) do
+        { cache: { key: 'a' }, rspec: { script: %w[ls] } }
+      end
 
       describe '#nodes' do
         it 'instantizes all nodes' do
           expect(global.descendants.count).to eq 8
         end
 
-        it 'contains undefined nodes' do
+        it 'contains unspecified nodes' do
           expect(global.descendants.first)
-            .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
+            .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified
         end
       end
 
@@ -188,8 +211,11 @@ describe Gitlab::Ci::Config::Node::Global do
     # details.
     #
     context 'when entires specified but not defined' do
-      let(:hash) { { variables: nil, rspec: { script: 'rspec' } } }
-      before { global.process! }
+      before { global.compose! }
+
+      let(:hash) do
+        { variables: nil, rspec: { script: 'rspec' } }
+      end
 
       describe '#variables' do
         it 'undefined entry returns a default value' do
@@ -200,7 +226,7 @@ describe Gitlab::Ci::Config::Node::Global do
   end
 
   context 'when hash is not valid' do
-    before { global.process! }
+    before { global.compose! }
 
     let(:hash) do
       { before_script: 'ls' }
@@ -247,4 +273,27 @@ describe Gitlab::Ci::Config::Node::Global do
       expect(global.specified?).to be true
     end
   end
+
+  describe '#[]' do
+    before { global.compose! }
+
+    let(:hash) do
+      { cache: { key: 'a' }, rspec: { script: 'ls' } }
+    end
+
+    context 'when node exists' do
+      it 'returns correct entry' do
+        expect(global[:cache])
+          .to be_an_instance_of Gitlab::Ci::Config::Node::Cache
+        expect(global[:jobs][:rspec][:script].value).to eq ['ls']
+      end
+    end
+
+    context 'when node does not exist' do
+      it 'always return unspecified node' do
+        expect(global[:some][:unknown][:node])
+          .not_to be_specified
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb
index 1484fb60dd81eedc8e4a2416f44c119118381151..91f676dae03325136b71851e38205d9a66c167dc 100644
--- a/spec/lib/gitlab/ci/config/node/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/job_spec.rb
@@ -3,9 +3,9 @@ require 'spec_helper'
 describe Gitlab::Ci::Config::Node::Job do
   let(:entry) { described_class.new(config, name: :rspec) }
 
-  before { entry.process! }
-
   describe 'validations' do
+    before { entry.compose! }
+
     context 'when entry config value is correct' do
       let(:config) { { script: 'rspec' } }
 
@@ -59,28 +59,82 @@ describe Gitlab::Ci::Config::Node::Job do
     end
   end
 
-  describe '#value' do
-    context 'when entry is correct' do
+  describe '#relevant?' do
+    it 'is a relevant entry' do
+      expect(entry).to be_relevant
+    end
+  end
+
+  describe '#compose!' do
+    let(:unspecified) { double('unspecified', 'specified?' => false) }
+
+    let(:specified) do
+      double('specified', 'specified?' => true, value: 'specified')
+    end
+
+    let(:deps) { double('deps', '[]' => unspecified) }
+
+    context 'when job config overrides global config' do
+      before { entry.compose!(deps) }
+
       let(:config) do
-        { before_script: %w[ls pwd],
-          script: 'rspec',
-          after_script: %w[cleanup] }
+        { image: 'some_image', cache: { key: 'test' } }
+      end
+
+      it 'overrides global config' do
+        expect(entry[:image].value).to eq 'some_image'
+        expect(entry[:cache].value).to eq(key: 'test')
+      end
+    end
+
+    context 'when job config does not override global config' do
+      before do
+        allow(deps).to receive('[]').with(:image).and_return(specified)
+        entry.compose!(deps)
       end
 
-      it 'returns correct value' do
-        expect(entry.value)
-          .to eq(name: :rspec,
-                 before_script: %w[ls pwd],
-                 script: %w[rspec],
-                 stage: 'test',
-                 after_script: %w[cleanup])
+      let(:config) { { script: 'ls', cache: { key: 'test' } } }
+
+      it 'uses config from global entry' do
+        expect(entry[:image].value).to eq 'specified'
+        expect(entry[:cache].value).to eq(key: 'test')
       end
     end
   end
 
-  describe '#relevant?' do
-    it 'is a relevant entry' do
-      expect(entry).to be_relevant
+  context 'when composed' do
+    before { entry.compose! }
+
+    describe '#value' do
+      before { entry.compose! }
+
+      context 'when entry is correct' do
+        let(:config) do
+          { before_script: %w[ls pwd],
+            script: 'rspec',
+            after_script: %w[cleanup] }
+        end
+
+        it 'returns correct value' do
+          expect(entry.value)
+            .to eq(name: :rspec,
+                   before_script: %w[ls pwd],
+                   script: %w[rspec],
+                   commands: "ls\npwd\nrspec",
+                   stage: 'test',
+                   after_script: %w[cleanup])
+        end
+      end
+    end
+
+    describe '#commands' do
+      let(:config) do
+        { before_script: %w[ls pwd], script: 'rspec' }
+      end
+
+      it 'returns a string of commands concatenated with new line character' do
+        expect(entry.commands).to eq "ls\npwd\nrspec"
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
index ae2c88aac3706c216a37c3061745ce094bfdc420..929809339ef54f5bb922798f534135d7ad46ee13 100644
--- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
   let(:entry) { described_class.new(config) }
 
   describe 'validations' do
-    before { entry.process! }
+    before { entry.compose! }
 
     context 'when entry config value is correct' do
       let(:config) { { rspec: { script: 'rspec' } } }
@@ -47,8 +47,8 @@ describe Gitlab::Ci::Config::Node::Jobs do
     end
   end
 
-  context 'when valid job entries processed' do
-    before { entry.process! }
+  context 'when valid job entries composed' do
+    before { entry.compose! }
 
     let(:config) do
       { rspec: { script: 'rspec' },
@@ -61,9 +61,11 @@ describe Gitlab::Ci::Config::Node::Jobs do
         expect(entry.value).to eq(
           rspec: { name: :rspec,
                    script: %w[rspec],
+                   commands: 'rspec',
                    stage: 'test' },
           spinach: { name: :spinach,
                      script: %w[spinach],
+                     commands: 'spinach',
                      stage: 'test' })
       end
     end
diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb
deleted file mode 100644
index 1ab5478dcfa01d2380c379391877f1d44030fa64..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/ci/config/node/null_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Ci::Config::Node::Null do
-  let(:null) { described_class.new(nil) }
-
-  describe '#leaf?' do
-    it 'is leaf node' do
-      expect(null).to be_leaf
-    end
-  end
-
-  describe '#valid?' do
-    it 'is always valid' do
-      expect(null).to be_valid
-    end
-  end
-
-  describe '#errors' do
-    it 'is does not contain errors' do
-      expect(null.errors).to be_empty
-    end
-  end
-
-  describe '#value' do
-    it 'returns nil' do
-      expect(null.value).to eq nil
-    end
-  end
-
-  describe '#relevant?' do
-    it 'is not relevant' do
-      expect(null.relevant?).to eq false
-    end
-  end
-
-  describe '#specified?' do
-    it 'is not defined' do
-      expect(null.specified?).to eq false
-    end
-  end
-end
diff --git a/spec/lib/gitlab/ci/config/node/script_spec.rb b/spec/lib/gitlab/ci/config/node/script_spec.rb
index ee7395362a96dc3978e4f77af87b7274a6fcc5d4..219a7e981d3b9fe22a5a1789caf0acca97cddd7f 100644
--- a/spec/lib/gitlab/ci/config/node/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/script_spec.rb
@@ -3,9 +3,7 @@ require 'spec_helper'
 describe Gitlab::Ci::Config::Node::Script do
   let(:entry) { described_class.new(config) }
 
-  describe '#process!' do
-    before { entry.process! }
-
+  describe 'validations' do
     context 'when entry config value is correct' do
       let(:config) { ['ls', 'pwd'] }
 
diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
index 2d43e1c1a9d6477f421d9710b0f666efacdba62d..6bde86029631f9fa4e789213eef36c94f2c10e20 100644
--- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
@@ -1,32 +1,41 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Config::Node::Undefined do
-  let(:undefined) { described_class.new(entry) }
-  let(:entry) { spy('Entry') }
+  let(:entry) { described_class.new }
+
+  describe '#leaf?' do
+    it 'is leaf node' do
+      expect(entry).to be_leaf
+    end
+  end
 
   describe '#valid?' do
-    it 'delegates method to entry' do
-      expect(undefined.valid).to eq entry
+    it 'is always valid' do
+      expect(entry).to be_valid
     end
   end
 
   describe '#errors' do
-    it 'delegates method to entry' do
-      expect(undefined.errors).to eq entry
+    it 'is does not contain errors' do
+      expect(entry.errors).to be_empty
     end
   end
 
   describe '#value' do
-    it 'delegates method to entry' do
-      expect(undefined.value).to eq entry
+    it 'returns nil' do
+      expect(entry.value).to eq nil
     end
   end
 
-  describe '#specified?' do
-    it 'is always false' do
-      allow(entry).to receive(:specified?).and_return(true)
+  describe '#relevant?' do
+    it 'is not relevant' do
+      expect(entry.relevant?).to eq false
+    end
+  end
 
-      expect(undefined.specified?).to be false
+  describe '#specified?' do
+    it 'is not defined' do
+      expect(entry.specified?).to eq false
     end
   end
 end
diff --git a/spec/lib/gitlab/ci/config/node/unspecified_spec.rb b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ba3ceef24ceff0a3d4a73711e5d04c375ff7f142
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Unspecified do
+  let(:unspecified) { described_class.new(entry) }
+  let(:entry) { spy('Entry') }
+
+  describe '#valid?' do
+    it 'delegates method to entry' do
+      expect(unspecified.valid?).to eq entry
+    end
+  end
+
+  describe '#errors' do
+    it 'delegates method to entry' do
+      expect(unspecified.errors).to eq entry
+    end
+  end
+
+  describe '#value' do
+    it 'delegates method to entry' do
+      expect(unspecified.value).to eq entry
+    end
+  end
+
+  describe '#specified?' do
+    it 'is always false' do
+      allow(entry).to receive(:specified?).and_return(true)
+
+      expect(unspecified.specified?).to be false
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b26728a843cc7993f91eb2bac62f857db2b1ca83
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb
@@ -0,0 +1,115 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::PipelineDuration do
+  let(:calculated_duration) { calculate(data) }
+
+  shared_examples 'calculating duration' do
+    it do
+      expect(calculated_duration).to eq(duration)
+    end
+  end
+
+  context 'test sample A' do
+    let(:data) do
+      [[0, 1],
+       [1, 2],
+       [3, 4],
+       [5, 6]]
+    end
+
+    let(:duration) { 4 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample B' do
+    let(:data) do
+      [[0, 1],
+       [1, 2],
+       [2, 3],
+       [3, 4],
+       [0, 4]]
+    end
+
+    let(:duration) { 4 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample C' do
+    let(:data) do
+      [[0, 4],
+       [2, 6],
+       [5, 7],
+       [8, 9]]
+    end
+
+    let(:duration) { 8 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample D' do
+    let(:data) do
+      [[0, 1],
+       [2, 3],
+       [4, 5],
+       [6, 7]]
+    end
+
+    let(:duration) { 4 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample E' do
+    let(:data) do
+      [[0, 1],
+       [3, 9],
+       [3, 4],
+       [3, 5],
+       [3, 8],
+       [4, 5],
+       [4, 7],
+       [5, 8]]
+    end
+
+    let(:duration) { 7 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample F' do
+    let(:data) do
+      [[1, 3],
+       [2, 4],
+       [2, 4],
+       [2, 4],
+       [5, 8]]
+    end
+
+    let(:duration) { 6 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  context 'test sample G' do
+    let(:data) do
+      [[1, 3],
+       [2, 4],
+       [6, 7]]
+    end
+
+    let(:duration) { 4 }
+
+    it_behaves_like 'calculating duration'
+  end
+
+  def calculate(data)
+    periods = data.shuffle.map do |(first, last)|
+      Gitlab::Ci::PipelineDuration::Period.new(first, last)
+    end
+
+    Gitlab::Ci::PipelineDuration.from_periods(periods.sort_by(&:first))
+  end
+end
diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb
index a1d2ca1e27263080b4c883818e36447e1993c191..16eb376635644762dcb29f047b5a3149161f33f1 100644
--- a/spec/lib/gitlab/conflict/parser_spec.rb
+++ b/spec/lib/gitlab/conflict/parser_spec.rb
@@ -179,8 +179,8 @@ CONFLICT
           to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
       end
 
-      it 'raises UnmergeableFile when the file is over 100 KB' do
-        expect { parse_text('a' * 102401) }.
+      it 'raises UnmergeableFile when the file is over 200 KB' do
+        expect { parse_text('a' * 204801) }.
           to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
       end
 
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 4ec3f19e03fb55bb2c3714718d8df5e4976e0c35..7fd25b9e5bf868c21876f17f15fc358a0628beef 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -91,63 +91,80 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
 
   describe '#add_column_with_default' do
     context 'outside of a transaction' do
-      before do
-        expect(model).to receive(:transaction_open?).and_return(false)
+      context 'when a column limit is not set' do
+        before do
+          expect(model).to receive(:transaction_open?).and_return(false)
 
-        expect(model).to receive(:transaction).and_yield
+          expect(model).to receive(:transaction).and_yield
 
-        expect(model).to receive(:add_column).
-          with(:projects, :foo, :integer, default: nil)
+          expect(model).to receive(:add_column).
+            with(:projects, :foo, :integer, default: nil)
 
-        expect(model).to receive(:change_column_default).
-          with(:projects, :foo, 10)
-      end
+          expect(model).to receive(:change_column_default).
+            with(:projects, :foo, 10)
+        end
 
-      it 'adds the column while allowing NULL values' do
-        expect(model).to receive(:update_column_in_batches).
-          with(:projects, :foo, 10)
+        it 'adds the column while allowing NULL values' do
+          expect(model).to receive(:update_column_in_batches).
+            with(:projects, :foo, 10)
 
-        expect(model).not_to receive(:change_column_null)
+          expect(model).not_to receive(:change_column_null)
 
-        model.add_column_with_default(:projects, :foo, :integer,
-                                      default: 10,
-                                      allow_null: true)
-      end
+          model.add_column_with_default(:projects, :foo, :integer,
+                                        default: 10,
+                                        allow_null: true)
+        end
 
-      it 'adds the column while not allowing NULL values' do
-        expect(model).to receive(:update_column_in_batches).
-          with(:projects, :foo, 10)
+        it 'adds the column while not allowing NULL values' do
+          expect(model).to receive(:update_column_in_batches).
+            with(:projects, :foo, 10)
 
-        expect(model).to receive(:change_column_null).
-          with(:projects, :foo, false)
+          expect(model).to receive(:change_column_null).
+            with(:projects, :foo, false)
 
-        model.add_column_with_default(:projects, :foo, :integer, default: 10)
-      end
+          model.add_column_with_default(:projects, :foo, :integer, default: 10)
+        end
 
-      it 'removes the added column whenever updating the rows fails' do
-        expect(model).to receive(:update_column_in_batches).
-          with(:projects, :foo, 10).
-          and_raise(RuntimeError)
+        it 'removes the added column whenever updating the rows fails' do
+          expect(model).to receive(:update_column_in_batches).
+            with(:projects, :foo, 10).
+            and_raise(RuntimeError)
 
-        expect(model).to receive(:remove_column).
-          with(:projects, :foo)
+          expect(model).to receive(:remove_column).
+            with(:projects, :foo)
 
-        expect do
-          model.add_column_with_default(:projects, :foo, :integer, default: 10)
-        end.to raise_error(RuntimeError)
+          expect do
+            model.add_column_with_default(:projects, :foo, :integer, default: 10)
+          end.to raise_error(RuntimeError)
+        end
+
+        it 'removes the added column whenever changing a column NULL constraint fails' do
+          expect(model).to receive(:change_column_null).
+            with(:projects, :foo, false).
+            and_raise(RuntimeError)
+
+          expect(model).to receive(:remove_column).
+            with(:projects, :foo)
+
+          expect do
+            model.add_column_with_default(:projects, :foo, :integer, default: 10)
+          end.to raise_error(RuntimeError)
+        end
       end
 
-      it 'removes the added column whenever changing a column NULL constraint fails' do
-        expect(model).to receive(:change_column_null).
-          with(:projects, :foo, false).
-          and_raise(RuntimeError)
+      context 'when a column limit is set' do
+        it 'adds the column with a limit' do
+          allow(model).to receive(:transaction_open?).and_return(false)
+          allow(model).to receive(:transaction).and_yield
+          allow(model).to receive(:update_column_in_batches).with(:projects, :foo, 10)
+          allow(model).to receive(:change_column_null).with(:projects, :foo, false)
+          allow(model).to receive(:change_column_default).with(:projects, :foo, 10)
 
-        expect(model).to receive(:remove_column).
-          with(:projects, :foo)
+          expect(model).to receive(:add_column).
+            with(:projects, :foo, :integer, default: nil, limit: 8)
 
-        expect do
-          model.add_column_with_default(:projects, :foo, :integer, default: 10)
-        end.to raise_error(RuntimeError)
+          model.add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
+        end
       end
     end
 
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index f12c9a370f741c827e03e726ea17eb7d2947e8d7..ed43646330f6c9cce10985ea0b40065c2156b777 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -1,10 +1,17 @@
 require 'spec_helper'
 
 describe Gitlab::GitAccess, lib: true do
-  let(:access) { Gitlab::GitAccess.new(actor, project, 'web') }
+  let(:access) { Gitlab::GitAccess.new(actor, project, 'web', authentication_abilities: authentication_abilities) }
   let(:project) { create(:project) }
   let(:user) { create(:user) }
   let(:actor) { user }
+  let(:authentication_abilities) do
+    [
+      :read_project,
+      :download_code,
+      :push_code
+    ]
+  end
 
   describe '#check with single protocols allowed' do
     def disable_protocol(protocol)
@@ -15,7 +22,7 @@ describe Gitlab::GitAccess, lib: true do
     context 'ssh disabled' do
       before do
         disable_protocol('ssh')
-        @acc = Gitlab::GitAccess.new(actor, project, 'ssh')
+        @acc = Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities)
       end
 
       it 'blocks ssh git push' do
@@ -30,7 +37,7 @@ describe Gitlab::GitAccess, lib: true do
     context 'http disabled' do
       before do
         disable_protocol('http')
-        @acc = Gitlab::GitAccess.new(actor, project, 'http')
+        @acc = Gitlab::GitAccess.new(actor, project, 'http', authentication_abilities: authentication_abilities)
       end
 
       it 'blocks http push' do
@@ -111,6 +118,36 @@ describe Gitlab::GitAccess, lib: true do
         end
       end
     end
+
+    describe 'build authentication_abilities permissions' do
+      let(:authentication_abilities) { build_authentication_abilities }
+
+      describe 'reporter user' do
+        before { project.team << [user, :reporter] }
+
+        context 'pull code' do
+          it { expect(subject).to be_allowed }
+        end
+      end
+
+      describe 'admin user' do
+        let(:user) { create(:admin) }
+
+        context 'when member of the project' do
+          before { project.team << [user, :reporter] }
+
+          context 'pull code' do
+            it { expect(subject).to be_allowed }
+          end
+        end
+
+        context 'when is not member of the project' do
+          context 'pull code' do
+            it { expect(subject).not_to be_allowed }
+          end
+        end
+      end
+    end
   end
 
   describe 'push_access_check' do
@@ -283,38 +320,71 @@ describe Gitlab::GitAccess, lib: true do
     end
   end
 
-  describe 'deploy key permissions' do
-    let(:key) { create(:deploy_key) }
-    let(:actor) { key }
+  shared_examples 'can not push code' do
+    subject { access.check('git-receive-pack', '_any') }
+
+    context 'when project is authorized' do
+      before { authorize }
 
-    context 'push code' do
-      subject { access.check('git-receive-pack', '_any') }
+      it { expect(subject).not_to be_allowed }
+    end
 
-      context 'when project is authorized' do
-        before { key.projects << project }
+    context 'when unauthorized' do
+      context 'to public project' do
+        let(:project) { create(:project, :public) }
 
         it { expect(subject).not_to be_allowed }
       end
 
-      context 'when unauthorized' do
-        context 'to public project' do
-          let(:project) { create(:project, :public) }
+      context 'to internal project' do
+        let(:project) { create(:project, :internal) }
 
-          it { expect(subject).not_to be_allowed }
-        end
+        it { expect(subject).not_to be_allowed }
+      end
 
-        context 'to internal project' do
-          let(:project) { create(:project, :internal) }
+      context 'to private project' do
+        let(:project) { create(:project, :internal) }
 
-          it { expect(subject).not_to be_allowed }
-        end
+        it { expect(subject).not_to be_allowed }
+      end
+    end
+  end
 
-        context 'to private project' do
-          let(:project) { create(:project, :internal) }
+  describe 'build authentication abilities' do
+    let(:authentication_abilities) { build_authentication_abilities }
 
-          it { expect(subject).not_to be_allowed }
-        end
+    it_behaves_like 'can not push code' do
+      def authorize
+        project.team << [user, :reporter]
       end
     end
   end
+
+  describe 'deploy key permissions' do
+    let(:key) { create(:deploy_key) }
+    let(:actor) { key }
+
+    it_behaves_like 'can not push code' do
+      def authorize
+        key.projects << project
+      end
+    end
+  end
+
+  private
+
+  def build_authentication_abilities
+    [
+      :read_project,
+      :build_download_code
+    ]
+  end
+
+  def full_authentication_abilities
+    [
+      :read_project,
+      :download_code,
+      :push_code
+    ]
+  end
 end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 4244b807d416a074c11383bcc361502632e7f6ec..576cda595bb1fc5f8a6c5b30a1b4a381c8f7758f 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -1,9 +1,16 @@
 require 'spec_helper'
 
 describe Gitlab::GitAccessWiki, lib: true do
-  let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web') }
+  let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) }
   let(:project) { create(:project) }
   let(:user) { create(:user) }
+  let(:authentication_abilities) do
+    [
+      :read_project,
+      :download_code,
+      :push_code
+    ]
+  end
 
   describe 'push_allowed?' do
     before do
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
index 9ae02a6c45fbb047f08a2b4836e1af86d2f68c32..c520a9c53ad7d4253b9acf6acecd6d2037e3e3e6 100644
--- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -73,6 +73,12 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
         gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
         expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
       end
+
+      it 'returns note without created at tag line' do
+        create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+        expect(comment.attributes.fetch(:note)).to eq("I'm having a problem with this.")
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index 3fb8de8154581e28ff7d74e58f630d2498d338a4..553c849c9b4a9bdd13a398c2e3c9058784665e9f 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
       let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
       let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
 
-      let(:label) do
+      let(:label1) do
         double(
           name: 'Bug',
           color: 'ff0000',
@@ -21,6 +21,14 @@ describe Gitlab::GithubImport::Importer, lib: true do
         )
       end
 
+      let(:label2) do
+        double(
+          name: nil,
+          color: 'ff0000',
+          url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug'
+        )
+      end
+
       let(:milestone) do
         double(
           number: 1347,
@@ -90,14 +98,39 @@ describe Gitlab::GithubImport::Importer, lib: true do
         )
       end
 
+      let(:release1) do
+        double(
+          tag_name: 'v1.0.0',
+          name: 'First release',
+          body: 'Release v1.0.0',
+          draft: false,
+          created_at: created_at,
+          updated_at: updated_at,
+          url: 'https://api.github.com/repos/octocat/Hello-World/releases/1'
+        )
+      end
+
+      let(:release2) do
+        double(
+          tag_name: 'v2.0.0',
+          name: 'Second release',
+          body: nil,
+          draft: false,
+          created_at: created_at,
+          updated_at: updated_at,
+          url: 'https://api.github.com/repos/octocat/Hello-World/releases/2'
+        )
+      end
+
       before do
         allow(project).to receive(:import_data).and_return(double.as_null_object)
         allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
-        allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label, label])
+        allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
         allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
         allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
         allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
         allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
+        allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
         allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
       end
 
@@ -113,14 +146,15 @@ describe Gitlab::GithubImport::Importer, lib: true do
         error = {
           message: 'The remote data could not be fully imported.',
           errors: [
-            { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title has already been taken" },
+            { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
             { type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" },
             { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." },
             { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" },
             { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." },
             { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" },
-            { type: :wiki, errors: "Gitlab::Shell::Error" }
-          ]
+            { type: :wiki, errors: "Gitlab::Shell::Error" },
+            { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" }
+        ]
         }
 
         described_class.new(project).execute
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
index d60c4111e99faa6357c0a09e7f1448bfb5e8bdf9..c2f1f6b91a112a08e89555243d2194c11c0ca971 100644
--- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -109,6 +109,12 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
 
         expect(issue.attributes.fetch(:author_id)).to eq gl_user.id
       end
+
+      it 'returns description without created at tag line' do
+        create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+        expect(issue.attributes.fetch(:description)).to eq("I'm having a problem with this.")
+      end
     end
   end
 
diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb
index 87593e32db0dc9282d240b1fcef84d0ea4dfad66..8098754d735f775b321b88710528ca8933fd2024 100644
--- a/spec/lib/gitlab/github_import/label_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb
@@ -1,18 +1,34 @@
 require 'spec_helper'
 
 describe Gitlab::GithubImport::LabelFormatter, lib: true do
-  describe '#attributes' do
-    it 'returns formatted attributes' do
-      project = create(:project)
-      raw = double(name: 'improvements', color: 'e6e6e6')
+  let(:project) { create(:project) }
+  let(:raw) { double(name: 'improvements', color: 'e6e6e6') }
 
-      formatter = described_class.new(project, raw)
+  subject { described_class.new(project, raw) }
 
-      expect(formatter.attributes).to eq({
+  describe '#attributes' do
+    it 'returns formatted attributes' do
+      expect(subject.attributes).to eq({
         project: project,
         title: 'improvements',
         color: '#e6e6e6'
       })
     end
   end
+
+  describe '#create!' do
+    context 'when label does not exist' do
+      it 'creates a new label' do
+        expect { subject.create! }.to change(Label, :count).by(1)
+      end
+    end
+
+    context 'when label exists' do
+      it 'does not create a new label' do
+        project.labels.create(name: raw.name)
+
+        expect { subject.create! }.not_to change(Label, :count)
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index edfc6ad81c68ef50eae70df03a2f0cd2f69877e4..302f0fc06236fbffeeb5a95001ba71d518d50f61 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -140,6 +140,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
 
         expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
       end
+
+      it 'returns description without created at tag line' do
+        create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+        expect(pull_request.attributes.fetch(:description)).to eq('Please pull these awesome changes')
+      end
     end
 
     context 'when it has a milestone' do
diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..793128c6ab92b271ec401950f9b786fd8d5a6e9b
--- /dev/null
+++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::ReleaseFormatter, lib: true do
+  let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
+  let(:octocat) { double(id: 123456, login: 'octocat') }
+  let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+
+  let(:base_data) do
+    {
+      tag_name: 'v1.0.0',
+      name: 'First release',
+      draft: false,
+      created_at: created_at,
+      published_at: created_at,
+      body: 'Release v1.0.0'
+    }
+  end
+
+  subject(:release) { described_class.new(project, raw_data) }
+
+  describe '#attributes' do
+    let(:raw_data) { double(base_data) }
+
+    it 'returns formatted attributes' do
+      expected = {
+        project: project,
+        tag: 'v1.0.0',
+        description: 'Release v1.0.0',
+        created_at: created_at,
+        updated_at: created_at
+      }
+
+      expect(release.attributes).to eq(expected)
+    end
+  end
+
+  describe '#valid' do
+    context 'when release is not a draft' do
+      let(:raw_data) { double(base_data) }
+
+      it 'returns true' do
+        expect(release.valid?).to eq true
+      end
+    end
+
+    context 'when release is draft' do
+      let(:raw_data) { double(base_data.merge(draft: true)) }
+
+      it 'returns false' do
+        expect(release.valid?).to eq false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb
index d3f1deb383765b221f5f5faaeb60a23d1d5d052e..9b499b593d32ced37b99ab25a72b56699ac2c1df 100644
--- a/spec/lib/gitlab/gitlab_import/importer_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb
@@ -13,6 +13,7 @@ describe Gitlab::GitlabImport::Importer, lib: true do
           'title' => 'Issue',
           'description' => 'Lorem ipsum',
           'state' => 'opened',
+          'confidential' => true,
           'author' => {
             'id' => 283999,
             'name' => 'John Doe'
@@ -34,6 +35,7 @@ describe Gitlab::GitlabImport::Importer, lib: true do
         title: 'Issue',
         description: "*Created by: John Doe*\n\nLorem ipsum",
         state: 'opened',
+        confidential: true,
         author_id: project.creator_id
       }
 
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 5114f9c55e11db98e831e0a726537aa22d734c1e..281f6cf117715afe50387f5599e9b9d877a65763 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -24,7 +24,7 @@
       "test_ee_field": "test",
       "milestone": {
         "id": 1,
-        "title": "v0.0",
+        "title": "test milestone",
         "project_id": 8,
         "description": "test milestone",
         "due_date": null,
@@ -51,7 +51,7 @@
         {
           "id": 2,
           "label_id": 2,
-          "target_id": 3,
+          "target_id": 40,
           "target_type": "Issue",
           "created_at": "2016-07-22T08:57:02.840Z",
           "updated_at": "2016-07-22T08:57:02.840Z",
@@ -281,6 +281,31 @@
       "deleted_at": null,
       "due_date": null,
       "moved_to_id": null,
+      "milestone": {
+        "id": 1,
+        "title": "test milestone",
+        "project_id": 8,
+        "description": "test milestone",
+        "due_date": null,
+        "created_at": "2016-06-14T15:02:04.415Z",
+        "updated_at": "2016-06-14T15:02:04.415Z",
+        "state": "active",
+        "iid": 1,
+        "events": [
+          {
+            "id": 487,
+            "target_type": "Milestone",
+            "target_id": 1,
+            "title": null,
+            "data": null,
+            "project_id": 46,
+            "created_at": "2016-06-14T15:02:04.418Z",
+            "updated_at": "2016-06-14T15:02:04.418Z",
+            "action": 1,
+            "author_id": 18
+          }
+        ]
+      },
       "notes": [
         {
           "id": 359,
@@ -494,6 +519,27 @@
       "deleted_at": null,
       "due_date": null,
       "moved_to_id": null,
+      "label_links": [
+        {
+          "id": 99,
+          "label_id": 2,
+          "target_id": 38,
+          "target_type": "Issue",
+          "created_at": "2016-07-22T08:57:02.840Z",
+          "updated_at": "2016-07-22T08:57:02.840Z",
+          "label": {
+            "id": 2,
+            "title": "test2",
+            "color": "#428bca",
+            "project_id": 8,
+            "created_at": "2016-07-22T08:55:44.161Z",
+            "updated_at": "2016-07-22T08:55:44.161Z",
+            "template": false,
+            "description": "",
+            "priority": null
+          }
+        }
+      ],
       "notes": [
         {
           "id": 367,
@@ -6478,7 +6524,7 @@
     {
       "id": 37,
       "project_id": 5,
-      "ref": "master",
+      "ref": null,
       "sha": "048721d90c449b244b7b4c53a9186b04330174ec",
       "before_sha": null,
       "push_data": null,
@@ -7301,6 +7347,30 @@
 
   ],
   "protected_branches": [
-
+    {
+      "id": 1,
+      "project_id": 9,
+      "name": "master",
+      "created_at": "2016-08-30T07:32:52.426Z",
+      "updated_at": "2016-08-30T07:32:52.426Z",
+      "merge_access_levels": [
+        {
+          "id": 1,
+          "protected_branch_id": 1,
+          "access_level": 40,
+          "created_at": "2016-08-30T07:32:52.458Z",
+          "updated_at": "2016-08-30T07:32:52.458Z"
+        }
+      ],
+      "push_access_levels": [
+        {
+          "id": 1,
+          "protected_branch_id": 1,
+          "access_level": 40,
+          "created_at": "2016-08-30T07:32:52.490Z",
+          "updated_at": "2016-08-30T07:32:52.490Z"
+        }
+      ]
+    }
   ]
-}
+}
\ No newline at end of file
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index a07ef279e6825ee3d1bab07f34338dd2c729016f..feacb295231eb64af554eb46b14c447f16be36c8 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -29,12 +29,30 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
         expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED)
       end
 
+      it 'has the same label associated to two issues' do
+        restored_project_json
+
+        expect(Label.first.issues.count).to eq(2)
+      end
+
+      it 'has milestones associated to two separate issues' do
+        restored_project_json
+
+        expect(Milestone.find_by_description('test milestone').issues.count).to eq(2)
+      end
+
       it 'creates a valid pipeline note' do
         restored_project_json
 
         expect(Ci::Pipeline.first.notes).not_to be_empty
       end
 
+      it 'restores pipelines with missing ref' do
+        restored_project_json
+
+        expect(Ci::Pipeline.where(ref: nil)).not_to be_empty
+      end
+
       it 'restores the correct event with symbolised data' do
         restored_project_json
 
@@ -49,6 +67,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
         expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
       end
 
+      it 'contains the merge access levels on a protected branch' do
+        restored_project_json
+
+        expect(ProtectedBranch.first.merge_access_levels).not_to be_empty
+      end
+
+      it 'contains the push access levels on a protected branch' do
+        restored_project_json
+
+        expect(ProtectedBranch.first.push_access_levels).not_to be_empty
+      end
+
       context 'event at forth level of the tree' do
         let(:event) { Event.where(title: 'test levels').first }
 
@@ -77,12 +107,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
         expect(Label.first.label_links.first.target).not_to be_nil
       end
 
-      it 'has milestones associated to issues' do
-        restored_project_json
-
-        expect(Milestone.find_by_description('test milestone').issues).not_to be_empty
-      end
-
       context 'Merge requests' do
         before do
           restored_project_json
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index 90c6d1c67f6ec920cc4e2feafeee930027aa8f2d..c680e712b591718e3a27b7a4c68abe06b55f6535 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -23,7 +23,7 @@ describe Gitlab::ImportExport::VersionChecker, services: true do
       it 'shows the correct error message' do
         described_class.check!(shared: shared)
 
-        expect(shared.errors.first).to eq("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
+        expect(shared.errors.first).to eq("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
       end
     end
   end
diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb
index 4847b5f3b0e62e724e36459ee28ffd7c04f909e0..0600893f4cffce4f6da79638b12f9fc74a0184db 100644
--- a/spec/lib/gitlab/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/ldap/adapter_spec.rb
@@ -1,12 +1,77 @@
 require 'spec_helper'
 
 describe Gitlab::LDAP::Adapter, lib: true do
-  let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' }
+  include LdapHelpers
+
+  let(:ldap) { double(:ldap) }
+  let(:adapter) { ldap_adapter('ldapmain', ldap) }
+
+  describe '#users' do
+    before do
+      stub_ldap_config(base: 'dc=example,dc=com')
+    end
+
+    it 'searches with the proper options when searching by uid' do
+      # Requires this expectation style to match the filter
+      expect(adapter).to receive(:ldap_search) do |arg|
+        expect(arg[:filter].to_s).to eq('(uid=johndoe)')
+        expect(arg[:base]).to eq('dc=example,dc=com')
+        expect(arg[:attributes]).to match(%w{uid cn mail dn})
+      end.and_return({})
+
+      adapter.users('uid', 'johndoe')
+    end
+
+    it 'searches with the proper options when searching by dn' do
+      expect(adapter).to receive(:ldap_search).with(
+        base: 'uid=johndoe,ou=users,dc=example,dc=com',
+        scope: Net::LDAP::SearchScope_BaseObject,
+        attributes: %w{uid cn mail dn},
+        filter: nil
+      ).and_return({})
+
+      adapter.users('dn', 'uid=johndoe,ou=users,dc=example,dc=com')
+    end
+
+    it 'searches with the proper options when searching with a limit' do
+      expect(adapter)
+        .to receive(:ldap_search).with(hash_including(size: 100)).and_return({})
+
+      adapter.users('uid', 'johndoe', 100)
+    end
+
+    it 'returns an LDAP::Person if search returns a result' do
+      entry = ldap_user_entry('johndoe')
+      allow(adapter).to receive(:ldap_search).and_return([entry])
+
+      results = adapter.users('uid', 'johndoe')
+
+      expect(results.size).to eq(1)
+      expect(results.first.uid).to eq('johndoe')
+    end
+
+    it 'returns empty array if search entry does not respond to uid' do
+      entry = Net::LDAP::Entry.new
+      entry['dn'] = user_dn('johndoe')
+      allow(adapter).to receive(:ldap_search).and_return([entry])
+
+      results = adapter.users('uid', 'johndoe')
+
+      expect(results).to be_empty
+    end
+
+    it 'uses the right uid attribute when non-default' do
+      stub_ldap_config(uid: 'sAMAccountName')
+      expect(adapter).to receive(:ldap_search).with(
+        hash_including(attributes: %w{sAMAccountName cn mail dn})
+      ).and_return({})
+
+      adapter.users('sAMAccountName', 'johndoe')
+    end
+  end
 
   describe '#dn_matches_filter?' do
-    let(:ldap) { double(:ldap) }
     subject { adapter.dn_matches_filter?(:dn, :filter) }
-    before { allow(adapter).to receive(:ldap).and_return(ldap) }
 
     context "when the search is successful" do
       context "and the result is non-empty" do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index c5c1402e8fcfc5d3d02aadb5859e93d395d1e401..6c7fa7e7c15564c0f6cf263dbb517b05a3efd88d 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Workhorse, lib: true do
   let(:project) { create(:project) }
   let(:subject) { Gitlab::Workhorse }
 
-  describe "#send_git_archive" do
+  describe ".send_git_archive" do
     context "when the repository doesn't have an archive file path" do
       before do
         allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
@@ -15,4 +15,93 @@ describe Gitlab::Workhorse, lib: true do
       end
     end
   end
+
+  describe ".secret" do
+    subject { described_class.secret }
+
+    before do
+      described_class.instance_variable_set(:@secret, nil)
+      described_class.write_secret
+    end
+
+    it 'returns 32 bytes' do
+      expect(subject).to be_a(String)
+      expect(subject.length).to eq(32)
+      expect(subject.encoding).to eq(Encoding::ASCII_8BIT)
+    end
+
+    it 'accepts a trailing newline' do
+      open(described_class.secret_path, 'a') { |f| f.write "\n" }
+      expect(subject.length).to eq(32)
+    end
+
+    it 'raises an exception if the secret file cannot be read' do
+      File.delete(described_class.secret_path)
+      expect { subject }.to raise_exception(Errno::ENOENT)
+    end
+
+    it 'raises an exception if the secret file contains the wrong number of bytes' do
+      File.truncate(described_class.secret_path, 0)
+      expect { subject }.to raise_exception(RuntimeError)
+    end
+  end
+
+  describe ".write_secret" do
+    let(:secret_path) { described_class.secret_path }
+    before do
+      begin
+        File.delete(secret_path)
+      rescue Errno::ENOENT
+      end
+
+      described_class.write_secret
+    end
+
+    it 'uses mode 0600' do
+      expect(File.stat(secret_path).mode & 0777).to eq(0600)
+    end
+
+    it 'writes base64 data' do
+      bytes = Base64.strict_decode64(File.read(secret_path))
+      expect(bytes).not_to be_empty
+    end
+  end
+
+  describe '#verify_api_request!' do
+    let(:header_key) { described_class::INTERNAL_API_REQUEST_HEADER }
+    let(:payload) { { 'iss' => 'gitlab-workhorse' } }
+
+    it 'accepts a correct header' do
+      headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') }
+      expect { call_verify(headers) }.not_to raise_error
+    end
+
+    it 'raises an error when the header is not set' do
+      expect { call_verify({}) }.to raise_jwt_error
+    end
+
+    it 'raises an error when the header is not signed' do
+      headers = { header_key => JWT.encode(payload, nil, 'none') }
+      expect { call_verify(headers) }.to raise_jwt_error
+    end
+
+    it 'raises an error when the header is signed with the wrong key' do
+      headers = { header_key => JWT.encode(payload, 'wrongkey', 'HS256') }
+      expect { call_verify(headers) }.to raise_jwt_error
+    end
+
+    it 'raises an error when the issuer is incorrect' do
+      payload['iss'] = 'somebody else'
+      headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') }
+      expect { call_verify(headers) }.to raise_jwt_error
+    end
+
+    def raise_jwt_error
+      raise_error(JWT::DecodeError)
+    end
+
+    def call_verify(headers)
+      described_class.verify_api_request!(headers)
+    end
+  end
 end
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index cee20234e1f8fae94929c46f503880627921d638..03d02b4d382bc25ca85cc41ff39dafe77f8f3a98 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
 require 'rails_helper'
 
 describe Blob do
@@ -7,6 +8,25 @@ describe Blob do
     end
   end
 
+  describe '#data' do
+    context 'using a binary blob' do
+      it 'returns the data as-is' do
+        data = "\n\xFF\xB9\xC3"
+        blob = described_class.new(double(binary?: true, data: data))
+
+        expect(blob.data).to eq(data)
+      end
+    end
+
+    context 'using a text blob' do
+      it 'converts the data to UTF-8' do
+        blob = described_class.new(double(binary?: false, data: "\n\xFF\xB9\xC3"))
+
+        expect(blob.data).to eq("\n���")
+      end
+    end
+  end
+
   describe '#svg?' do
     it 'is falsey when not text' do
       git_blob = double(text?: false)
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index c45c2635cf4afc91c24f31c8c93053bfc2bfe69b..e7864b7ad33fbf0f50d440002a18e77e2093c46b 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -88,9 +88,7 @@ describe Ci::Build, models: true do
   end
 
   describe '#trace' do
-    subject { build.trace_html }
-
-    it { is_expected.to be_empty }
+    it { expect(build.trace).to be_nil }
 
     context 'when build.trace contains text' do
       let(:text) { 'example output' }
@@ -98,16 +96,80 @@ describe Ci::Build, models: true do
         build.trace = text
       end
 
-      it { is_expected.to include(text) }
-      it { expect(subject.length).to be >= text.length }
+      it { expect(build.trace).to eq(text) }
+    end
+
+    context 'when build.trace hides runners token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.update(trace: token)
+        build.project.update(runners_token: token)
+      end
+
+      it { expect(build.trace).not_to include(token) }
+      it { expect(build.raw_trace).to include(token) }
+    end
+
+    context 'when build.trace hides build token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.update(trace: token)
+        build.update(token: token)
+      end
+
+      it { expect(build.trace).not_to include(token) }
+      it { expect(build.raw_trace).to include(token) }
+    end
+  end
+
+  describe '#raw_trace' do
+    subject { build.raw_trace }
+
+    context 'when build.trace hides runners token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.project.update(runners_token: token)
+        build.update(trace: token)
+      end
+
+      it { is_expected.not_to include(token) }
+    end
+
+    context 'when build.trace hides build token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.update(token: token)
+        build.update(trace: token)
+      end
+
+      it { is_expected.not_to include(token) }
+    end
+  end
+
+  context '#append_trace' do
+    subject { build.trace_html }
+
+    context 'when build.trace hides runners token' do
+      let(:token) { 'my_secret_token' }
+
+      before do
+        build.project.update(runners_token: token)
+        build.append_trace(token, 0)
+      end
+
+      it { is_expected.not_to include(token) }
     end
 
-    context 'when build.trace hides token' do
+    context 'when build.trace hides build token' do
       let(:token) { 'my_secret_token' }
 
       before do
-        build.project.update_attributes(runners_token: token)
-        build.update_attributes(trace: token)
+        build.update(token: token)
+        build.append_trace(token, 0)
       end
 
       it { is_expected.not_to include(token) }
@@ -231,6 +293,34 @@ describe Ci::Build, models: true do
       it { is_expected.to eq(predefined_variables) }
     end
 
+    context 'when build has user' do
+      let(:user) { create(:user, username: 'starter') }
+      let(:user_variables) do
+        [
+          { key: 'GITLAB_USER_ID',    value: user.id.to_s, public: true },
+          { key: 'GITLAB_USER_EMAIL', value: user.email,   public: true }
+        ]
+      end
+
+      before do
+        build.update_attributes(user: user)
+      end
+
+      it { user_variables.each { |v| is_expected.to include(v) } }
+    end
+
+    context 'when build started manually' do
+      before do
+        build.update_attributes(when: :manual)
+      end
+
+      let(:manual_variable) do
+        { key: 'CI_BUILD_MANUAL', value: 'true', public: true }
+      end
+
+      it { is_expected.to include(manual_variable) }
+    end
+
     context 'when build is for tag' do
       let(:tag_variable) do
         { key: 'CI_BUILD_TAG', value: 'master', public: true }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index bce18b4e99ef513dac38eb21a70d09beb2a08d0c..a37a00f461a888f7b3b61390b4874681b93a8e2f 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -8,7 +8,7 @@ describe Ci::Build, models: true do
     it 'obfuscates project runners token' do
       allow(build).to receive(:raw_trace).and_return("Test: #{build.project.runners_token}")
 
-      expect(build.trace).to eq("Test: xxxxxx")
+      expect(build.trace).to eq("Test: xxxxxxxxxxxxxxxxxxxx")
     end
 
     it 'empty project runners token' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 5bafcc13f612d56db55907003a6b19645d903e4d..0b989b98b6128328b3446cf92934ad596f1a9646 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -124,21 +124,38 @@ describe Ci::Pipeline, models: true do
 
   describe 'state machine' do
     let(:current) { Time.now.change(usec: 0) }
-    let(:build) { create :ci_build, name: 'build1', pipeline: pipeline }
+    let(:build) { create_build('build1', current, 10) }
+    let(:build_b) { create_build('build2', current, 20) }
+    let(:build_c) { create_build('build3', current + 50, 10) }
 
     describe '#duration' do
       before do
-        travel_to(current - 120) do
+        pipeline.update(created_at: current)
+
+        travel_to(current + 5) do
           pipeline.run
+          pipeline.save
+        end
+
+        travel_to(current + 30) do
+          build.success
+        end
+
+        travel_to(current + 40) do
+          build_b.drop
         end
 
-        travel_to(current) do
-          pipeline.succeed
+        travel_to(current + 70) do
+          build_c.success
         end
+
+        pipeline.drop
       end
 
       it 'matches sum of builds duration' do
-        expect(pipeline.reload.duration).to eq(120)
+        pipeline.reload
+
+        expect(pipeline.duration).to eq(40)
       end
     end
 
@@ -200,6 +217,14 @@ describe Ci::Pipeline, models: true do
         end
       end
     end
+
+    def create_build(name, queued_at = current, started_from = 0)
+      create(:ci_build,
+             name: name,
+             pipeline: pipeline,
+             queued_at: queued_at,
+             started_at: queued_at + started_from)
+    end
   end
 
   describe '#branch?' do
@@ -379,8 +404,8 @@ describe Ci::Pipeline, models: true do
   end
 
   describe '#execute_hooks' do
-    let!(:build_a) { create_build('a') }
-    let!(:build_b) { create_build('b') }
+    let!(:build_a) { create_build('a', 0) }
+    let!(:build_b) { create_build('b', 1) }
 
     let!(:hook) do
       create(:project_hook, project: project, pipeline_events: enabled)
@@ -404,7 +429,7 @@ describe Ci::Pipeline, models: true do
             build_b.enqueue
           end
 
-          it 'receive a pending event once' do
+          it 'receives a pending event once' do
             expect(WebMock).to have_requested_pipeline_hook('pending').once
           end
         end
@@ -417,7 +442,7 @@ describe Ci::Pipeline, models: true do
             build_b.run
           end
 
-          it 'receive a running event once' do
+          it 'receives a running event once' do
             expect(WebMock).to have_requested_pipeline_hook('running').once
           end
         end
@@ -428,11 +453,21 @@ describe Ci::Pipeline, models: true do
             build_b.success
           end
 
-          it 'receive a success event once' do
+          it 'receives a success event once' do
             expect(WebMock).to have_requested_pipeline_hook('success').once
           end
         end
 
+        context 'when stage one failed' do
+          before do
+            build_a.drop
+          end
+
+          it 'receives a failed event once' do
+            expect(WebMock).to have_requested_pipeline_hook('failed').once
+          end
+        end
+
         def have_requested_pipeline_hook(status)
           have_requested(:post, hook.url).with do |req|
             json_body = JSON.parse(req.body)
@@ -456,8 +491,12 @@ describe Ci::Pipeline, models: true do
       end
     end
 
-    def create_build(name)
-      create(:ci_build, :created, pipeline: pipeline, name: name)
+    def create_build(name, stage_idx)
+      create(:ci_build,
+             :created,
+             pipeline: pipeline,
+             name: name,
+             stage_idx: stage_idx)
     end
   end
 
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index fcfa3138ce50b16cc2b0f5e7ae34acf15da63e5d..2f1baff5d66a9c83a5a29c25db8bc2dd8094e378 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -40,7 +40,7 @@ describe CommitStatus, models: true do
       it { is_expected.to be_falsey }
     end
 
-    %w(running success failed).each do |status|
+    %w[running success failed].each do |status|
       context "if commit status is #{status}" do
         before { commit_status.status = status }
 
@@ -48,7 +48,7 @@ describe CommitStatus, models: true do
       end
     end
 
-    %w(pending canceled).each do |status|
+    %w[pending canceled].each do |status|
       context "if commit status is #{status}" do
         before { commit_status.status = status }
 
@@ -60,7 +60,7 @@ describe CommitStatus, models: true do
   describe '#active?' do
     subject { commit_status.active? }
 
-    %w(pending running).each do |state|
+    %w[pending running].each do |state|
       context "if commit_status.status is #{state}" do
         before { commit_status.status = state }
 
@@ -68,7 +68,7 @@ describe CommitStatus, models: true do
       end
     end
 
-    %w(success failed canceled).each do |state|
+    %w[success failed canceled].each do |state|
       context "if commit_status.status is #{state}" do
         before { commit_status.status = state }
 
@@ -80,7 +80,7 @@ describe CommitStatus, models: true do
   describe '#complete?' do
     subject { commit_status.complete? }
 
-    %w(success failed canceled).each do |state|
+    %w[success failed canceled].each do |state|
       context "if commit_status.status is #{state}" do
         before { commit_status.status = state }
 
@@ -88,7 +88,7 @@ describe CommitStatus, models: true do
       end
     end
 
-    %w(pending running).each do |state|
+    %w[pending running].each do |state|
       context "if commit_status.status is #{state}" do
         before { commit_status.status = state }
 
@@ -187,7 +187,7 @@ describe CommitStatus, models: true do
       subject { CommitStatus.where(pipeline: pipeline).stages }
 
       it 'returns ordered list of stages' do
-        is_expected.to eq(%w(build test deploy))
+        is_expected.to eq(%w[build test deploy])
       end
     end
 
@@ -223,4 +223,33 @@ describe CommitStatus, models: true do
       expect(commit_status.commit).to eq project.commit
     end
   end
+
+  describe '#group_name' do
+    subject { commit_status.group_name }
+
+    tests = {
+      'rspec:windows' => 'rspec:windows',
+      'rspec:windows 0' => 'rspec:windows 0',
+      'rspec:windows 0 test' => 'rspec:windows 0 test',
+      'rspec:windows 0 1' => 'rspec:windows',
+      'rspec:windows 0 1 name' => 'rspec:windows name',
+      'rspec:windows 0/1' => 'rspec:windows',
+      'rspec:windows 0/1 name' => 'rspec:windows name',
+      'rspec:windows 0:1' => 'rspec:windows',
+      'rspec:windows 0:1 name' => 'rspec:windows name',
+      'rspec:windows 10000 20000' => 'rspec:windows',
+      'rspec:windows 0 : / 1' => 'rspec:windows',
+      'rspec:windows 0 : / 1 name' => 'rspec:windows name',
+      '0 1 name ruby' => 'name ruby',
+      '0 :/ 1 name ruby' => 'name ruby'
+    }
+
+    tests.each do |name, group_name|
+      it "'#{name}' puts in '#{group_name}'" do
+        commit_status.name = name
+
+        is_expected.to eq(group_name)
+      end
+    end
+  end
 end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 6a640474cfe04f6b057af2f4c91e514b359db77e..3db5937a4f3932eec5c2354fbb372e79ebe7fb33 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -31,6 +31,43 @@ describe DiffNote, models: true do
 
   subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
 
+  describe ".resolve!" do
+    let(:current_user) { create(:user) }
+    let!(:commit_note) { create(:diff_note_on_commit) }
+    let!(:resolved_note) { create(:diff_note_on_merge_request, :resolved) }
+    let!(:unresolved_note) { create(:diff_note_on_merge_request) }
+
+    before do
+      described_class.resolve!(current_user)
+
+      commit_note.reload
+      resolved_note.reload
+      unresolved_note.reload
+    end
+
+    it 'resolves only the resolvable, not yet resolved notes' do
+      expect(commit_note.resolved_at).to be_nil
+      expect(resolved_note.resolved_by).not_to eq(current_user)
+      expect(unresolved_note.resolved_at).not_to be_nil
+      expect(unresolved_note.resolved_by).to eq(current_user)
+    end
+  end
+
+  describe ".unresolve!" do
+    let!(:resolved_note) { create(:diff_note_on_merge_request, :resolved) }
+
+    before do
+      described_class.unresolve!
+
+      resolved_note.reload
+    end
+
+    it 'unresolves the resolved notes' do
+      expect(resolved_note.resolved_by).to be_nil
+      expect(resolved_note.resolved_at).to be_nil
+    end
+  end
+
   describe "#position=" do
     context "when provided a string" do
       it "sets the position" do
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index 179f2e7366247a1b684035e0ff9045f730d3f6b8..0142706d140039b54b98fa3e3e1e713713bfac3e 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -238,27 +238,19 @@ describe Discussion, model: true do
 
     context "when resolvable" do
       let(:user) { create(:user) }
+      let(:second_note) { create(:diff_note_on_commit) } # unresolvable
 
       before do
         allow(subject).to receive(:resolvable?).and_return(true)
-
-        allow(first_note).to receive(:resolvable?).and_return(true)
-        allow(second_note).to receive(:resolvable?).and_return(false)
-        allow(third_note).to receive(:resolvable?).and_return(true)
       end
 
       context "when all resolvable notes are resolved" do
         before do
           first_note.resolve!(user)
           third_note.resolve!(user)
-        end
 
-        it "calls resolve! on every resolvable note" do
-          expect(first_note).to receive(:resolve!).with(current_user)
-          expect(second_note).not_to receive(:resolve!)
-          expect(third_note).to receive(:resolve!).with(current_user)
-
-          subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
         end
 
         it "doesn't change resolved_at on the resolved notes" do
@@ -309,46 +301,44 @@ describe Discussion, model: true do
           first_note.resolve!(user)
         end
 
-        it "calls resolve! on every resolvable note" do
-          expect(first_note).to receive(:resolve!).with(current_user)
-          expect(second_note).not_to receive(:resolve!)
-          expect(third_note).to receive(:resolve!).with(current_user)
-
-          subject.resolve!(current_user)
-        end
-
         it "doesn't change resolved_at on the resolved note" do
           expect(first_note.resolved_at).not_to be_nil
 
-          expect { subject.resolve!(current_user) }.not_to change { first_note.resolved_at }
+          expect { subject.resolve!(current_user) }.
+            not_to change { first_note.reload.resolved_at }
         end
 
         it "doesn't change resolved_by on the resolved note" do
           expect(first_note.resolved_by).to eq(user)
 
-          expect { subject.resolve!(current_user) }.not_to change { first_note.resolved_by }
+          expect { subject.resolve!(current_user) }.
+            not_to change { first_note.reload && first_note.resolved_by }
         end
 
         it "doesn't change the resolved state on the resolved note" do
           expect(first_note.resolved?).to be true
 
-          expect { subject.resolve!(current_user) }.not_to change { first_note.resolved? }
+          expect { subject.resolve!(current_user) }.
+            not_to change { first_note.reload && first_note.resolved? }
         end
 
         it "sets resolved_at on the unresolved note" do
           subject.resolve!(current_user)
+          third_note.reload
 
           expect(third_note.resolved_at).not_to be_nil
         end
 
         it "sets resolved_by on the unresolved note" do
           subject.resolve!(current_user)
+          third_note.reload
 
           expect(third_note.resolved_by).to eq(current_user)
         end
 
         it "marks the unresolved note as resolved" do
           subject.resolve!(current_user)
+          third_note.reload
 
           expect(third_note.resolved?).to be true
         end
@@ -373,16 +363,10 @@ describe Discussion, model: true do
       end
 
       context "when no resolvable notes are resolved" do
-        it "calls resolve! on every resolvable note" do
-          expect(first_note).to receive(:resolve!).with(current_user)
-          expect(second_note).not_to receive(:resolve!)
-          expect(third_note).to receive(:resolve!).with(current_user)
-
-          subject.resolve!(current_user)
-        end
-
         it "sets resolved_at on the unresolved notes" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved_at).not_to be_nil
           expect(third_note.resolved_at).not_to be_nil
@@ -390,6 +374,8 @@ describe Discussion, model: true do
 
         it "sets resolved_by on the unresolved notes" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved_by).to eq(current_user)
           expect(third_note.resolved_by).to eq(current_user)
@@ -397,6 +383,8 @@ describe Discussion, model: true do
 
         it "marks the unresolved notes as resolved" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved?).to be true
           expect(third_note.resolved?).to be true
@@ -404,18 +392,24 @@ describe Discussion, model: true do
 
         it "sets resolved_at" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved_at).not_to be_nil
         end
 
         it "sets resolved_by" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved_by).to eq(current_user)
         end
 
         it "marks as resolved" do
           subject.resolve!(current_user)
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved?).to be true
         end
@@ -451,16 +445,10 @@ describe Discussion, model: true do
           third_note.resolve!(user)
         end
 
-        it "calls unresolve! on every resolvable note" do
-          expect(first_note).to receive(:unresolve!)
-          expect(second_note).not_to receive(:unresolve!)
-          expect(third_note).to receive(:unresolve!)
-
-          subject.unresolve!
-        end
-
         it "unsets resolved_at on the resolved notes" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved_at).to be_nil
           expect(third_note.resolved_at).to be_nil
@@ -468,6 +456,8 @@ describe Discussion, model: true do
 
         it "unsets resolved_by on the resolved notes" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved_by).to be_nil
           expect(third_note.resolved_by).to be_nil
@@ -475,6 +465,8 @@ describe Discussion, model: true do
 
         it "unmarks the resolved notes as resolved" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(first_note.resolved?).to be false
           expect(third_note.resolved?).to be false
@@ -482,12 +474,16 @@ describe Discussion, model: true do
 
         it "unsets resolved_at" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved_at).to be_nil
         end
 
         it "unsets resolved_by" do
           subject.unresolve!
+          first_note.reload
+          third_note.reload
 
           expect(subject.resolved_by).to be_nil
         end
@@ -504,40 +500,22 @@ describe Discussion, model: true do
           first_note.resolve!(user)
         end
 
-        it "calls unresolve! on every resolvable note" do
-          expect(first_note).to receive(:unresolve!)
-          expect(second_note).not_to receive(:unresolve!)
-          expect(third_note).to receive(:unresolve!)
-
-          subject.unresolve!
-        end
-
         it "unsets resolved_at on the resolved note" do
           subject.unresolve!
 
-          expect(first_note.resolved_at).to be_nil
+          expect(subject.first_note.resolved_at).to be_nil
         end
 
         it "unsets resolved_by on the resolved note" do
           subject.unresolve!
 
-          expect(first_note.resolved_by).to be_nil
+          expect(subject.first_note.resolved_by).to be_nil
         end
 
         it "unmarks the resolved note as resolved" do
           subject.unresolve!
 
-          expect(first_note.resolved?).to be false
-        end
-      end
-
-      context "when no resolvable notes are resolved" do
-        it "calls unresolve! on every resolvable note" do
-          expect(first_note).to receive(:unresolve!)
-          expect(second_note).not_to receive(:unresolve!)
-          expect(third_note).to receive(:unresolve!)
-
-          subject.unresolve!
+          expect(subject.first_note.resolved?).to be false
         end
       end
     end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index c881897926e36b4172dc977fa8dc3fec10f97c71..6b1867a44e1232805b92da166ed784d2479ba0ea 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -63,4 +63,20 @@ describe Environment, models: true do
       end
     end
   end
+
+  describe '#environment_type' do
+    subject { environment.environment_type }
+
+    it 'sets a environment type if name has multiple segments' do
+      environment.update!(name: 'production/worker.gitlab.com')
+
+      is_expected.to eq('production')
+    end
+
+    it 'nullifies a type if it\'s a simple name' do
+      environment.update!(name: 'production')
+
+      is_expected.to be_nil
+    end
+  end
 end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index b5d0d79e14e836b3652a7d4c5bfa63be9c6b557f..8600eb4d2c40d7e1ec443f260da53654e60a953d 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -16,18 +16,12 @@ describe Event, models: true do
 
   describe 'Callbacks' do
     describe 'after_create :reset_project_activity' do
-      let(:project) { create(:project) }
+      let(:project) { create(:empty_project) }
 
-      context "project's last activity was less than 5 minutes ago" do
-        it 'does not update project.last_activity_at if it has been touched less than 5 minutes ago' do
-          create_event(project, project.owner)
-          project.update_column(:last_activity_at, 5.minutes.ago)
-          project_last_activity_at = project.last_activity_at
+      it 'calls the reset_project_activity method' do
+        expect_any_instance_of(Event).to receive(:reset_project_activity)
 
-          create_event(project, project.owner)
-
-          expect(project.last_activity_at).to eq(project_last_activity_at)
-        end
+        create_event(project, project.owner)
       end
     end
   end
@@ -161,6 +155,35 @@ describe Event, models: true do
     end
   end
 
+  describe '#reset_project_activity' do
+    let(:project) { create(:empty_project) }
+
+    context 'when a project was updated less than 1 hour ago' do
+      it 'does not update the project' do
+        project.update(last_activity_at: Time.now)
+
+        expect(project).not_to receive(:update_column).
+          with(:last_activity_at, a_kind_of(Time))
+
+        create_event(project, project.owner)
+      end
+    end
+
+    context 'when a project was updated more than 1 hour ago' do
+      it 'updates the project' do
+        project.update(last_activity_at: 1.year.ago)
+
+        expect_any_instance_of(Gitlab::ExclusiveLease).
+          to receive(:try_obtain).and_return(true)
+
+        expect(project).to receive(:update_column).
+          with(:last_activity_at, a_kind_of(Time))
+
+        create_event(project, project.owner)
+      end
+    end
+  end
+
   def create_event(project, user, attrs = {})
     data = {
       before: Gitlab::Git::BLANK_SHA,
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index ea4b59c26b1e6d8274a3669b3f868e59d13e8533..0b3ef9b98fd4ed7c29781c96ecd90b949c5ee30a 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -187,6 +187,52 @@ describe Group, models: true do
     it { expect(group.has_master?(@members[:requester])).to be_falsey }
   end
 
+  describe '#lfs_enabled?' do
+    context 'LFS enabled globally' do
+      before do
+        allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+      end
+
+      it 'returns true when nothing is set' do
+        expect(group.lfs_enabled?).to be_truthy
+      end
+
+      it 'returns false when set to false' do
+        group.update_attribute(:lfs_enabled, false)
+
+        expect(group.lfs_enabled?).to be_falsey
+      end
+
+      it 'returns true when set to true' do
+        group.update_attribute(:lfs_enabled, true)
+
+        expect(group.lfs_enabled?).to be_truthy
+      end
+    end
+
+    context 'LFS disabled globally' do
+      before do
+        allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
+      end
+
+      it 'returns false when nothing is set' do
+        expect(group.lfs_enabled?).to be_falsey
+      end
+
+      it 'returns false when set to false' do
+        group.update_attribute(:lfs_enabled, false)
+
+        expect(group.lfs_enabled?).to be_falsey
+      end
+
+      it 'returns false when set to true' do
+        group.update_attribute(:lfs_enabled, true)
+
+        expect(group.lfs_enabled?).to be_falsey
+      end
+    end
+  end
+
   describe '#owners' do
     let(:owner) { create(:user) }
     let(:developer) { create(:user) }
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 4a457997a4fa9dd8997bc8ed3049e1a0ffb2ed21..474ae62cceca6d40fb70fbcdcd83d4cc5713a162 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-#  id                    :integer          not null, primary key
-#  url                   :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  type                  :string(255)      default("ProjectHook")
-#  service_id            :integer
-#  push_events           :boolean          default(TRUE), not null
-#  issues_events         :boolean          default(FALSE), not null
-#  merge_requests_events :boolean          default(FALSE), not null
-#  tag_push_events       :boolean          default(FALSE)
-#  note_events           :boolean          default(FALSE), not null
-#
-
 require 'spec_helper'
 
 describe ProjectHook, models: true do
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 534e1b4f1285e12f156ec823a41799c91cff0de8..1a83c836652ebd2c15667f64f19511872f4b8e15 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-#  id                    :integer          not null, primary key
-#  url                   :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  type                  :string(255)      default("ProjectHook")
-#  service_id            :integer
-#  push_events           :boolean          default(TRUE), not null
-#  issues_events         :boolean          default(FALSE), not null
-#  merge_requests_events :boolean          default(FALSE), not null
-#  tag_push_events       :boolean          default(FALSE)
-#  note_events           :boolean          default(FALSE), not null
-#
-
 require "spec_helper"
 
 describe ServiceHook, models: true do
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index cbdf7eec082d4832c99691babb7cd96396f377eb..ad2b710041a38b3f3974e5a73f0031fc1c94b56f 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-#  id                    :integer          not null, primary key
-#  url                   :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  type                  :string(255)      default("ProjectHook")
-#  service_id            :integer
-#  push_events           :boolean          default(TRUE), not null
-#  issues_events         :boolean          default(FALSE), not null
-#  merge_requests_events :boolean          default(FALSE), not null
-#  tag_push_events       :boolean          default(FALSE)
-#  note_events           :boolean          default(FALSE), not null
-#
-
 require "spec_helper"
 
 describe SystemHook, models: true do
@@ -48,7 +30,7 @@ describe SystemHook, models: true do
 
     it "user_create hook" do
       create(:user)
-      
+
       expect(WebMock).to have_requested(:post, system_hook.url).with(
         body: /user_create/,
         headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index f9bab487b96338bb1c68f80eeca03f754b5d8f30..e52b9d75cefab6713e5ff8e04448f4fbeab00751 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-#  id                    :integer          not null, primary key
-#  url                   :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  type                  :string(255)      default("ProjectHook")
-#  service_id            :integer
-#  push_events           :boolean          default(TRUE), not null
-#  issues_events         :boolean          default(FALSE), not null
-#  merge_requests_events :boolean          default(FALSE), not null
-#  tag_push_events       :boolean          default(FALSE)
-#  note_events           :boolean          default(FALSE), not null
-#
-
 require 'spec_helper'
 
 describe WebHook, models: true do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index fef90d9b5cb8368c1d31a04c448cc8ddc0b442fe..0b1634f654af88cd808b5b359530877918414b7d 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -57,7 +57,7 @@ describe Member, models: true do
 
   describe 'Scopes & finders' do
     before do
-      project = create(:project)
+      project = create(:empty_project)
       group = create(:group)
       @owner_user = create(:user).tap { |u| group.add_owner(u) }
       @owner = group.members.find_by(user_id: @owner_user.id)
@@ -65,6 +65,15 @@ describe Member, models: true do
       @master_user = create(:user).tap { |u| project.team << [u, :master] }
       @master = project.members.find_by(user_id: @master_user.id)
 
+      @blocked_user = create(:user).tap do |u|
+        project.team << [u, :master]
+        project.team << [u, :developer]
+
+        u.block!
+      end
+      @blocked_master = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::MASTER)
+      @blocked_developer = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::DEVELOPER)
+
       Member.add_user(
         project.members,
         'toto1@example.com',
@@ -73,7 +82,7 @@ describe Member, models: true do
       )
       @invited_member = project.members.invite.find_by_invite_email('toto1@example.com')
 
-      accepted_invite_user = build(:user)
+      accepted_invite_user = build(:user, state: :active)
       Member.add_user(
         project.members,
         'toto2@example.com',
@@ -91,7 +100,7 @@ describe Member, models: true do
 
     describe '.access_for_user_ids' do
       it 'returns the right access levels' do
-        users = [@owner_user.id, @master_user.id]
+        users = [@owner_user.id, @master_user.id, @blocked_user.id]
         expected = {
           @owner_user.id => Gitlab::Access::OWNER,
           @master_user.id => Gitlab::Access::MASTER
@@ -125,6 +134,19 @@ describe Member, models: true do
       it { expect(described_class.request).not_to include @accepted_request_member }
     end
 
+    describe '.developers' do
+      subject { described_class.developers.to_a }
+
+      it { is_expected.not_to include @owner }
+      it { is_expected.not_to include @master }
+      it { is_expected.to include @invited_member }
+      it { is_expected.to include @accepted_invite_member }
+      it { is_expected.not_to include @requested_member }
+      it { is_expected.to include @accepted_request_member }
+      it { is_expected.not_to include @blocked_master }
+      it { is_expected.not_to include @blocked_developer }
+    end
+
     describe '.owners_and_masters' do
       it { expect(described_class.owners_and_masters).to include @owner }
       it { expect(described_class.owners_and_masters).to include @master }
@@ -132,6 +154,20 @@ describe Member, models: true do
       it { expect(described_class.owners_and_masters).not_to include @accepted_invite_member }
       it { expect(described_class.owners_and_masters).not_to include @requested_member }
       it { expect(described_class.owners_and_masters).not_to include @accepted_request_member }
+      it { expect(described_class.owners_and_masters).not_to include @blocked_master }
+    end
+
+    describe '.has_access' do
+      subject { described_class.has_access.to_a }
+
+      it { is_expected.to include @owner }
+      it { is_expected.to include @master }
+      it { is_expected.to include @invited_member }
+      it { is_expected.to include @accepted_invite_member }
+      it { is_expected.not_to include @requested_member }
+      it { is_expected.to include @accepted_request_member }
+      it { is_expected.not_to include @blocked_master }
+      it { is_expected.not_to include @blocked_developer }
     end
   end
 
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 4f875fd257a5fa18e155e082ca8dcacc5686f07a..56fa7fa61347cc6c73f12866ab0754008173be57 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-#  id                 :integer          not null, primary key
-#  access_level       :integer          not null
-#  source_id          :integer          not null
-#  source_type        :string(255)      not null
-#  user_id            :integer
-#  notification_level :integer          not null
-#  type               :string(255)
-#  created_at         :datetime
-#  updated_at         :datetime
-#  created_by_id      :integer
-#  invite_email       :string(255)
-#  invite_token       :string(255)
-#  invite_accepted_at :datetime
-#
-
 require 'spec_helper'
 
 describe GroupMember, models: true do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index be57957b569dcb456d8eed65feb5e73c20d2308c..805c15a4e5ecd85d0d0a6f995d04f40e36ba2e9c 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-#  id                 :integer          not null, primary key
-#  access_level       :integer          not null
-#  source_id          :integer          not null
-#  source_type        :string(255)      not null
-#  user_id            :integer
-#  notification_level :integer          not null
-#  type               :string(255)
-#  created_at         :datetime
-#  updated_at         :datetime
-#  created_by_id      :integer
-#  invite_email       :string(255)
-#  invite_token       :string(255)
-#  invite_accepted_at :datetime
-#
-
 require 'spec_helper'
 
 describe ProjectMember, models: true do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 5bf3b8e609e68e5a80d0ac86335f85de0a410d9c..06feeb1bbba6d897ad2367b8edd77271b58989b7 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -703,16 +703,24 @@ describe MergeRequest, models: true do
 
   describe "#environments" do
     let(:project)       { create(:project) }
-    let!(:environment)  { create(:environment, project: project) }
-    let!(:environment1) { create(:environment, project: project) }
-    let!(:environment2) { create(:environment, project: project) }
     let(:merge_request) { create(:merge_request, source_project: project) }
 
     it 'selects deployed environments' do
-      create(:deployment, environment: environment, sha: project.commit('master').id)
-      create(:deployment, environment: environment1, sha: project.commit('feature').id)
+      environments = create_list(:environment, 3, project: project)
+      create(:deployment, environment: environments.first, sha: project.commit('master').id)
+      create(:deployment, environment: environments.second, sha: project.commit('feature').id)
 
-      expect(merge_request.environments).to eq [environment]
+      expect(merge_request.environments).to eq [environments.first]
+    end
+
+    context 'without a diff_head_commit' do
+      before do
+        expect(merge_request).to receive(:diff_head_commit).and_return(nil)
+      end
+
+      it 'returns an empty array' do
+        expect(merge_request.environments).to be_empty
+      end
     end
   end
 
@@ -1038,4 +1046,81 @@ describe MergeRequest, models: true do
       end
     end
   end
+
+  describe '#closed_without_source_project?' do
+    let(:project)      { create(:project) }
+    let(:user)         { create(:user) }
+    let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) }
+    let(:destroy_service) { Projects::DestroyService.new(fork_project, user) }
+
+    context 'when the merge request is closed' do
+      let(:closed_merge_request) do
+        create(:closed_merge_request,
+          source_project: fork_project,
+          target_project: project)
+      end
+
+      it 'returns false if the source project exists' do
+        expect(closed_merge_request.closed_without_source_project?).to be_falsey
+      end
+
+      it 'returns true if the source project does not exist' do
+        destroy_service.execute
+        closed_merge_request.reload
+
+        expect(closed_merge_request.closed_without_source_project?).to be_truthy
+      end
+    end
+
+    context 'when the merge request is open' do
+      it 'returns false' do
+        expect(subject.closed_without_source_project?).to be_falsey
+      end
+    end
+  end
+
+  describe '#reopenable?' do
+    context 'when the merge request is closed' do
+      it 'returns true' do
+        subject.close
+
+        expect(subject.reopenable?).to be_truthy
+      end
+
+      context 'forked project' do
+        let(:project)      { create(:project) }
+        let(:user)         { create(:user) }
+        let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) }
+        let(:merge_request) do
+          create(:closed_merge_request,
+            source_project: fork_project,
+            target_project: project)
+        end
+
+        it 'returns false if unforked' do
+          Projects::UnlinkForkService.new(fork_project, user).execute
+
+          expect(merge_request.reload.reopenable?).to be_falsey
+        end
+
+        it 'returns false if the source project is deleted' do
+          Projects::DestroyService.new(fork_project, user).execute
+
+          expect(merge_request.reload.reopenable?).to be_falsey
+        end
+
+        it 'returns false if the merge request is merged' do
+          merge_request.update_attributes(state: 'merged')
+
+          expect(merge_request.reload.reopenable?).to be_falsey
+        end
+      end
+    end
+
+    context 'when the merge request is opened' do
+      it 'returns false' do
+        expect(subject.reopenable?).to be_falsey
+      end
+    end
+  end
 end
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index dc702cfc42c5c88619937ba4d767c654ee3c9593..8e5145e824b09eb597a60fe1c58af265e55e2754 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe AsanaService, models: true do
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index d672d80156c91090c32a84dc7df732f44a406465..4c5acb7990be089af3a01435e30e929950a9b177 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe AssemblaService, models: true do
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 9ae461f8c2d8befbb6042a3b49fec7aeb455e0f9..d7e1a4e3b6c51d965c0bd185a94e249d66ecec0a 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe BambooService, models: true do
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb
index a6d9717ccb52038af69880c59c1b4787cef9e834..739cc72b2ff7eb5e5207dcb7832c0fd91c16c49d 100644
--- a/spec/models/project_services/bugzilla_service_spec.rb
+++ b/spec/models/project_services/bugzilla_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe BugzillaService, models: true do
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 0866e1532dd2b03a9469a411ecc1792315662369..6f65beb79d0e8bb86cbe93d3a402aa20b1d4449e 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe BuildkiteService, models: true do
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index c76ae21421b3cbd58b3bb4e1f614bd8596aa3733..a3b9d084a755c21afd558fa02ffc023a995b63ca 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe CampfireService, models: true do
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
index ff976f8ec5932ff613ae4f51fc9e395b566c9155..7020667ea581db2d69bd68ee3cdf320264f4c61a 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe CustomIssueTrackerService, models: true do
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 8ef892259f260b8692b1b6f3319911d753371fe0..f13bb1e8adfe3d89fc88cd87a95ac609a1c36539 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe DroneCiService, models: true do
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index d7c5ea95d71af9f6fda4401a9503e4a8acc4def7..342d86aeca974e3d16e22de5e324760979c9f40d 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#  build_events          :boolean          default(FALSE), not null
-#
-
 require 'spec_helper'
 
 describe ExternalWikiService, models: true do
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index d25570197567cadb8e05481741503fbc5a05f65a..d6db02d6e7666bd69a373baa4df0e9a863d92631 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe FlowdockService, models: true do
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index 3d0b6c9816bdc45beed5a12d426767db5d7d8b61..529044d1d8bba734d988eec3caea9655f24867bf 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe GemnasiumService, models: true do
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 8ef79a17d502a05cd5a899eecd00bea3537f4363..652804fb44410ffe3d64ae932f8102a4b9374d95 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe GitlabIssueTrackerService, models: true do
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 34eafbe555d7234d75af619d96689db5bd991031..cf713684463e203fcecca6cdd8aa39393f4277ee 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe HipchatService, models: true do
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index ffb17fd3259d3bbb4f0810d1619db131a3a5dead..f8c45b3756190612ec4e53395366f60d498d818f 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 require 'socket'
 require 'json'
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 9037ca5cc2026513c9794247d51f8bd6c320a61b..b48a317600787b29b065a823e99c6e9501abfb39 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe JiraService, models: true do
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index d098d988521188c8cb1694f52523d4b10216d595..45b2f1068bffb13e72b6ee8074238a9413269314 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe PivotaltrackerService, models: true do
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 5959c81577d93a6a8ec4ccfd3b1f73a56fccac6f..8fc92a9ab51aa4c5b25787f36e9dfa9bd300b2ef 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe PushoverService, models: true do
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 7d14f6e82806f6466dad3a06c1da7e2865f09358..b8679cd25635c913e646360c18d3e74b0aa3735a 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe RedmineService, models: true do
diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb
index 7fcfdf0eacdd76b11b3b4a0d53278384d7fb1ca7..452f4e2782c80568f71bffc76f8deeaa6438b0ed 100644
--- a/spec/models/project_services/slack_service/build_message_spec.rb
+++ b/spec/models/project_services/slack_service/build_message_spec.rb
@@ -10,7 +10,7 @@ describe SlackService::BuildMessage do
       tag: false,
 
       project_name: 'project_name',
-      project_url: 'somewhere.com',
+      project_url: 'example.gitlab.com',
 
       commit: {
         status: status,
@@ -20,42 +20,38 @@ describe SlackService::BuildMessage do
     }
   end
 
-  context 'succeeded' do
+  let(:message) { build_message }
+
+  context 'build succeeded' do
     let(:status) { 'success' }
     let(:color) { 'good' }
     let(:duration) { 10 }
-    
+    let(:message) { build_message('passed') }
+
     it 'returns a message with information about succeeded build' do
-      message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker passed in 10 seconds'
       expect(subject.pretext).to be_empty
       expect(subject.fallback).to eq(message)
       expect(subject.attachments).to eq([text: message, color: color])
     end
   end
 
-  context 'failed' do
+  context 'build failed' do
     let(:status) { 'failed' }
     let(:color) { 'danger' }
     let(:duration) { 10 }
 
     it 'returns a message with information about failed build' do
-      message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 10 seconds'
       expect(subject.pretext).to be_empty
       expect(subject.fallback).to eq(message)
       expect(subject.attachments).to eq([text: message, color: color])
     end
-  end 
-  
-  describe '#seconds_name' do
-    let(:status) { 'failed' }
-    let(:color) { 'danger' }
-    let(:duration) { 1 }
+  end
 
-    it 'returns seconds as singular when there is only one' do
-      message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 1 second'
-      expect(subject.pretext).to be_empty
-      expect(subject.fallback).to eq(message)
-      expect(subject.attachments).to eq([text: message, color: color])
-    end
+  def build_message(status_text = status)
+    "<example.gitlab.com|project_name>:" \
+    " Commit <example.gitlab.com/commit/" \
+    "97de212e80737a608d939f648d959671fb0a0142/builds|97de212e>" \
+    " of <example.gitlab.com/commits/develop|develop> branch" \
+    " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
   end
 end
diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..babb3909f5681eda53d10cb1a47a92bffec15d17
--- /dev/null
+++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe SlackService::PipelineMessage do
+  subject { SlackService::PipelineMessage.new(args) }
+
+  let(:args) do
+    {
+      object_attributes: {
+        id: 123,
+        sha: '97de212e80737a608d939f648d959671fb0a0142',
+        tag: false,
+        ref: 'develop',
+        status: status,
+        duration: duration
+      },
+      project: { path_with_namespace: 'project_name',
+                 web_url: 'example.gitlab.com' },
+      commit: { author_name: 'hacker' }
+    }
+  end
+
+  let(:message) { build_message }
+
+  context 'pipeline succeeded' do
+    let(:status) { 'success' }
+    let(:color) { 'good' }
+    let(:duration) { 10 }
+    let(:message) { build_message('passed') }
+
+    it 'returns a message with information about succeeded build' do
+      expect(subject.pretext).to be_empty
+      expect(subject.fallback).to eq(message)
+      expect(subject.attachments).to eq([text: message, color: color])
+    end
+  end
+
+  context 'pipeline failed' do
+    let(:status) { 'failed' }
+    let(:color) { 'danger' }
+    let(:duration) { 10 }
+
+    it 'returns a message with information about failed build' do
+      expect(subject.pretext).to be_empty
+      expect(subject.fallback).to eq(message)
+      expect(subject.attachments).to eq([text: message, color: color])
+    end
+  end
+
+  def build_message(status_text = status)
+    "<example.gitlab.com|project_name>:" \
+    " Pipeline <example.gitlab.com/pipelines/123|97de212e>" \
+    " of <example.gitlab.com/commits/develop|develop> branch" \
+    " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
+  end
+end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 28af68d13b49b64ff9fbb0076e528012f5e5d1a2..c07a70a806965975c637e5a877b7225811c95a71 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -1,26 +1,9 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe SlackService, models: true do
+  let(:slack) { SlackService.new }
+  let(:webhook_url) { 'https://example.gitlab.com/' }
+
   describe "Associations" do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
@@ -42,15 +25,14 @@ describe SlackService, models: true do
   end
 
   describe "Execute" do
-    let(:slack)   { SlackService.new }
     let(:user)    { create(:user) }
     let(:project) { create(:project) }
+    let(:username) { 'slack_username' }
+    let(:channel)  { 'slack_channel' }
+
     let(:push_sample_data) do
       Gitlab::DataBuilder::Push.build_sample(project, user)
     end
-    let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
-    let(:username) { 'slack_username' }
-    let(:channel) { 'slack_channel' }
 
     before do
       allow(slack).to receive_messages(
@@ -212,10 +194,8 @@ describe SlackService, models: true do
   end
 
   describe "Note events" do
-    let(:slack)   { SlackService.new }
     let(:user) { create(:user) }
     let(:project) { create(:project, creator_id: user.id) }
-    let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
 
     before do
       allow(slack).to receive_messages(
@@ -285,4 +265,63 @@ describe SlackService, models: true do
       end
     end
   end
+
+  describe 'Pipeline events' do
+    let(:user) { create(:user) }
+    let(:project) { create(:project) }
+
+    let(:pipeline) do
+      create(:ci_pipeline,
+             project: project, status: status,
+             sha: project.commit.sha, ref: project.default_branch)
+    end
+
+    before do
+      allow(slack).to receive_messages(
+        project: project,
+        service_hook: true,
+        webhook: webhook_url
+      )
+    end
+
+    shared_examples 'call Slack API' do
+      before do
+        WebMock.stub_request(:post, webhook_url)
+      end
+
+      it 'calls Slack API for pipeline events' do
+        data = Gitlab::DataBuilder::Pipeline.build(pipeline)
+        slack.execute(data)
+
+        expect(WebMock).to have_requested(:post, webhook_url).once
+      end
+    end
+
+    context 'with failed pipeline' do
+      let(:status) { 'failed' }
+
+      it_behaves_like 'call Slack API'
+    end
+
+    context 'with succeeded pipeline' do
+      let(:status) { 'success' }
+
+      context 'with default to notify_only_broken_pipelines' do
+        it 'does not call Slack API for pipeline events' do
+          data = Gitlab::DataBuilder::Pipeline.build(pipeline)
+          result = slack.execute(data)
+
+          expect(result).to be_falsy
+        end
+      end
+
+      context 'with setting notify_only_broken_pipelines to false' do
+        before do
+          slack.notify_only_broken_pipelines = false
+        end
+
+        it_behaves_like 'call Slack API'
+      end
+    end
+  end
 end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 474715d24c3adbcd7cde80b017bba2aa72d9f5b5..f7e878844dcff4d87a01b589aa45242abccfb8a3 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
 require 'spec_helper'
 
 describe TeamcityService, models: true do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 4a41fafb84d911b4b042b3c1fa9b545a474e0198..a388ff703a6354586c83d7a7fd0162da67feb446 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -6,6 +6,7 @@ describe Project, models: true do
     it { is_expected.to belong_to(:namespace) }
     it { is_expected.to belong_to(:creator).class_name('User') }
     it { is_expected.to have_many(:users) }
+    it { is_expected.to have_many(:services) }
     it { is_expected.to have_many(:events).dependent(:destroy) }
     it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
     it { is_expected.to have_many(:issues).dependent(:destroy) }
@@ -24,6 +25,30 @@ describe Project, models: true do
     it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
     it { is_expected.to have_one(:asana_service).dependent(:destroy) }
     it { is_expected.to have_one(:board).dependent(:destroy) }
+    it { is_expected.to have_one(:campfire_service).dependent(:destroy) }
+    it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) }
+    it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) }
+    it { is_expected.to have_one(:builds_email_service).dependent(:destroy) }
+    it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) }
+    it { is_expected.to have_one(:irker_service).dependent(:destroy) }
+    it { is_expected.to have_one(:pivotaltracker_service).dependent(:destroy) }
+    it { is_expected.to have_one(:hipchat_service).dependent(:destroy) }
+    it { is_expected.to have_one(:flowdock_service).dependent(:destroy) }
+    it { is_expected.to have_one(:assembla_service).dependent(:destroy) }
+    it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) }
+    it { is_expected.to have_one(:buildkite_service).dependent(:destroy) }
+    it { is_expected.to have_one(:bamboo_service).dependent(:destroy) }
+    it { is_expected.to have_one(:teamcity_service).dependent(:destroy) }
+    it { is_expected.to have_one(:jira_service).dependent(:destroy) }
+    it { is_expected.to have_one(:redmine_service).dependent(:destroy) }
+    it { is_expected.to have_one(:custom_issue_tracker_service).dependent(:destroy) }
+    it { is_expected.to have_one(:bugzilla_service).dependent(:destroy) }
+    it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) }
+    it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) }
+    it { is_expected.to have_one(:project_feature).dependent(:destroy) }
+    it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:destroy) }
+    it { is_expected.to have_one(:last_event).class_name('Event') }
+    it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
     it { is_expected.to have_many(:commit_statuses) }
     it { is_expected.to have_many(:pipelines) }
     it { is_expected.to have_many(:builds) }
@@ -31,9 +56,16 @@ describe Project, models: true do
     it { is_expected.to have_many(:runners) }
     it { is_expected.to have_many(:variables) }
     it { is_expected.to have_many(:triggers) }
+    it { is_expected.to have_many(:labels).dependent(:destroy) }
+    it { is_expected.to have_many(:users_star_projects).dependent(:destroy) }
     it { is_expected.to have_many(:environments).dependent(:destroy) }
     it { is_expected.to have_many(:deployments).dependent(:destroy) }
     it { is_expected.to have_many(:todos).dependent(:destroy) }
+    it { is_expected.to have_many(:releases).dependent(:destroy) }
+    it { is_expected.to have_many(:lfs_objects_projects).dependent(:destroy) }
+    it { is_expected.to have_many(:project_group_links).dependent(:destroy) }
+    it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
+    it { is_expected.to have_many(:forks).through(:forked_project_links) }
 
     describe '#members & #requesters' do
       let(:project) { create(:project) }
@@ -178,7 +210,7 @@ describe Project, models: true do
       expect(project.runners_token).not_to eq('')
     end
 
-    it 'does not set an random toke if one provided' do
+    it 'does not set an random token if one provided' do
       project = FactoryGirl.create :empty_project, runners_token: 'my-token'
       expect(project.runners_token).to eq('my-token')
     end
@@ -276,20 +308,23 @@ describe Project, models: true do
   end
 
   describe 'last_activity methods' do
-    let(:project) { create(:project) }
-    let(:last_event) { double(created_at: Time.now) }
+    let(:timestamp) { Time.now - 2.hours }
+    let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
 
     describe 'last_activity' do
       it 'alias last_activity to last_event' do
-        allow(project).to receive(:last_event).and_return(last_event)
+        last_event = create(:event, project: project)
+
         expect(project.last_activity).to eq(last_event)
       end
     end
 
     describe 'last_activity_date' do
       it 'returns the creation date of the project\'s last event if present' do
-        create(:event, project: project)
-        expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i)
+        expect_any_instance_of(Event).to receive(:try_obtain_lease).and_return(true)
+        new_event = create(:event, project: project, created_at: Time.now)
+
+        expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
       end
 
       it 'returns the project\'s last update date if it has no events' do
@@ -1385,6 +1420,68 @@ describe Project, models: true do
     end
   end
 
+  describe '#lfs_enabled?' do
+    let(:project) { create(:project) }
+
+    shared_examples 'project overrides group' do
+      it 'returns true when enabled in project' do
+        project.update_attribute(:lfs_enabled, true)
+
+        expect(project.lfs_enabled?).to be_truthy
+      end
+
+      it 'returns false when disabled in project' do
+        project.update_attribute(:lfs_enabled, false)
+
+        expect(project.lfs_enabled?).to be_falsey
+      end
+
+      it 'returns the value from the namespace, when no value is set in project' do
+        expect(project.lfs_enabled?).to eq(project.namespace.lfs_enabled?)
+      end
+    end
+
+    context 'LFS disabled in group' do
+      before do
+        project.namespace.update_attribute(:lfs_enabled, false)
+        enable_lfs
+      end
+
+      it_behaves_like 'project overrides group'
+    end
+
+    context 'LFS enabled in group' do
+      before do
+        project.namespace.update_attribute(:lfs_enabled, true)
+        enable_lfs
+      end
+
+      it_behaves_like 'project overrides group'
+    end
+
+    describe 'LFS disabled globally' do
+      shared_examples 'it always returns false' do
+        it do
+          expect(project.lfs_enabled?).to be_falsey
+          expect(project.namespace.lfs_enabled?).to be_falsey
+        end
+      end
+
+      context 'when no values are set' do
+        it_behaves_like 'it always returns false'
+      end
+
+      context 'when all values are set to true' do
+        before do
+          project.namespace.update_attribute(:lfs_enabled, true)
+          project.update_attribute(:lfs_enabled, true)
+        end
+
+        it_behaves_like 'it always returns false'
+      end
+    end
+  end
+
   describe '.where_paths_in' do
     context 'without any paths' do
       it 'returns an empty relation' do
@@ -1497,4 +1594,60 @@ describe Project, models: true do
       project.change_head(project.default_branch)
     end
   end
+
+  describe '#pushes_since_gc' do
+    let(:project) { create(:project) }
+
+    after do
+      project.reset_pushes_since_gc
+    end
+
+    context 'without any pushes' do
+      it 'returns 0' do
+        expect(project.pushes_since_gc).to eq(0)
+      end
+    end
+
+    context 'with a number of pushes' do
+      it 'returns the number of pushes' do
+        3.times { project.increment_pushes_since_gc }
+
+        expect(project.pushes_since_gc).to eq(3)
+      end
+    end
+  end
+
+  describe '#increment_pushes_since_gc' do
+    let(:project) { create(:project) }
+
+    after do
+      project.reset_pushes_since_gc
+    end
+
+    it 'increments the number of pushes since the last GC' do
+      3.times { project.increment_pushes_since_gc }
+
+      expect(project.pushes_since_gc).to eq(3)
+    end
+  end
+
+  describe '#reset_pushes_since_gc' do
+    let(:project) { create(:project) }
+
+    after do
+      project.reset_pushes_since_gc
+    end
+
+    it 'resets the number of pushes since the last GC' do
+      3.times { project.increment_pushes_since_gc }
+
+      project.reset_pushes_since_gc
+
+      expect(project.pushes_since_gc).to eq(0)
+    end
+  end
+
+  def enable_lfs
+    allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+  end
 end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index afc7dc5db81679965de5dff26053e815c2bed55b..94681004c9619164fc9499a36a55be5b185ec94c 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -186,32 +186,6 @@ describe Repository, models: true do
       it { is_expected.to be_an String }
       it { expect(subject.lines[2]).to eq("master:CHANGELOG:188:  - Feature: Replace teams with group membership\n") }
     end
-
-    describe 'parsing result' do
-      subject { repository.parse_search_result(search_result) }
-      let(:search_result) { results.first }
-
-      it { is_expected.to be_an OpenStruct }
-      it { expect(subject.filename).to eq('CHANGELOG') }
-      it { expect(subject.basename).to eq('CHANGELOG') }
-      it { expect(subject.ref).to eq('master') }
-      it { expect(subject.startline).to eq(186) }
-      it { expect(subject.data.lines[2]).to eq("  - Feature: Replace teams with group membership\n") }
-
-      context "when filename has extension" do
-        let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
-
-        it { expect(subject.filename).to eq('CONTRIBUTE.md') }
-        it { expect(subject.basename).to eq('CONTRIBUTE') }
-      end
-
-      context "when file under directory" do
-        let(:search_result) { "master:a/b/c.md:5:a b c\n" }
-
-        it { expect(subject.filename).to eq('a/b/c.md') }
-        it { expect(subject.basename).to eq('a/b/c') }
-      end
-    end
   end
 
   describe "#changelog" do
@@ -441,7 +415,7 @@ describe Repository, models: true do
     end
   end
 
-  describe '#commit_with_hooks' do
+  describe '#update_branch_with_hooks' do
     let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
     let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev
 
@@ -454,31 +428,64 @@ describe Repository, models: true do
 
       it 'runs without errors' do
         expect do
-          repository.commit_with_hooks(user, 'feature') { new_rev }
+          repository.update_branch_with_hooks(user, 'feature') { new_rev }
         end.not_to raise_error
       end
 
       it 'ensures the autocrlf Git option is set to :input' do
         expect(repository).to receive(:update_autocrlf_option)
 
-        repository.commit_with_hooks(user, 'feature') { new_rev }
+        repository.update_branch_with_hooks(user, 'feature') { new_rev }
       end
 
       context "when the branch wasn't empty" do
         it 'updates the head' do
           expect(repository.find_branch('feature').target.id).to eq(old_rev)
-          repository.commit_with_hooks(user, 'feature') { new_rev }
+          repository.update_branch_with_hooks(user, 'feature') { new_rev }
           expect(repository.find_branch('feature').target.id).to eq(new_rev)
         end
       end
     end
 
+    context 'when the update adds more than one commit' do
+      it 'runs without errors' do
+        old_rev = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
+
+        # old_rev is an ancestor of new_rev
+        expect(repository.rugged.merge_base(old_rev, new_rev)).to eq(old_rev)
+
+        # old_rev is not a direct ancestor (parent) of new_rev
+        expect(repository.rugged.lookup(new_rev).parent_ids).not_to include(old_rev)
+
+        branch = 'feature-ff-target'
+        repository.add_branch(user, branch, old_rev)
+
+        expect { repository.update_branch_with_hooks(user, branch) { new_rev } }.not_to raise_error
+      end
+    end
+
+    context 'when the update would remove commits from the target branch' do
+      it 'raises an exception' do
+        branch = 'master'
+        old_rev = repository.find_branch(branch).target.sha
+
+        # The 'master' branch is NOT an ancestor of new_rev.
+        expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev)
+
+        # Updating 'master' to new_rev would lose the commits on 'master' that
+        # are not contained in new_rev. This should not be allowed.
+        expect do
+          repository.update_branch_with_hooks(user, branch) { new_rev }
+        end.to raise_error(Repository::CommitError)
+      end
+    end
+
     context 'when pre hooks failed' do
       it 'gets an error' do
         allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
 
         expect do
-          repository.commit_with_hooks(user, 'feature') { new_rev }
+          repository.update_branch_with_hooks(user, 'feature') { new_rev }
         end.to raise_error(GitHooksService::PreReceiveError)
       end
     end
@@ -497,7 +504,7 @@ describe Repository, models: true do
         expect(repository).to     receive(:expire_has_visible_content_cache)
         expect(repository).to     receive(:expire_branch_count_cache)
 
-        repository.commit_with_hooks(user, 'new-feature') { new_rev }
+        repository.update_branch_with_hooks(user, 'new-feature') { new_rev }
       end
     end
 
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 2d6093fec7a467eb9b7545729ef348cefae68e25..7aa7e85a9e272403561982e46aa91a7ec40406e4 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -117,17 +117,36 @@ describe API::CommitStatuses, api: true do
     let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" }
 
     context 'developer user' do
-      context 'only required parameters' do
-        before { post api(post_url, developer), state: 'success' }
+      %w[pending running success failed canceled].each do |status|
+        context "for #{status}" do
+          context 'uses only required parameters' do
+            it 'creates commit status' do
+              post api(post_url, developer), state: status
+
+              expect(response).to have_http_status(201)
+              expect(json_response['sha']).to eq(commit.id)
+              expect(json_response['status']).to eq(status)
+              expect(json_response['name']).to eq('default')
+              expect(json_response['ref']).not_to be_empty
+              expect(json_response['target_url']).to be_nil
+              expect(json_response['description']).to be_nil
+            end
+          end
+        end
+      end
 
-        it 'creates commit status' do
-          expect(response).to have_http_status(201)
-          expect(json_response['sha']).to eq(commit.id)
-          expect(json_response['status']).to eq('success')
-          expect(json_response['name']).to eq('default')
-          expect(json_response['ref']).to be_nil
-          expect(json_response['target_url']).to be_nil
-          expect(json_response['description']).to be_nil
+      context 'transitions status from pending' do
+        before do
+          post api(post_url, developer), state: 'pending'
+        end
+
+        %w[running success failed canceled].each do |status|
+          it "to #{status}" do
+            expect { post api(post_url, developer), state: status }.not_to change { CommitStatus.count }
+
+            expect(response).to have_http_status(201)
+            expect(json_response['status']).to eq(status)
+          end
         end
       end
 
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 5b3dc60aba2fcdf515ac066f7e934316a2c25b05..10f772c5b1adc5fd24e7dd25fdd24df1346ad565 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -110,7 +110,7 @@ describe API::API, api: true  do
         get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
 
         expect(response).to have_http_status(200)
-        expect(json_response['status']).to be_nil
+        expect(json_response['status']).to eq("created")
       end
     end
 
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 4860b23c2ed7bfa2f68648b18c6ebac1bba93c64..1f68ef1af8fc645784d39c6899547770e6b51784 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -120,10 +120,11 @@ describe API::API, api: true  do
 
     context 'when authenticated as the group owner' do
       it 'updates the group' do
-        put api("/groups/#{group1.id}", user1), name: new_group_name
+        put api("/groups/#{group1.id}", user1), name: new_group_name, request_access_enabled: true
 
         expect(response).to have_http_status(200)
         expect(json_response['name']).to eq(new_group_name)
+        expect(json_response['request_access_enabled']).to eq(true)
       end
 
       it 'returns 404 for a non existing group' do
@@ -238,8 +239,14 @@ describe API::API, api: true  do
 
     context "when authenticated as user with group permissions" do
       it "creates group" do
-        post api("/groups", user3), attributes_for(:group)
+        group = attributes_for(:group, { request_access_enabled: false })
+
+        post api("/groups", user3), group
         expect(response).to have_http_status(201)
+
+        expect(json_response["name"]).to eq(group[:name])
+        expect(json_response["path"]).to eq(group[:path])
+        expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled])
       end
 
       it "does not create group, duplicate" do
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 47344a13b5e8fd66adaab19ad0ce8528e49e57a2..f840778ae9b5662b5e2cacd06562aec7e7f83ba1 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -17,21 +17,27 @@ describe API::API, api: true  do
            assignee: user,
            project: project,
            state: :closed,
-           milestone: milestone
+           milestone: milestone,
+           created_at: generate(:issue_created_at),
+           updated_at: 3.hours.ago
   end
   let!(:confidential_issue) do
     create :issue,
            :confidential,
            project: project,
            author: author,
-           assignee: assignee
+           assignee: assignee,
+           created_at: generate(:issue_created_at),
+           updated_at: 2.hours.ago
   end
   let!(:issue) do
     create :issue,
            author: user,
            assignee: user,
            project: project,
-           milestone: milestone
+           milestone: milestone,
+           created_at: generate(:issue_created_at),
+           updated_at: 1.hour.ago
   end
   let!(:label) do
     create(:label, title: 'label', color: '#FFAABB', project: project)
@@ -135,6 +141,42 @@ describe API::API, api: true  do
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(0)
       end
+
+      it 'sorts by created_at descending by default' do
+        get api('/issues', user)
+        response_dates = json_response.map { |issue| issue['created_at'] }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(response_dates).to eq(response_dates.sort.reverse)
+      end
+
+      it 'sorts ascending when requested' do
+        get api('/issues?sort=asc', user)
+        response_dates = json_response.map { |issue| issue['created_at'] }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(response_dates).to eq(response_dates.sort)
+      end
+
+      it 'sorts by updated_at descending when requested' do
+        get api('/issues?order_by=updated_at', user)
+        response_dates = json_response.map { |issue| issue['updated_at'] }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(response_dates).to eq(response_dates.sort.reverse)
+      end
+
+      it 'sorts by updated_at ascending when requested' do
+        get api('/issues?order_by=updated_at&sort=asc', user)
+        response_dates = json_response.map { |issue| issue['updated_at'] }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(response_dates).to eq(response_dates.sort)
+      end
     end
   end
 
@@ -147,21 +189,24 @@ describe API::API, api: true  do
              assignee: user,
              project: group_project,
              state: :closed,
-             milestone: group_milestone
+             milestone: group_milestone,
+             updated_at: 3.hours.ago
     end
     let!(:group_confidential_issue) do
       create :issue,
              :confidential,
              project: group_project,
              author: author,
-             assignee: assignee
+             assignee: assignee,
+             updated_at: 2.hours.ago
     end
     let!(:group_issue) do
       create :issue,
              author: user,
              assignee: user,
              project: group_project,
-             milestone: group_milestone
+             milestone: group_milestone,
+             updated_at: 1.hour.ago
     end
     let!(:group_label) do
       create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
@@ -278,6 +323,42 @@ describe API::API, api: true  do
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(group_closed_issue.id)
     end
+
+    it 'sorts by created_at descending by default' do
+      get api(base_url, user)
+      response_dates = json_response.map { |issue| issue['created_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort.reverse)
+    end
+
+    it 'sorts ascending when requested' do
+      get api("#{base_url}?sort=asc", user)
+      response_dates = json_response.map { |issue| issue['created_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort)
+    end
+
+    it 'sorts by updated_at descending when requested' do
+      get api("#{base_url}?order_by=updated_at", user)
+      response_dates = json_response.map { |issue| issue['updated_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort.reverse)
+    end
+
+    it 'sorts by updated_at ascending when requested' do
+      get api("#{base_url}?order_by=updated_at&sort=asc", user)
+      response_dates = json_response.map { |issue| issue['updated_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort)
+    end
   end
 
   describe "GET /projects/:id/issues" do
@@ -386,6 +467,42 @@ describe API::API, api: true  do
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(closed_issue.id)
     end
+
+    it 'sorts by created_at descending by default' do
+      get api("#{base_url}/issues", user)
+      response_dates = json_response.map { |issue| issue['created_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort.reverse)
+    end
+
+    it 'sorts ascending when requested' do
+      get api("#{base_url}/issues?sort=asc", user)
+      response_dates = json_response.map { |issue| issue['created_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort)
+    end
+
+    it 'sorts by updated_at descending when requested' do
+      get api("#{base_url}/issues?order_by=updated_at", user)
+      response_dates = json_response.map { |issue| issue['updated_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort.reverse)
+    end
+
+    it 'sorts by updated_at ascending when requested' do
+      get api("#{base_url}/issues?order_by=updated_at&sort=asc", user)
+      response_dates = json_response.map { |issue| issue['updated_at'] }
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(response_dates).to eq(response_dates.sort)
+    end
   end
 
   describe "GET /projects/:id/issues/:issue_id" do
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..391fc13a380f56ff2fd997f221685e1f166d4146
--- /dev/null
+++ b/spec/requests/api/lint_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe API::Lint, api: true do
+  include ApiHelpers
+
+  describe 'POST /ci/lint' do
+    context 'with valid .gitlab-ci.yaml content' do
+      let(:yaml_content) do
+        File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+      end
+
+      it 'passes validation' do
+        post api('/ci/lint'), { content: yaml_content }
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Hash
+        expect(json_response['status']).to eq('valid')
+        expect(json_response['errors']).to eq([])
+      end
+    end
+
+    context 'with an invalid .gitlab_ci.yml' do
+      it 'responds with errors about invalid syntax' do
+        post api('/ci/lint'), { content: 'invalid content' }
+
+        expect(response).to have_http_status(200)
+        expect(json_response['status']).to eq('invalid')
+        expect(json_response['errors']).to eq(['Invalid configuration format'])
+      end
+
+      it "responds with errors about invalid configuration" do
+        post api('/ci/lint'), { content: '{ image: "ruby:2.1",  services: ["postgres"] }' }
+
+        expect(response).to have_http_status(200)
+        expect(json_response['status']).to eq('invalid')
+        expect(json_response['errors']).to eq(['jobs config should contain at least one visible job'])
+      end
+    end
+
+    context 'without the content parameter' do
+      it 'responds with validation error about missing content' do
+        post api('/ci/lint')
+
+        expect(response).to have_http_status(400)
+        expect(json_response['error']).to eq('content is missing')
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 1e365bf353a9e133de342456237ef9a65e379021..92032f09b1759503342ef3da689368ef96147da1 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -30,20 +30,29 @@ describe API::Members, api: true  do
         let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members", stranger) }
       end
 
-      context 'when authenticated as a non-member' do
-        %i[access_requester stranger].each do |type|
-          context "as a #{type}" do
-            it 'returns 200' do
-              user = public_send(type)
-              get api("/#{source_type.pluralize}/#{source.id}/members", user)
+      %i[master developer access_requester stranger].each do |type|
+        context "when authenticated as a #{type}" do
+          it 'returns 200' do
+            user = public_send(type)
+            get api("/#{source_type.pluralize}/#{source.id}/members", user)
 
-              expect(response).to have_http_status(200)
-              expect(json_response.size).to eq(2)
-            end
+            expect(response).to have_http_status(200)
+            expect(json_response.size).to eq(2)
+            expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
           end
         end
       end
 
+      it 'does not return invitees' do
+        create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil)
+
+        get api("/#{source_type.pluralize}/#{source.id}/members", developer)
+
+        expect(response).to have_http_status(200)
+        expect(json_response.size).to eq(2)
+        expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+      end
+
       it 'finds members with query string' do
         get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
 
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index d6a0c656e7495dccdf43e21145a41d169962487e..b89dac01040194974002bd50353a02f451969f1d 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe API::API, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
-  let!(:project) { create(:project, namespace: user.namespace ) }
+  let!(:project) { create(:empty_project, namespace: user.namespace ) }
   let!(:closed_milestone) { create(:closed_milestone, project: project) }
   let!(:milestone) { create(:milestone, project: project) }
 
@@ -12,6 +12,7 @@ describe API::API, api: true  do
   describe 'GET /projects/:id/milestones' do
     it 'returns project milestones' do
       get api("/projects/#{project.id}/milestones", user)
+
       expect(response).to have_http_status(200)
       expect(json_response).to be_an Array
       expect(json_response.first['title']).to eq(milestone.title)
@@ -19,6 +20,7 @@ describe API::API, api: true  do
 
     it 'returns a 401 error if user not authenticated' do
       get api("/projects/#{project.id}/milestones")
+
       expect(response).to have_http_status(401)
     end
 
@@ -44,6 +46,7 @@ describe API::API, api: true  do
   describe 'GET /projects/:id/milestones/:milestone_id' do
     it 'returns a project milestone by id' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
+
       expect(response).to have_http_status(200)
       expect(json_response['title']).to eq(milestone.title)
       expect(json_response['iid']).to eq(milestone.iid)
@@ -60,11 +63,13 @@ describe API::API, api: true  do
 
     it 'returns 401 error if user not authenticated' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}")
+
       expect(response).to have_http_status(401)
     end
 
     it 'returns a 404 error if milestone id not found' do
       get api("/projects/#{project.id}/milestones/1234", user)
+
       expect(response).to have_http_status(404)
     end
   end
@@ -72,6 +77,7 @@ describe API::API, api: true  do
   describe 'POST /projects/:id/milestones' do
     it 'creates a new project milestone' do
       post api("/projects/#{project.id}/milestones", user), title: 'new milestone'
+
       expect(response).to have_http_status(201)
       expect(json_response['title']).to eq('new milestone')
       expect(json_response['description']).to be_nil
@@ -80,6 +86,7 @@ describe API::API, api: true  do
     it 'creates a new project milestone with description and due date' do
       post api("/projects/#{project.id}/milestones", user),
         title: 'new milestone', description: 'release', due_date: '2013-03-02'
+
       expect(response).to have_http_status(201)
       expect(json_response['description']).to eq('release')
       expect(json_response['due_date']).to eq('2013-03-02')
@@ -87,6 +94,14 @@ describe API::API, api: true  do
 
     it 'returns a 400 error if title is missing' do
       post api("/projects/#{project.id}/milestones", user)
+
+      expect(response).to have_http_status(400)
+    end
+
+    it 'returns a 400 error if params are invalid (duplicate title)' do
+      post api("/projects/#{project.id}/milestones", user),
+        title: milestone.title, description: 'release', due_date: '2013-03-02'
+
       expect(response).to have_http_status(400)
     end
   end
@@ -95,6 +110,7 @@ describe API::API, api: true  do
     it 'updates a project milestone' do
       put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
         title: 'updated title'
+
       expect(response).to have_http_status(200)
       expect(json_response['title']).to eq('updated title')
     end
@@ -102,6 +118,7 @@ describe API::API, api: true  do
     it 'returns a 404 error if milestone id not found' do
       put api("/projects/#{project.id}/milestones/1234", user),
         title: 'updated title'
+
       expect(response).to have_http_status(404)
     end
   end
@@ -131,6 +148,7 @@ describe API::API, api: true  do
     end
     it 'returns project issues for a particular milestone' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+
       expect(response).to have_http_status(200)
       expect(json_response).to be_an Array
       expect(json_response.first['milestone']['title']).to eq(milestone.title)
@@ -138,11 +156,12 @@ describe API::API, api: true  do
 
     it 'returns a 401 error if user not authenticated' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
+
       expect(response).to have_http_status(401)
     end
 
     describe 'confidential issues' do
-      let(:public_project) { create(:project, :public) }
+      let(:public_project) { create(:empty_project, :public) }
       let(:milestone) { create(:milestone, project: public_project) }
       let(:issue) { create(:issue, project: public_project) }
       let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 223444ea39fc62cd519f152401bd0ba3a07de4a6..063a8706e76fee4084c09d92ddf0753385729e6d 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -220,6 +220,15 @@ describe API::API, api: true  do
           expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time)
         end
       end
+
+      context 'when the user is posting an award emoji' do
+        it 'returns an award emoji' do
+          post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: ':+1:'
+
+          expect(response).to have_http_status(201)
+          expect(json_response['awardable_id']).to eq issue.id
+        end
+      end
     end
 
     context "when noteable is a Snippet" do
diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e6d8a5ee95407914659bace6e3055fdb30421a10
--- /dev/null
+++ b/spec/requests/api/notification_settings_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+  include ApiHelpers
+
+  let(:user) { create(:user) }
+  let!(:group) { create(:group) }
+  let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) }
+
+  describe "GET /notification_settings" do
+    it "returns global notification settings for the current user" do
+      get api("/notification_settings", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a Hash
+      expect(json_response['notification_email']).to eq(user.notification_email)
+      expect(json_response['level']).to eq(user.global_notification_setting.level)
+    end
+  end
+
+  describe "PUT /notification_settings" do
+    let(:email) { create(:email, user: user) }
+
+    it "updates global notification settings for the current user" do
+      put api("/notification_settings", user), { level: 'watch', notification_email: email.email }
+
+      expect(response).to have_http_status(200)
+      expect(json_response['notification_email']).to eq(email.email)
+      expect(user.reload.notification_email).to eq(email.email)
+      expect(json_response['level']).to eq(user.reload.global_notification_setting.level)
+    end
+  end
+
+  describe "PUT /notification_settings" do
+    it "fails on non-user email address" do
+      put api("/notification_settings", user), { notification_email: 'invalid@example.com' }
+
+      expect(response).to have_http_status(400)
+    end
+  end
+
+  describe "GET /groups/:id/notification_settings" do
+    it "returns group level notification settings for the current user" do
+      get api("/groups/#{group.id}/notification_settings", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a Hash
+      expect(json_response['level']).to eq(user.notification_settings_for(group).level)
+    end
+  end
+
+  describe "PUT /groups/:id/notification_settings" do
+    it "updates group level notification settings for the current user" do
+      put api("/groups/#{group.id}/notification_settings", user), { level: 'watch' }
+
+      expect(response).to have_http_status(200)
+      expect(json_response['level']).to eq(user.reload.notification_settings_for(group).level)
+    end
+  end
+
+  describe "GET /projects/:id/notification_settings" do
+    it "returns project level notification settings for the current user" do
+      get api("/projects/#{project.id}/notification_settings", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a Hash
+      expect(json_response['level']).to eq(user.notification_settings_for(project).level)
+    end
+  end
+
+  describe "PUT /projects/:id/notification_settings" do
+    it "updates project level notification settings for the current user" do
+      put api("/projects/#{project.id}/notification_settings", user), { level: 'custom', new_note: true }
+
+      expect(response).to have_http_status(200)
+      expect(json_response['level']).to eq(user.reload.notification_settings_for(project).level)
+      expect(json_response['events']['new_note']).to eq(true)
+      expect(json_response['events']['new_issue']).to eq(false)
+    end
+  end
+
+  describe "PUT /projects/:id/notification_settings" do
+    it "fails on invalid level" do
+      put api("/projects/#{project.id}/notification_settings", user), { level: 'invalid' }
+
+      expect(response).to have_http_status(400)
+    end
+  end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 28aa56e8644c513953e1d6bf7bf8f7f02710923b..192c7d14c13a9d6def0965777d839d1f98e07b01 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -225,7 +225,8 @@ describe API::API, api: true  do
         issues_enabled: false,
         merge_requests_enabled: false,
         wiki_enabled: false,
-        only_allow_merge_if_build_succeeds: false
+        only_allow_merge_if_build_succeeds: false,
+        request_access_enabled: true
       })
 
       post api('/projects', user), project
@@ -352,7 +353,8 @@ describe API::API, api: true  do
         description: FFaker::Lorem.sentence,
         issues_enabled: false,
         merge_requests_enabled: false,
-        wiki_enabled: false
+        wiki_enabled: false,
+        request_access_enabled: true
       })
 
       post api("/projects/user/#{user.id}", admin), project
@@ -887,6 +889,15 @@ describe API::API, api: true  do
         expect(json_response['message']['name']).to eq(['has already been taken'])
       end
 
+      it 'updates request_access_enabled' do
+        project_param = { request_access_enabled: false }
+
+        put api("/projects/#{project.id}", user), project_param
+
+        expect(response).to have_http_status(200)
+        expect(json_response['request_access_enabled']).to eq(false)
+      end
+
       it 'updates path & name to existing path & name in different namespace' do
         project_param = { path: project4.path, name: project4.name }
         put api("/projects/#{project3.id}", user), project_param
@@ -948,7 +959,8 @@ describe API::API, api: true  do
                           wiki_enabled: true,
                           snippets_enabled: true,
                           merge_requests_enabled: true,
-                          description: 'new description' }
+                          description: 'new description',
+                          request_access_enabled: true }
         put api("/projects/#{project.id}", user3), project_param
         expect(response).to have_http_status(403)
       end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index ca7932dc5da3d1ef2aa71232121ab8f84000367c..df97f1bf7b6a0e8b52546a3f7b6b1eb08caf89c3 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -15,6 +15,25 @@ describe Ci::API::API do
 
     describe "POST /builds/register" do
       let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
+      let(:user_agent) { 'gitlab-ci-multi-runner 1.5.2 (1-5-stable; go1.6.3; linux/amd64)' }
+
+      shared_examples 'no builds available' do
+        context 'when runner sends version in User-Agent' do
+          context 'for stable version' do
+            it { expect(response).to have_http_status(204) }
+          end
+
+          context 'for beta version' do
+            let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (1-5-stable; go1.6.3; linux/amd64)' }
+            it { expect(response).to have_http_status(204) }
+          end
+        end
+
+        context "when runner doesn't send version in User-Agent" do
+          let(:user_agent) { 'Go-http-client/1.1' }
+          it { expect(response).to have_http_status(404) }
+        end
+      end
 
       it "starts a build" do
         register_builds info: { platform: :darwin }
@@ -33,36 +52,30 @@ describe Ci::API::API do
       context 'when builds are finished' do
         before do
           build.success
-        end
-
-        it "returns 404 error if no builds for specific runner" do
           register_builds
-
-          expect(response).to have_http_status(404)
         end
+
+        it_behaves_like 'no builds available'
       end
 
       context 'for other project with builds' do
         before do
           build.success
           create(:ci_build, :pending)
-        end
-
-        it "returns 404 error if no builds for shared runner" do
           register_builds
-
-          expect(response).to have_http_status(404)
         end
+
+        it_behaves_like 'no builds available'
       end
 
       context 'for shared runner' do
         let(:shared_runner) { create(:ci_runner, token: "SharedRunner") }
 
-        it "should return 404 error if no builds for shared runner" do
+        before do
           register_builds shared_runner.token
-
-          expect(response).to have_http_status(404)
         end
+
+        it_behaves_like 'no builds available'
       end
 
       context 'for triggered build' do
@@ -136,18 +149,27 @@ describe Ci::API::API do
         end
 
         context 'when runner is not allowed to pick untagged builds' do
-          before { runner.update_column(:run_untagged, false) }
-
-          it 'does not pick build' do
+          before do
+            runner.update_column(:run_untagged, false)
             register_builds
-
-            expect(response).to have_http_status 404
           end
+
+          it_behaves_like 'no builds available'
+        end
+      end
+
+      context 'when runner is paused' do
+        let(:inactive_runner) { create(:ci_runner, :inactive, token: "InactiveRunner") }
+
+        before do
+          register_builds inactive_runner.token
         end
+
+        it { expect(response).to have_http_status 404 }
       end
 
       def register_builds(token = runner.token, **params)
-        post ci_api("/builds/register"), params.merge(token: token)
+        post ci_api("/builds/register"), params.merge(token: token), { 'User-Agent' => user_agent }
       end
     end
 
@@ -230,8 +252,10 @@ describe Ci::API::API do
       let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
       let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
       let(:get_url) { ci_api("/builds/#{build.id}/artifacts") }
-      let(:headers) { { "GitLab-Workhorse" => "1.0" } }
-      let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
+      let(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+      let(:headers) { { "GitLab-Workhorse" => "1.0", Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } }
+      let(:token) { build.token }
+      let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => token) }
 
       before { build.run! }
 
@@ -239,27 +263,51 @@ describe Ci::API::API do
         context "should authorize posting artifact to running build" do
           it "using token as parameter" do
             post authorize_url, { token: build.token }, headers
+
             expect(response).to have_http_status(200)
+            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
             expect(json_response["TempPath"]).not_to be_nil
           end
 
           it "using token as header" do
             post authorize_url, {}, headers_with_token
+
             expect(response).to have_http_status(200)
+            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
             expect(json_response["TempPath"]).not_to be_nil
           end
+
+          it "using runners token" do
+            post authorize_url, { token: build.project.runners_token }, headers
+
+            expect(response).to have_http_status(200)
+            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+            expect(json_response["TempPath"]).not_to be_nil
+          end
+
+          it "reject requests that did not go through gitlab-workhorse" do
+            headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+            post authorize_url, { token: build.token }, headers
+
+            expect(response).to have_http_status(500)
+          end
         end
 
         context "should fail to post too large artifact" do
           it "using token as parameter" do
             stub_application_setting(max_artifacts_size: 0)
+
             post authorize_url, { token: build.token, filesize: 100 }, headers
+
             expect(response).to have_http_status(413)
           end
 
           it "using token as header" do
             stub_application_setting(max_artifacts_size: 0)
+
             post authorize_url, { filesize: 100 }, headers_with_token
+
             expect(response).to have_http_status(413)
           end
         end
@@ -327,6 +375,16 @@ describe Ci::API::API do
 
               it_behaves_like 'successful artifacts upload'
             end
+
+            context 'when using runners token' do
+              let(:token) { build.project.runners_token }
+
+              before do
+                upload_artifacts(file_upload, headers_with_token)
+              end
+
+              it_behaves_like 'successful artifacts upload'
+            end
           end
 
           context 'posts artifacts file and metadata file' do
@@ -466,19 +524,40 @@ describe Ci::API::API do
 
         before do
           delete delete_url, token: build.token
-          build.reload
         end
 
-        it 'removes build artifacts' do
-          expect(response).to have_http_status(200)
-          expect(build.artifacts_file.exists?).to be_falsy
-          expect(build.artifacts_metadata.exists?).to be_falsy
-          expect(build.artifacts_size).to be_nil
+        shared_examples 'having removable artifacts' do
+          it 'removes build artifacts' do
+            build.reload
+
+            expect(response).to have_http_status(200)
+            expect(build.artifacts_file.exists?).to be_falsy
+            expect(build.artifacts_metadata.exists?).to be_falsy
+            expect(build.artifacts_size).to be_nil
+          end
+        end
+
+        context 'when using build token' do
+          before do
+            delete delete_url, token: build.token
+          end
+
+          it_behaves_like 'having removable artifacts'
+        end
+
+        context 'when using runnners token' do
+          before do
+            delete delete_url, token: build.project.runners_token
+          end
+
+          it_behaves_like 'having removable artifacts'
         end
       end
 
       describe 'GET /builds/:id/artifacts' do
-        before { get get_url, token: build.token }
+        before do
+          get get_url, token: token
+        end
 
         context 'build has artifacts' do
           let(:build) { create(:ci_build, :artifacts) }
@@ -487,13 +566,29 @@ describe Ci::API::API do
               'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
           end
 
-          it 'downloads artifact' do
-            expect(response).to have_http_status(200)
-            expect(response.headers).to include download_headers
+          shared_examples 'having downloadable artifacts' do
+            it 'download artifacts' do
+              expect(response).to have_http_status(200)
+              expect(response.headers).to include download_headers
+            end
+          end
+
+          context 'when using build token' do
+            let(:token) { build.token }
+
+            it_behaves_like 'having downloadable artifacts'
+          end
+
+          context 'when using runnners token' do
+            let(:token) { build.project.runners_token }
+
+            it_behaves_like 'having downloadable artifacts'
           end
         end
 
         context 'build does not has artifacts' do
+          let(:token) { build.token }
+
           it 'responds with not found' do
             expect(response).to have_http_status(404)
           end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 9ca3b021aa22fca0574b164a32e0995c6fd670d6..e3922bec6893be110f495e6859cee8036fe02636 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -1,6 +1,8 @@
 require "spec_helper"
 
 describe 'Git HTTP requests', lib: true do
+  include WorkhorseHelpers
+
   let(:user)    { create(:user) }
   let(:project) { create(:project, path: 'project.git-project') }
 
@@ -48,6 +50,7 @@ describe 'Git HTTP requests', lib: true do
 
         expect(response).to have_http_status(200)
         expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
+        expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
       end
     end
   end
@@ -63,6 +66,7 @@ describe 'Git HTTP requests', lib: true do
       it "downloads get status 200" do
         download(path, {}) do |response|
           expect(response).to have_http_status(200)
+          expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
         end
       end
 
@@ -101,6 +105,14 @@ describe 'Git HTTP requests', lib: true do
           end
         end
       end
+      
+      context 'when the request is not from gitlab-workhorse' do
+        it 'raises an exception' do
+          expect do
+            get("/#{project.path_with_namespace}.git/info/refs?service=git-upload-pack")
+          end.to raise_error(JWT::DecodeError)
+        end
+      end
     end
 
     context "when the project is private" do
@@ -170,11 +182,13 @@ describe 'Git HTTP requests', lib: true do
                 clone_get(path, env)
 
                 expect(response).to have_http_status(200)
+                expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
               end
 
               it "uploads get status 200" do
                 upload(path, env) do |response|
                   expect(response).to have_http_status(200)
+                  expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
                 end
               end
             end
@@ -189,6 +203,7 @@ describe 'Git HTTP requests', lib: true do
                 clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
 
                 expect(response).to have_http_status(200)
+                expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
               end
 
               it "uploads get status 401 (no project existence information leak)" do
@@ -285,24 +300,79 @@ describe 'Git HTTP requests', lib: true do
       end
 
       context "when a gitlab ci token is provided" do
-        let(:token) { 123 }
-        let(:project) { FactoryGirl.create :empty_project }
+        let(:build) { create(:ci_build, :running) }
+        let(:project) { build.project }
+        let(:other_project) { create(:empty_project) }
 
         before do
-          project.update_attributes(runners_token: token)
           project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED)
         end
 
-        it "downloads get status 200" do
-          clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
+        context 'when build created by system is authenticated' do
+          it "downloads get status 200" do
+            clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
 
-          expect(response).to have_http_status(200)
+            expect(response).to have_http_status(200)
+            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+          end
+
+          it "uploads get status 401 (no project existence information leak)" do
+            push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+            expect(response).to have_http_status(401)
+          end
+
+          it "downloads from other project get status 404" do
+            clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+            expect(response).to have_http_status(404)
+          end
         end
 
-        it "uploads get status 401 (no project existence information leak)" do
-          push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
+        context 'and build created by' do
+          before do
+            build.update(user: user)
+            project.team << [user, :reporter]
+          end
 
-          expect(response).to have_http_status(401)
+          shared_examples 'can download code only from own projects' do
+            it 'downloads get status 200' do
+              clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+              expect(response).to have_http_status(200)
+              expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+            end
+
+            it 'uploads get status 403' do
+              push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+              expect(response).to have_http_status(401)
+            end
+          end
+
+          context 'administrator' do
+            let(:user) { create(:admin) }
+
+            it_behaves_like 'can download code only from own projects'
+
+            it 'downloads from other project get status 403' do
+              clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+              expect(response).to have_http_status(403)
+            end
+          end
+
+          context 'regular user' do
+            let(:user) { create(:user) }
+
+            it_behaves_like 'can download code only from own projects'
+
+            it 'downloads from other project get status 404' do
+              clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+              expect(response).to have_http_status(404)
+            end
+          end
         end
       end
     end
@@ -426,7 +496,7 @@ describe 'Git HTTP requests', lib: true do
   end
 
   def auth_env(user, password, spnego_request_token)
-    env = {}
+    env = workhorse_internal_api_request_header
     if user && password
       env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password)
     elsif spnego_request_token
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index fc42b534dca7102c2dbc0cff1c4587c4875b577b..6b956e6300439852a33d9dcc23cce9495af7fcd8 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -22,11 +22,13 @@ describe JwtController do
 
   context 'when using authorized request' do
     context 'using CI token' do
-      let(:project) { create(:empty_project, runners_token: 'token') }
-      let(:headers) { { authorization: credentials('gitlab-ci-token', project.runners_token) } }
+      let(:build) { create(:ci_build, :running) }
+      let(:project) { build.project }
+      let(:headers) { { authorization: credentials('gitlab-ci-token', build.token) } }
 
       context 'project with enabled CI' do
         subject! { get '/jwt/auth', parameters, headers }
+
         it { expect(service_class).to have_received(:new).with(project, nil, parameters) }
       end
 
@@ -43,13 +45,31 @@ describe JwtController do
 
     context 'using User login' do
       let(:user) { create(:user) }
-      let(:headers) { { authorization: credentials('user', 'password') } }
-
-      before { expect(Gitlab::Auth).to receive(:find_with_user_password).with('user', 'password').and_return(user) }
+      let(:headers) { { authorization: credentials(user.username, user.password) } }
 
       subject! { get '/jwt/auth', parameters, headers }
 
       it { expect(service_class).to have_received(:new).with(nil, user, parameters) }
+
+      context 'when user has 2FA enabled' do
+        let(:user) { create(:user, :two_factor) }
+
+        context 'without personal token' do
+          it 'rejects the authorization attempt' do
+            expect(response).to have_http_status(401)
+            expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
+          end
+        end
+
+        context 'with personal token' do
+          let(:access_token) { create(:personal_access_token, user: user) }
+          let(:headers) { { authorization: credentials(user.username, access_token.token) } }
+
+          it 'rejects the authorization attempt' do
+            expect(response).to have_http_status(200)
+          end
+        end
+      end
     end
 
     context 'using invalid login' do
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index fcd6521317a0e5b7180d7f6fb081a198443722b6..b58d410b7a33811db4aebe010b7d2eb5365bcbd3 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe 'Git LFS API and storage' do
+  include WorkhorseHelpers
+
   let(:user) { create(:user) }
   let!(:lfs_object) { create(:lfs_object, :with_file) }
 
@@ -12,6 +14,7 @@ describe 'Git LFS API and storage' do
   end
   let(:authorization) { }
   let(:sendfile) { }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project) }
 
   let(:sample_oid) { lfs_object.oid }
   let(:sample_size) { lfs_object.size }
@@ -242,14 +245,63 @@ describe 'Git LFS API and storage' do
           end
         end
 
-        context 'when CI is authorized' do
+        context 'when build is authorized as' do
           let(:authorization) { authorize_ci_project }
 
-          let(:update_permissions) do
-            project.lfs_objects << lfs_object
+          shared_examples 'can download LFS only from own projects' do
+            context 'for own project' do
+              let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+              let(:update_permissions) do
+                project.team << [user, :reporter]
+                project.lfs_objects << lfs_object
+              end
+
+              it_behaves_like 'responds with a file'
+            end
+
+            context 'for other project' do
+              let(:other_project) { create(:empty_project) }
+              let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+
+              let(:update_permissions) do
+                project.lfs_objects << lfs_object
+              end
+
+              it 'rejects downloading code' do
+                expect(response).to have_http_status(other_project_status)
+              end
+            end
+          end
+
+          context 'administrator' do
+            let(:user) { create(:admin) }
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            it_behaves_like 'can download LFS only from own projects' do
+              # We render 403, because administrator does have normally access
+              let(:other_project_status) { 403 }
+            end
+          end
+
+          context 'regular user' do
+            let(:user) { create(:user) }
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            it_behaves_like 'can download LFS only from own projects' do
+              # We render 404, to prevent data leakage about existence of the project
+              let(:other_project_status) { 404 }
+            end
           end
 
-          it_behaves_like 'responds with a file'
+          context 'does not have user' do
+            let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+            it_behaves_like 'can download LFS only from own projects' do
+              # We render 404, to prevent data leakage about existence of the project
+              let(:other_project_status) { 404 }
+            end
+          end
         end
       end
 
@@ -429,10 +481,62 @@ describe 'Git LFS API and storage' do
         end
       end
 
-      context 'when CI is authorized' do
+      context 'when build is authorized as' do
         let(:authorization) { authorize_ci_project }
 
-        it_behaves_like 'an authorized requests'
+        let(:update_lfs_permissions) do
+          project.lfs_objects << lfs_object
+        end
+
+        shared_examples 'can download LFS only from own projects' do
+          context 'for own project' do
+            let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+            let(:update_user_permissions) do
+              project.team << [user, :reporter]
+            end
+
+            it_behaves_like 'an authorized requests'
+          end
+
+          context 'for other project' do
+            let(:other_project) { create(:empty_project) }
+            let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+
+            it 'rejects downloading code' do
+              expect(response).to have_http_status(other_project_status)
+            end
+          end
+        end
+
+        context 'administrator' do
+          let(:user) { create(:admin) }
+          let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+          it_behaves_like 'can download LFS only from own projects' do
+            # We render 403, because administrator does have normally access
+            let(:other_project_status) { 403 }
+          end
+        end
+
+        context 'regular user' do
+          let(:user) { create(:user) }
+          let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+          it_behaves_like 'can download LFS only from own projects' do
+            # We render 404, to prevent data leakage about existence of the project
+            let(:other_project_status) { 404 }
+          end
+        end
+
+        context 'does not have user' do
+          let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+          it_behaves_like 'can download LFS only from own projects' do
+            # We render 404, to prevent data leakage about existence of the project
+            let(:other_project_status) { 404 }
+          end
+        end
       end
 
       context 'when user is not authenticated' do
@@ -581,11 +685,37 @@ describe 'Git LFS API and storage' do
           end
         end
 
-        context 'when CI is authorized' do
+        context 'when build is authorized' do
           let(:authorization) { authorize_ci_project }
 
-          it 'responds with 401' do
-            expect(response).to have_http_status(401)
+          context 'build has an user' do
+            let(:user) { create(:user) }
+
+            context 'tries to push to own project' do
+              let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+              it 'responds with 401' do
+                expect(response).to have_http_status(401)
+              end
+            end
+
+            context 'tries to push to other project' do
+              let(:other_project) { create(:empty_project) }
+              let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+              let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+              it 'responds with 401' do
+                expect(response).to have_http_status(401)
+              end
+            end
+          end
+
+          context 'does not have user' do
+            let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
           end
         end
       end
@@ -607,14 +737,6 @@ describe 'Git LFS API and storage' do
           end
         end
       end
-
-      context 'when CI is authorized' do
-        let(:authorization) { authorize_ci_project }
-
-        it 'responds with status 403' do
-          expect(response).to have_http_status(401)
-        end
-      end
     end
 
     describe 'unsupported' do
@@ -715,6 +837,12 @@ describe 'Git LFS API and storage' do
             project.team << [user, :developer]
           end
 
+          context 'and the request bypassed workhorse' do
+            it 'raises an exception' do
+              expect { put_authorize(verified: false) }.to raise_error JWT::DecodeError
+            end
+          end
+
           context 'and request is sent by gitlab-workhorse to authorize the request' do
             before do
               put_authorize
@@ -724,6 +852,10 @@ describe 'Git LFS API and storage' do
               expect(response).to have_http_status(200)
             end
 
+            it 'uses the gitlab-workhorse content type' do
+              expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+            end
+
             it 'responds with status 200, location of lfs store and object details' do
               expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
               expect(json_response['LfsOid']).to eq(sample_oid)
@@ -767,10 +899,51 @@ describe 'Git LFS API and storage' do
         end
       end
 
-      context 'when CI is authenticated' do
+      context 'when build is authorized' do
         let(:authorization) { authorize_ci_project }
 
-        it_behaves_like 'unauthorized'
+        context 'build has an user' do
+          let(:user) { create(:user) }
+
+          context 'tries to push to own project' do
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            before do
+              project.team << [user, :developer]
+              put_authorize
+            end
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
+          end
+
+          context 'tries to push to other project' do
+            let(:other_project) { create(:empty_project) }
+            let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            before do
+              put_authorize
+            end
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
+          end
+        end
+
+        context 'does not have user' do
+          let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+          before do
+            put_authorize
+          end
+
+          it 'responds with 401' do
+            expect(response).to have_http_status(401)
+          end
+        end
       end
 
       context 'for unauthenticated' do
@@ -827,10 +1000,42 @@ describe 'Git LFS API and storage' do
         end
       end
 
-      context 'when CI is authenticated' do
+      context 'when build is authorized' do
         let(:authorization) { authorize_ci_project }
 
-        it_behaves_like 'unauthorized'
+        before do
+          put_authorize
+        end
+
+        context 'build has an user' do
+          let(:user) { create(:user) }
+
+          context 'tries to push to own project' do
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
+          end
+
+          context 'tries to push to other project' do
+            let(:other_project) { create(:empty_project) }
+            let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
+            let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
+
+            it 'responds with 401' do
+              expect(response).to have_http_status(401)
+            end
+          end
+        end
+
+        context 'does not have user' do
+          let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+
+          it 'responds with 401' do
+            expect(response).to have_http_status(401)
+          end
+        end
       end
 
       context 'for unauthenticated' do
@@ -863,8 +1068,11 @@ describe 'Git LFS API and storage' do
       end
     end
 
-    def put_authorize
-      put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, headers
+    def put_authorize(verified: true)
+      authorize_headers = headers
+      authorize_headers.merge!(workhorse_internal_api_request_header) if verified
+
+      put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, authorize_headers
     end
 
     def put_finalize(lfs_tmp = lfs_tmp_file)
@@ -882,7 +1090,7 @@ describe 'Git LFS API and storage' do
   end
 
   def authorize_ci_project
-    ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token)
+    ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', build.token)
   end
 
   def authorize_user
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 7cc71f706ce6c9638b473408ece6f204d5335125..c64df4979b096c29fe630f29d8f2ef596b96cee1 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -6,8 +6,14 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
   let(:current_params) { {} }
   let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
   let(:payload) { JWT.decode(subject[:token], rsa_key).first }
+  let(:authentication_abilities) do
+    [
+      :read_container_image,
+      :create_container_image
+    ]
+  end
 
-  subject { described_class.new(current_project, current_user, current_params).execute }
+  subject { described_class.new(current_project, current_user, current_params).execute(authentication_abilities: authentication_abilities) }
 
   before do
     allow(Gitlab.config.registry).to receive_messages(enabled: true, issuer: 'rspec', key: nil)
@@ -189,13 +195,22 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
     end
   end
 
-  context 'project authorization' do
+  context 'build authorized as user' do
     let(:current_project) { create(:empty_project) }
+    let(:current_user) { create(:user) }
+    let(:authentication_abilities) do
+      [
+        :build_read_container_image,
+        :build_create_container_image
+      ]
+    end
 
-    context 'allow to use scope-less authentication' do
-      it_behaves_like 'a valid token'
+    before do
+      current_project.team << [current_user, :developer]
     end
 
+    it_behaves_like 'a valid token'
+
     context 'allow to pull and push images' do
       let(:current_params) do
         { scope: "repository:#{current_project.path_with_namespace}:pull,push" }
@@ -214,12 +229,44 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
 
         context 'allow for public' do
           let(:project) { create(:empty_project, :public) }
+
           it_behaves_like 'a pullable'
         end
 
-        context 'disallow for private' do
+        shared_examples 'pullable for being team member' do
+          context 'when you are not member' do
+            it_behaves_like 'an inaccessible'
+          end
+
+          context 'when you are member' do
+            before do
+              project.team << [current_user, :developer]
+            end
+
+            it_behaves_like 'a pullable'
+          end
+        end
+
+        context 'for private' do
           let(:project) { create(:empty_project, :private) }
-          it_behaves_like 'an inaccessible'
+
+          it_behaves_like 'pullable for being team member'
+
+          context 'when you are admin' do
+            let(:current_user) { create(:admin) }
+
+            context 'when you are not member' do
+              it_behaves_like 'an inaccessible'
+            end
+
+            context 'when you are member' do
+              before do
+                project.team << [current_user, :developer]
+              end
+
+              it_behaves_like 'a pullable'
+            end
+          end
         end
       end
 
@@ -230,6 +277,11 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
 
         context 'disallow for all' do
           let(:project) { create(:empty_project, :public) }
+
+          before do
+            project.team << [current_user, :developer]
+          end
+
           it_behaves_like 'an inaccessible'
         end
       end
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index 5cc0f2ab974cb0074e03942171f2b4b651b8a123..f80f89534868ea8c49b9ba18345fe6b2df55ff23 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -41,7 +41,7 @@ describe CreateDeploymentService, services: true do
 
     context 'for environment with invalid name' do
       let(:params) do
-        { environment: 'name with spaces',
+        { environment: 'name,with,commas',
           ref: 'master',
           tag: false,
           sha: '97de212e80737a608d939f648d959671fb0a0142',
@@ -56,8 +56,36 @@ describe CreateDeploymentService, services: true do
         expect(subject).not_to be_persisted
       end
     end
+
+    context 'when variables are used' do
+      let(:params) do
+        { environment: 'review-apps/$CI_BUILD_REF_NAME',
+          ref: 'master',
+          tag: false,
+          sha: '97de212e80737a608d939f648d959671fb0a0142',
+          options: {
+            name: 'review-apps/$CI_BUILD_REF_NAME',
+            url: 'http://$CI_BUILD_REF_NAME.review-apps.gitlab.com'
+          },
+          variables: [
+            { key: 'CI_BUILD_REF_NAME', value: 'feature-review-apps' }
+          ]
+        }
+      end
+
+      it 'does create a new environment' do
+        expect { subject }.to change { Environment.count }.by(1)
+
+        expect(subject.environment.name).to eq('review-apps/feature-review-apps')
+        expect(subject.environment.external_url).to eq('http://feature-review-apps.review-apps.gitlab.com')
+      end
+
+      it 'does create a new deployment' do
+        expect(subject).to be_persisted
+      end
+    end
   end
-  
+
   describe 'processing of builds' do
     let(:environment) { nil }
     
@@ -95,6 +123,12 @@ describe CreateDeploymentService, services: true do
 
         expect(Deployment.last.deployable).to eq(deployable)
       end
+
+      it 'create environment has URL set' do
+        subject
+
+        expect(Deployment.last.environment.external_url).not_to be_nil
+      end
     end
 
     context 'without environment specified' do
@@ -107,7 +141,10 @@ describe CreateDeploymentService, services: true do
     
     context 'when environment is specified' do
       let(:pipeline) { create(:ci_pipeline, project: project) }
-      let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production') }
+      let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production', options: options) }
+      let(:options) do
+        { environment: { name: 'production', url: 'http://gitlab.com' } }
+      end
 
       context 'when build succeeds' do
         it_behaves_like 'does create environment and deployment' do
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 125110e0c3750ad65728e95a51e1c59445141ecf..f2ef9f3dd81e131f4c049e3a7e663c3a70914d40 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -253,6 +253,21 @@ describe GitPushService, services: true do
         expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
       end
 
+      it "when pushing a branch for the first time with an existing branch permission configured" do
+        stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+        create(:protected_branch, :no_one_can_push, :developers_can_merge, project: project, name: 'master')
+        expect(project).to receive(:execute_hooks)
+        expect(project.default_branch).to eq("master")
+        expect_any_instance_of(ProtectedBranches::CreateService).not_to receive(:execute)
+
+        execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+
+        expect(project.protected_branches).not_to be_empty
+        expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::NO_ACCESS])
+        expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
+      end
+
       it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do
         stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
 
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
similarity index 97%
rename from spec/services/issues/bulk_update_service_spec.rb
rename to spec/services/issuable/bulk_update_service_spec.rb
index ac08aa53b0ba04129b07fc57d6d4fc0729a5317a..6f7ce8ca992018a3826359dd6ea5d04b7082e4e5 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -1,14 +1,14 @@
 require 'spec_helper'
 
-describe Issues::BulkUpdateService, services: true do
+describe Issuable::BulkUpdateService, services: true do
   let(:user)    { create(:user) }
   let(:project) { create(:empty_project, namespace: user.namespace) }
 
   def bulk_update(issues, extra_params = {})
     bulk_update_params = extra_params
-      .reverse_merge(issues_ids: Array(issues).map(&:id).join(','))
+      .reverse_merge(issuable_ids: Array(issues).map(&:id).join(','))
 
-    Issues::BulkUpdateService.new(project, user, bulk_update_params).execute
+    Issuable::BulkUpdateService.new(project, user, bulk_update_params).execute('issue')
   end
 
   describe 'close issues' do
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index ad0d58672b3df13fd90e703932cfbe30eeb469d9..cf90b33dfb43b4d2c9c4545574839e26e824c036 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -4,12 +4,15 @@ describe Projects::HousekeepingService do
   subject { Projects::HousekeepingService.new(project) }
   let(:project) { create :project }
 
-  describe 'execute' do
-    before do
-      project.pushes_since_gc = 3
-      project.save!
-    end
+  before do
+    project.reset_pushes_since_gc
+  end
+
+  after do
+    project.reset_pushes_since_gc
+  end
 
+  describe '#execute' do
     it 'enqueues a sidekiq job' do
       expect(subject).to receive(:try_obtain_lease).and_return(true)
       expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id)
@@ -32,12 +35,12 @@ describe Projects::HousekeepingService do
       it 'does not reset pushes_since_gc' do
         expect do
           expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken)
-        end.not_to change { project.pushes_since_gc }.from(3)
+        end.not_to change { project.pushes_since_gc }
       end
     end
   end
 
-  describe 'needed?' do
+  describe '#needed?' do
     it 'when the count is low enough' do
       expect(subject.needed?).to eq(false)
     end
@@ -48,25 +51,11 @@ describe Projects::HousekeepingService do
     end
   end
 
-  describe 'increment!' do
-    let(:lease_key) { "project_housekeeping:increment!:#{project.id}" }
-
+  describe '#increment!' do
     it 'increments the pushes_since_gc counter' do
-      lease = double(:lease, try_obtain: true)
-      expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease)
-
       expect do
         subject.increment!
       end.to change { project.pushes_since_gc }.from(0).to(1)
     end
-
-    it 'does not increment when no lease can be obtained' do
-      lease = double(:lease, try_obtain: false)
-      expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease)
-
-      expect do
-        subject.increment!
-      end.not_to change { project.pushes_since_gc }
-    end
   end
 end
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7d4eff3b6ef6c14c3f9c5cef4c749cdfa4df3d7c
--- /dev/null
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe ProtectedBranches::CreateService, services: true do
+  let(:project) { create(:empty_project) }
+  let(:user) { project.owner }
+  let(:params) do
+    {
+      name: 'master',
+      merge_access_levels_attributes: [ { access_level: Gitlab::Access::MASTER } ],
+      push_access_levels_attributes: [ { access_level: Gitlab::Access::MASTER } ]
+    }
+  end
+
+  describe '#execute' do
+    subject(:service) { described_class.new(project, user, params) }
+
+    it 'creates a new protected branch' do
+      expect { service.execute }.to change(ProtectedBranch, :count).by(1)
+      expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
+      expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER])
+    end
+  end
+end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index cafcad3e3c04a46aae0d189cd775a2f55c6e9ae2..b41f6f14fbdb34d3c4bce3263a12bfd5be5cb1e1 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -145,6 +145,14 @@ describe TodoService, services: true do
       end
     end
 
+    describe '#destroy_issue' do
+      it 'refresh the todos count cache for the user' do
+        expect(john_doe).to receive(:update_todos_count_cache).and_call_original
+
+        service.destroy_issue(issue, john_doe)
+      end
+    end
+
     describe '#reassigned_issue' do
       it 'creates a pending todo for new assignee' do
         unassigned_issue.update_attribute(:assignee, john_doe)
@@ -394,6 +402,14 @@ describe TodoService, services: true do
       end
     end
 
+    describe '#destroy_merge_request' do
+      it 'refresh the todos count cache for the user' do
+        expect(john_doe).to receive(:update_todos_count_cache).and_call_original
+
+        service.destroy_merge_request(mr_assigned, john_doe)
+      end
+    end
+
     describe '#reassigned_merge_request' do
       it 'creates a pending todo for new assignee' do
         mr_unassigned.update_attribute(:assignee, john_doe)
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index e0dbc9aa84c786f3ce2f3fb12b6d04d9eb72e66e..ac38e31b77e0e707c2fb4895776e1e4536394cb9 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -15,7 +15,7 @@ RSpec.configure do |config|
     DatabaseCleaner.start
   end
 
-  config.after(:each) do
+  config.append_after(:each) do
     DatabaseCleaner.clean
   end
 end
diff --git a/spec/support/issuable_slash_commands_shared_examples.rb b/spec/support/issuable_slash_commands_shared_examples.rb
index d2a49ea5c5edc787d8e9997dd1144a101d816d18..5e3b8f2b23e9c477623b3593d55261a69c128d08 100644
--- a/spec/support/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/issuable_slash_commands_shared_examples.rb
@@ -2,6 +2,9 @@
 # It takes a `issuable_type`, and expect an `issuable`.
 
 shared_examples 'issuable record that supports slash commands in its description and notes' do |issuable_type|
+  include SlashCommandsHelpers
+  include WaitForAjax
+
   let(:master) { create(:user) }
   let(:assignee) { create(:user, username: 'bob') }
   let(:guest) { create(:user) }
@@ -18,6 +21,11 @@ shared_examples 'issuable record that supports slash commands in its description
     login_with(master)
   end
 
+  after do
+    # Ensure all outstanding Ajax requests are complete to avoid database deadlocks
+    wait_for_ajax
+  end
+
   describe "new #{issuable_type}" do
     context 'with commands in the description' do
       it "creates the #{issuable_type} and interpret commands accordingly" do
@@ -44,10 +52,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
     context 'with a note containing commands' do
       it 'creates a note without the commands and interpret the commands accordingly' do
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\""
-          click_button 'Comment'
-        end
+        write_note("Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
 
         expect(page).to have_content 'Awesome!'
         expect(page).not_to have_content '/assign @bob'
@@ -66,10 +71,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
     context 'with a note containing only commands' do
       it 'does not create a note but interpret the commands accordingly' do
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/assign @bob\n/label ~bug\n/milestone %\"ASAP\""
-          click_button 'Comment'
-        end
+        write_note("/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
 
         expect(page).not_to have_content '/assign @bob'
         expect(page).not_to have_content '/label ~bug'
@@ -92,10 +94,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
       context "when current user can close #{issuable_type}" do
         it "closes the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/close"
-            click_button 'Comment'
-          end
+          write_note("/close")
 
           expect(page).not_to have_content '/close'
           expect(page).to have_content 'Your commands have been executed!'
@@ -112,10 +111,7 @@ shared_examples 'issuable record that supports slash commands in its description
         end
 
         it "does not close the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/close"
-            click_button 'Comment'
-          end
+          write_note("/close")
 
           expect(page).not_to have_content '/close'
           expect(page).not_to have_content 'Your commands have been executed!'
@@ -133,10 +129,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
       context "when current user can reopen #{issuable_type}" do
         it "reopens the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/reopen"
-            click_button 'Comment'
-          end
+          write_note("/reopen")
 
           expect(page).not_to have_content '/reopen'
           expect(page).to have_content 'Your commands have been executed!'
@@ -153,10 +146,7 @@ shared_examples 'issuable record that supports slash commands in its description
         end
 
         it "does not reopen the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/reopen"
-            click_button 'Comment'
-          end
+          write_note("/reopen")
 
           expect(page).not_to have_content '/reopen'
           expect(page).not_to have_content 'Your commands have been executed!'
@@ -169,10 +159,7 @@ shared_examples 'issuable record that supports slash commands in its description
     context "with a note changing the #{issuable_type}'s title" do
       context "when current user can change title of #{issuable_type}" do
         it "reopens the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/title Awesome new title"
-            click_button 'Comment'
-          end
+          write_note("/title Awesome new title")
 
           expect(page).not_to have_content '/title'
           expect(page).to have_content 'Your commands have been executed!'
@@ -189,10 +176,7 @@ shared_examples 'issuable record that supports slash commands in its description
         end
 
         it "does not reopen the #{issuable_type}" do
-          page.within('.js-main-target-form') do
-            fill_in 'note[note]', with: "/title Awesome new title"
-            click_button 'Comment'
-          end
+          write_note("/title Awesome new title")
 
           expect(page).not_to have_content '/title'
           expect(page).not_to have_content 'Your commands have been executed!'
@@ -204,10 +188,7 @@ shared_examples 'issuable record that supports slash commands in its description
 
     context "with a note marking the #{issuable_type} as todo" do
       it "creates a new todo for the #{issuable_type}" do
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/todo"
-          click_button 'Comment'
-        end
+        write_note("/todo")
 
         expect(page).not_to have_content '/todo'
         expect(page).to have_content 'Your commands have been executed!'
@@ -238,10 +219,7 @@ shared_examples 'issuable record that supports slash commands in its description
         expect(todo.author).to eq master
         expect(todo.user).to eq master
 
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/done"
-          click_button 'Comment'
-        end
+        write_note("/done")
 
         expect(page).not_to have_content '/done'
         expect(page).to have_content 'Your commands have been executed!'
@@ -254,10 +232,7 @@ shared_examples 'issuable record that supports slash commands in its description
       it "creates a new todo for the #{issuable_type}" do
         expect(issuable.subscribed?(master)).to be_falsy
 
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/subscribe"
-          click_button 'Comment'
-        end
+        write_note("/subscribe")
 
         expect(page).not_to have_content '/subscribe'
         expect(page).to have_content 'Your commands have been executed!'
@@ -274,10 +249,7 @@ shared_examples 'issuable record that supports slash commands in its description
       it "creates a new todo for the #{issuable_type}" do
         expect(issuable.subscribed?(master)).to be_truthy
 
-        page.within('.js-main-target-form') do
-          fill_in 'note[note]', with: "/unsubscribe"
-          click_button 'Comment'
-        end
+        write_note("/unsubscribe")
 
         expect(page).not_to have_content '/unsubscribe'
         expect(page).to have_content 'Your commands have been executed!'
diff --git a/spec/support/ldap_helpers.rb b/spec/support/ldap_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..079f244475cf721c37726ee74af57738c651e122
--- /dev/null
+++ b/spec/support/ldap_helpers.rb
@@ -0,0 +1,47 @@
+module LdapHelpers
+  def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap))
+    ::Gitlab::LDAP::Adapter.new(provider, ldap)
+  end
+
+  def user_dn(uid)
+    "uid=#{uid},ou=users,dc=example,dc=com"
+  end
+
+  # Accepts a hash of Gitlab::LDAP::Config keys and values.
+  #
+  # Example:
+  #   stub_ldap_config(
+  #     group_base: 'ou=groups,dc=example,dc=com',
+  #     admin_group: 'my-admin-group'
+  #   )
+  def stub_ldap_config(messages)
+    messages.each do |config, value|
+      allow_any_instance_of(::Gitlab::LDAP::Config)
+        .to receive(config.to_sym).and_return(value)
+    end
+  end
+
+  # Stub an LDAP person search and provide the return entry. Specify `nil` for
+  # `entry` to simulate when an LDAP person is not found
+  #
+  # Example:
+  #  adapter = ::Gitlab::LDAP::Adapter.new('ldapmain', double(:ldap))
+  #  ldap_user_entry = ldap_user_entry('john_doe')
+  #
+  #  stub_ldap_person_find_by_uid('john_doe', ldap_user_entry, adapter)
+  def stub_ldap_person_find_by_uid(uid, entry, provider = 'ldapmain')
+    return_value = ::Gitlab::LDAP::Person.new(entry, provider) if entry.present?
+
+    allow(::Gitlab::LDAP::Person)
+      .to receive(:find_by_uid).with(uid, any_args).and_return(return_value)
+  end
+
+  # Create a simple LDAP user entry.
+  def ldap_user_entry(uid)
+    entry = Net::LDAP::Entry.new
+    entry['dn'] = user_dn(uid)
+    entry['uid'] = uid
+
+    entry
+  end
+end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index e5f76afbfc0883755c305d24a254355f32408e85..c0b3e83244ddd6fdca29dd7e77d8bba6629828d5 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -75,6 +75,7 @@ module LoginHelpers
   def logout
     find(".header-user-dropdown-toggle").click
     click_link "Sign out"
+    expect(page).to have_content('Signed out successfully')
   end
 
   # Logout without JavaScript driver
diff --git a/spec/support/slash_commands_helpers.rb b/spec/support/slash_commands_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..df483afa0e371cd870ecba7a2b9410d879af3442
--- /dev/null
+++ b/spec/support/slash_commands_helpers.rb
@@ -0,0 +1,10 @@
+module SlashCommandsHelpers
+  def write_note(text)
+    Sidekiq::Testing.fake! do
+      page.within('.js-main-target-form') do
+        fill_in 'note[note]', with: text
+        click_button 'Comment'
+      end
+    end
+  end
+end
diff --git a/spec/support/wait_for_vue_resource.rb b/spec/support/wait_for_vue_resource.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1029f84716fd99503eb95949ce7996292f2fced1
--- /dev/null
+++ b/spec/support/wait_for_vue_resource.rb
@@ -0,0 +1,7 @@
+module WaitForVueResource
+  def wait_for_vue_resource(spinner: true)
+    Timeout.timeout(Capybara.default_max_wait_time) do
+      loop until page.evaluate_script('Vue.activeResources').zero?
+    end
+  end
+end
diff --git a/spec/support/workhorse_helpers.rb b/spec/support/workhorse_helpers.rb
index 107b6e309240d8f1b7165d445fd9e3c45cd70582..47673cd4c3afe1d0311f9a7c2b039c5ffcf07ab2 100644
--- a/spec/support/workhorse_helpers.rb
+++ b/spec/support/workhorse_helpers.rb
@@ -13,4 +13,9 @@ module WorkhorseHelpers
       ]
     end
   end
+
+  def workhorse_internal_api_request_header
+    jwt_token = JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256')
+    { 'HTTP_' + Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER.upcase.tr('-', '_') => jwt_token }
+  end
 end
diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac7f3ffb1579ac18ec37c2236aea84cd6e7e80da
--- /dev/null
+++ b/spec/views/projects/pipelines/show.html.haml_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe 'projects/pipelines/show' do
+  include Devise::TestHelpers
+
+  let(:project) { create(:project) }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
+
+  before do
+    controller.prepend_view_path('app/views/projects')
+
+    create_build('build', 0, 'build', :success)
+    create_build('test', 1, 'rspec 0:2', :pending)
+    create_build('test', 1, 'rspec 1:2', :running)
+    create_build('test', 1, 'spinach 0:2', :created)
+    create_build('test', 1, 'spinach 1:2', :created)
+    create_build('test', 1, 'audit', :created)
+    create_build('deploy', 2, 'production', :created)
+
+    create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3)
+
+    assign(:project, project)
+    assign(:pipeline, pipeline)
+
+    allow(view).to receive(:can?).and_return(true)
+  end
+
+  it 'shows a graph with grouped stages' do
+    render
+
+    expect(rendered).to have_css('.pipeline-graph')
+    expect(rendered).to have_css('.grouped-pipeline-dropdown')
+
+    # stages
+    expect(rendered).to have_text('Build')
+    expect(rendered).to have_text('Test')
+    expect(rendered).to have_text('Deploy')
+    expect(rendered).to have_text('External')
+
+    # builds
+    expect(rendered).to have_text('rspec')
+    expect(rendered).to have_text('spinach')
+    expect(rendered).to have_text('rspec 0:2')
+    expect(rendered).to have_text('production')
+    expect(rendered).to have_text('jenkins')
+  end
+
+  private
+
+  def create_build(stage, stage_idx, name, status)
+    create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name, status: status)
+  end
+end
diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..35e1518a35e6c7fbb5f99c90659d34f45001cad5
--- /dev/null
+++ b/spec/workers/prune_old_events_worker_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe PruneOldEventsWorker do
+  describe '#perform' do
+    let!(:expired_event) { create(:event, author_id: 0, created_at: 13.months.ago) }
+    let!(:not_expired_event) { create(:event, author_id: 0,  created_at: 1.day.ago) }
+    let!(:exactly_12_months_event) { create(:event, author_id: 0, created_at: 12.months.ago) }
+
+    it 'prunes events older than 12 months' do
+      expect { subject.perform }.to change { Event.count }.by(-1)
+      expect(Event.find_by(id: expired_event.id)).to be_nil
+    end
+
+    it 'leaves fresh events' do
+      subject.perform
+      expect(not_expired_event.reload).to be_present
+    end
+
+    it 'leaves events from exactly 12 months ago' do
+      subject.perform
+      expect(exactly_12_months_event).to be_present
+    end
+  end
+end
diff --git a/vendor/assets/javascripts/task_list.js b/vendor/assets/javascripts/task_list.js
index bc451506b6a71eab728b3480efdcac00ec2d9334..9fbfef03f6d61ac874c4aa8ad286e55f6d1fa0eb 100644
--- a/vendor/assets/javascripts/task_list.js
+++ b/vendor/assets/javascripts/task_list.js
@@ -1,15 +1,118 @@
-
+// The MIT License (MIT)
+//
+// Copyright (c) 2014 GitHub, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+// TaskList Behavior
+//
 /*= provides tasklist:enabled */
-
-
 /*= provides tasklist:disabled */
-
-
 /*= provides tasklist:change */
-
-
 /*= provides tasklist:changed */
-
+//
+//
+// Enables Task List update behavior.
+//
+// ### Example Markup
+//
+//   <div class="js-task-list-container">
+//     <ul class="task-list">
+//       <li class="task-list-item">
+//         <input type="checkbox" class="js-task-list-item-checkbox" disabled />
+//         text
+//       </li>
+//     </ul>
+//     <form>
+//       <textarea class="js-task-list-field">- [ ] text</textarea>
+//     </form>
+//   </div>
+//
+// ### Specification
+//
+// TaskLists MUST be contained in a `(div).js-task-list-container`.
+//
+// TaskList Items SHOULD be an a list (`UL`/`OL`) element.
+//
+// Task list items MUST match `(input).task-list-item-checkbox` and MUST be
+// `disabled` by default.
+//
+// TaskLists MUST have a `(textarea).js-task-list-field` form element whose
+// `value` attribute is the source (Markdown) to be udpated. The source MUST
+// follow the syntax guidelines.
+//
+// TaskList updates trigger `tasklist:change` events. If the change is
+// successful, `tasklist:changed` is fired. The change can be canceled.
+//
+// jQuery is required.
+//
+// ### Methods
+//
+// `.taskList('enable')` or `.taskList()`
+//
+// Enables TaskList updates for the container.
+//
+// `.taskList('disable')`
+//
+// Disables TaskList updates for the container.
+//
+//# ### Events
+//
+// `tasklist:enabled`
+//
+// Fired when the TaskList is enabled.
+//
+// * **Synchronicity** Sync
+// * **Bubbles** Yes
+// * **Cancelable** No
+// * **Target** `.js-task-list-container`
+//
+// `tasklist:disabled`
+//
+// Fired when the TaskList is disabled.
+//
+// * **Synchronicity** Sync
+// * **Bubbles** Yes
+// * **Cancelable** No
+// * **Target** `.js-task-list-container`
+//
+// `tasklist:change`
+//
+// Fired before the TaskList item change takes affect.
+//
+// * **Synchronicity** Sync
+// * **Bubbles** Yes
+// * **Cancelable** Yes
+// * **Target** `.js-task-list-field`
+//
+// `tasklist:changed`
+//
+// Fired once the TaskList item change has taken affect.
+//
+// * **Synchronicity** Sync
+// * **Bubbles** Yes
+// * **Cancelable** No
+// * **Target** `.js-task-list-field`
+//
+// ### NOTE
+//
+// Task list checkboxes are rendered as disabled by default because rendered
+// user content is cached without regard for the viewer.
 (function() {
   var codeFencesPattern, complete, completePattern, disableTaskList, disableTaskLists, enableTaskList, enableTaskLists, escapePattern, incomplete, incompletePattern, itemPattern, itemsInParasPattern, updateTaskList, updateTaskListItem,
     indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
@@ -18,20 +121,48 @@
 
   complete = "[x]";
 
+  // Escapes the String for regular expression matching.
   escapePattern = function(str) {
     return str.replace(/([\[\]])/g, "\\$1").replace(/\s/, "\\s").replace("x", "[xX]");
   };
 
-  incompletePattern = RegExp("" + (escapePattern(incomplete)));
-
-  completePattern = RegExp("" + (escapePattern(complete)));
+  incompletePattern = RegExp("" + (escapePattern(incomplete))); // escape square brackets
+ // match all white space
+  completePattern = RegExp("" + (escapePattern(complete))); // match all cases
 
+  // Pattern used to identify all task list items.
+  // Useful when you need iterate over all items.
   itemPattern = RegExp("^(?:\\s*(?:>\\s*)*(?:[-+*]|(?:\\d+\\.)))\\s*(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ")\\s+(?!\\(.*?\\))(?=(?:\\[.*?\\]\\s*(?:\\[.*?\\]|\\(.*?\\))\\s*)*(?:[^\\[]|$))");
 
+  // prefix, consisting of
+  // optional leading whitespace
+  // zero or more blockquotes
+  // list item indicator
+  // optional whitespace prefix
+  // checkbox
+  // is followed by whitespace
+  // is not part of a [foo](url) link
+  // and is followed by zero or more links
+  // and either a non-link or the end of the string
+  // Used to filter out code fences from the source for comparison only.
+  // http://rubular.com/r/x5EwZVrloI
+  // Modified slightly due to issues with JS
   codeFencesPattern = /^`{3}(?:\s*\w+)?[\S\s].*[\S\s]^`{3}$/mg;
 
+  // ```
+  // followed by optional language
+  // whitespace
+  // code
+  // whitespace
+  // ```
+  // Used to filter out potential mismatches (items not in lists).
+  // http://rubular.com/r/OInl6CiePy
   itemsInParasPattern = RegExp("^(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ").+$", "g");
 
+  // Given the source text, updates the appropriate task list item to match the
+  // given checked value.
+  //
+  // Returns the updated String text.
   updateTaskListItem = function(source, itemIndex, checked) {
     var clean, index, line, result;
     clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').replace(itemsInParasPattern, '').split("\n");
@@ -55,6 +186,9 @@
     return result.join("\n");
   };
 
+  // Updates the $field value to reflect the state of $item.
+  // Triggers the `tasklist:change` event before the value has changed, and fires
+  // a `tasklist:changed` event once the value has changed.
   updateTaskList = function($item) {
     var $container, $field, checked, event, index;
     $container = $item.closest('.js-task-list-container');
@@ -70,10 +204,12 @@
     }
   };
 
+  // When the task list item checkbox is updated, submit the change
   $(document).on('change', '.task-list-item-checkbox', function() {
     return updateTaskList($(this));
   });
 
+  // Enables TaskList item changes.
   enableTaskList = function($container) {
     if ($container.find('.js-task-list-field').length > 0) {
       $container.find('.task-list-item').addClass('enabled').find('.task-list-item-checkbox').attr('disabled', null);
@@ -81,6 +217,7 @@
     }
   };
 
+  // Enables a collection of TaskList containers.
   enableTaskLists = function($containers) {
     var container, i, len, results;
     results = [];
@@ -91,11 +228,13 @@
     return results;
   };
 
+  // Disable TaskList item changes.
   disableTaskList = function($container) {
     $container.find('.task-list-item').removeClass('enabled').find('.task-list-item-checkbox').attr('disabled', 'disabled');
     return $container.removeClass('is-task-list-enabled').trigger('tasklist:disabled');
   };
 
+  // Disables a collection of TaskList containers.
   disableTaskLists = function($containers) {
     var container, i, len, results;
     results = [];
diff --git a/vendor/gitignore/Global/NetBeans.gitignore b/vendor/gitignore/Global/NetBeans.gitignore
index 520d91ff584bda8a9385fb02acbbf03e0d35fed3..254108cd23b0df55029e67f56c812f8484ae0e44 100644
--- a/vendor/gitignore/Global/NetBeans.gitignore
+++ b/vendor/gitignore/Global/NetBeans.gitignore
@@ -3,5 +3,4 @@ build/
 nbbuild/
 dist/
 nbdist/
-nbactions.xml
 .nb-gradle/
diff --git a/vendor/gitignore/Global/Tags.gitignore b/vendor/gitignore/Global/Tags.gitignore
index c0318165a2790cedcc8a27765b3e8bc2f586006c..91927af4cd6514b62b66d58a5108b05da96dfcff 100644
--- a/vendor/gitignore/Global/Tags.gitignore
+++ b/vendor/gitignore/Global/Tags.gitignore
@@ -9,6 +9,7 @@ gtags.files
 GTAGS
 GRTAGS
 GPATH
+GSYMS
 cscope.files
 cscope.out
 cscope.in.out
diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/macOS.gitignore
similarity index 98%
rename from vendor/gitignore/Global/OSX.gitignore
rename to vendor/gitignore/Global/macOS.gitignore
index 5972fe50f66e4c7b4b5d87afde97758eeeb7c64f..828a509a137632e51c973018cc379fa1c4aa5be5 100644
--- a/vendor/gitignore/Global/OSX.gitignore
+++ b/vendor/gitignore/Global/macOS.gitignore
@@ -3,7 +3,8 @@
 .LSOverride
 
 # Icon must end with two \r
-Icon

+Icon
+
 
 # Thumbnails
 ._*
diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore
index a4ee41ab62b01d5467e6b817aa1107606ba478d4..450f32ec40cc967988bf7e36f1996ce98e74c0cc 100644
--- a/vendor/gitignore/Haskell.gitignore
+++ b/vendor/gitignore/Haskell.gitignore
@@ -17,3 +17,4 @@ cabal.sandbox.config
 *.eventlog
 .stack-work/
 cabal.project.local
+.HTF/
diff --git a/vendor/gitignore/Joomla.gitignore b/vendor/gitignore/Joomla.gitignore
index 0d7a0de298faec7abe9c2592558364fbdda7bde7..93103fdbe772bb02b704a41ab13fe4563685f71e 100644
--- a/vendor/gitignore/Joomla.gitignore
+++ b/vendor/gitignore/Joomla.gitignore
@@ -52,6 +52,7 @@
 /administrator/language/en-GB/en-GB.plg_content_contact.sys.ini
 /administrator/language/en-GB/en-GB.plg_content_finder.ini
 /administrator/language/en-GB/en-GB.plg_content_finder.sys.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_module*
 /administrator/language/en-GB/en-GB.plg_finder_categories.ini
 /administrator/language/en-GB/en-GB.plg_finder_categories.sys.ini
 /administrator/language/en-GB/en-GB.plg_finder_contacts.ini
@@ -64,6 +65,10 @@
 /administrator/language/en-GB/en-GB.plg_finder_tags.sys.ini
 /administrator/language/en-GB/en-GB.plg_finder_weblinks.ini
 /administrator/language/en-GB/en-GB.plg_finder_weblinks.sys.ini
+/administrator/language/en-GB/en-GB.plg_installer_folderinstaller*
+/administrator/language/en-GB/en-GB.plg_installer_packageinstaller*
+/administrator/language/en-GB/en-GB.plg_installer_packageinstaller
+/administrator/language/en-GB/en-GB.plg_installer_urlinstaller*
 /administrator/language/en-GB/en-GB.plg_installer_webinstaller.ini
 /administrator/language/en-GB/en-GB.plg_installer_webinstaller.sys.ini
 /administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.ini
@@ -72,6 +77,8 @@
 /administrator/language/en-GB/en-GB.plg_search_tags.sys.ini
 /administrator/language/en-GB/en-GB.plg_system_languagecode.ini
 /administrator/language/en-GB/en-GB.plg_system_languagecode.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_stats*
+/administrator/language/en-GB/en-GB.plg_system_updatenotification*
 /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.ini
 /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.sys.ini
 /administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.ini
@@ -249,8 +256,10 @@
 /administrator/language/en-GB/en-GB.tpl_hathor.sys.ini
 /administrator/language/en-GB/en-GB.xml
 /administrator/language/en-GB/index.html
+/administrator/language/ru-RU/index.html
 /administrator/language/overrides/*
 /administrator/language/index.html
+/administrator/logs/index.html
 /administrator/manifests/*
 /administrator/modules/mod_custom/*
 /administrator/modules/mod_feed/*
@@ -289,6 +298,7 @@
 /components/com_finder/*
 /components/com_mailto/*
 /components/com_media/*
+/components/com_modules/*
 /components/com_newsfeeds/*
 /components/com_search/*
 /components/com_users/*
@@ -407,6 +417,7 @@
 /libraries/idna_convert/*
 /libraries/joomla/*
 /libraries/legacy/*
+/libraries/php-encryption/*
 /libraries/phpass/*
 /libraries/phpmailer/*
 /libraries/phputf8/*
@@ -431,9 +442,11 @@
 /media/media/*
 /media/mod_languages/*
 /media/overrider/*
+/media/plg_captcha_recaptcha/*
 /media/plg_quickicon_extensionupdate/*
 /media/plg_quickicon_joomlaupdate/*
 /media/plg_system_highlight/*
+/media/plg_system_stats/*
 /media/system/*
 /media/index.html
 /modules/mod_articles_archive/*
@@ -486,6 +499,7 @@
 /plugins/editors/none/*
 /plugins/editors/tinymce/*
 /plugins/editors/index.html
+/plugins/editors-xtd/module/*
 /plugins/editors-xtd/article/*
 /plugins/editors-xtd/image/*
 /plugins/editors-xtd/pagebreak/*
@@ -523,6 +537,8 @@
 /plugins/system/redirect/*
 /plugins/system/remember/*
 /plugins/system/sef/*
+/plugins/system/stats/*
+/plugins/system/updatenotification/*
 /plugins/system/index.html
 /plugins/twofactorauth/*
 /plugins/user/contactcreator/*
diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore
index aea5294de9dca6cd0f3edbaab53aab540c7020ca..bf7525f991213f3fa92266d13c849a5d2717bc94 100644
--- a/vendor/gitignore/Node.gitignore
+++ b/vendor/gitignore/Node.gitignore
@@ -34,5 +34,8 @@ jspm_packages
 # Optional npm cache directory
 .npm
 
+# Optional eslint cache
+.eslintcache
+
 # Optional REPL history
 .node_repl_history
diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore
index 20592083931a5924e8891b6e9916bd3b51b062bd..58c51ecaed4c4d08067f2072b04482ed2cda8819 100644
--- a/vendor/gitignore/Objective-C.gitignore
+++ b/vendor/gitignore/Objective-C.gitignore
@@ -50,7 +50,9 @@ Carthage/Build
 # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
 
 fastlane/report.xml
+fastlane/Preview.html
 fastlane/screenshots
+fastlane/test_output
 
 # Code Injection
 #
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index 72364f99fe4bf8d5262df3b19b33102aeaa791e5..37fc9d40817580a7922e9f71772922e58465b2b1 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -79,6 +79,7 @@ celerybeat-schedule
 .env
 
 # virtualenv
+.venv/
 venv/
 ENV/
 
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index d8c256c1925e11e94c5b0a56919ec844517a7329..e97427608c1a0e49deca2c77b170dc433ebd6c8b 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -12,9 +12,11 @@ capybara-*.html
 rerun.txt
 pickle-email-*.html
 
-# TODO Comment out these rules if you are OK with secrets being uploaded to the repo
+# TODO Comment out this rule if you are OK with secrets being uploaded to the repo
 config/initializers/secret_token.rb
-config/secrets.yml
+
+# Only include if you have production secrets in this file, which is no longer a Rails default
+# config/secrets.yml
 
 # dotenv
 # TODO Comment out this rule if environment variables can be committed
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index 67acbf42f5ee14c6ed7089ef2aa6559f57c860cd..d56f8b532889e73e92aafe28695b83e82adb9f60 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -251,3 +251,10 @@ paket-files/
 # JetBrains Rider
 .idea/
 *.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
index 396d3f1b042f1c15ea4663c9c43d08f85d0ff9ff..f3fa3949656176b4332b6a5b31fb4c17cb693788 100644
--- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
@@ -1,7 +1,12 @@
 # Official docker image.
 image: docker:latest
 
+services:
+  - docker:dind
+
 build:
   stage: build
   script:
-    - docker build -t test .
+    - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" $CI_REGISTRY
+    - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME" .
+    - docker push "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME"
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
index 166f146ee05e1a2c33ade906499ac5dd2de8b0be..08b57c8c0acf4f91c914011a6a477afeffe9f180 100644
--- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -43,3 +43,12 @@ rails:
   - bundle exec rake db:migrate
   - bundle exec rake db:seed
   - bundle exec rake test
+
+# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk
+# are supported too: https://github.com/travis-ci/dpl
+deploy:
+  type: deploy
+  environment: production
+  script:
+  - gem install dpl
+  - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY
diff --git a/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c9c35906d1c23bc894a95acb59b0a327f8547c9a
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml
@@ -0,0 +1,30 @@
+# Lifted from: https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/
+# This file assumes an own GitLab CI runner, setup on an OS X system.
+stages:
+  - build
+  - archive
+
+build_project:
+  stage: build
+  script:
+    - xcodebuild clean -project ProjectName.xcodeproj -scheme SchemeName | xcpretty
+    - xcodebuild test -project ProjectName.xcodeproj -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.2' | xcpretty -s
+  tags:
+    - ios_9-2
+    - xcode_7-2
+    - osx_10-11
+
+archive_project:
+  stage: archive
+  script:
+    - xcodebuild clean archive -archivePath build/ProjectName -scheme SchemeName
+    - xcodebuild -exportArchive -exportFormat ipa -archivePath "build/ProjectName.xcarchive" -exportPath "build/ProjectName.ipa" -exportProvisioningProfile "ProvisioningProfileName"
+  only:
+    - master
+  artifacts:
+    paths:
+    - build/ProjectName.ipa
+  tags:
+    - ios_9-2
+    - xcode_7-2
+    - osx_10-11