summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlfredo Sumaran <alfredo@gitlab.com>2016-04-11 15:24:59 -0500
committerAlfredo Sumaran <alfredo@gitlab.com>2016-04-11 15:24:59 -0500
commitfe276d0d755c9047d8a1dea7c6c8af9601f4d5e7 (patch)
treefd8884e8b7a9950a2b9467ca50c10dc4210db445
parent45c93b52e497c6c3fc10272623fc7f38f11e9079 (diff)
parent7998725e3c2557c04ceece028dd73a316a3a746c (diff)
downloadgitlab-ce-fe276d0d755c9047d8a1dea7c6c8af9601f4d5e7.tar.gz
Merge branch 'master' into issue_14678
# Conflicts: # app/assets/javascripts/todos.js.coffee
-rw-r--r--.rubocop.yml2
-rw-r--r--CHANGELOG25
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile11
-rw-r--r--Gemfile.lock90
-rw-r--r--app/assets/javascripts/awards_handler.coffee15
-rw-r--r--app/assets/javascripts/compare.js.coffee67
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee41
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee3
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee8
-rw-r--r--app/assets/javascripts/sidebar.js.coffee1
-rw-r--r--app/assets/javascripts/subscription.js.coffee4
-rw-r--r--app/assets/javascripts/todos.js.coffee9
-rw-r--r--app/assets/stylesheets/framework/buttons.scss29
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss4
-rw-r--r--app/assets/stylesheets/framework/files.scss5
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss6
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss123
-rw-r--r--app/assets/stylesheets/framework/timeline.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss28
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss4
-rw-r--r--app/assets/stylesheets/highlight/white.scss24
-rw-r--r--app/assets/stylesheets/pages/commit.scss2
-rw-r--r--app/assets/stylesheets/pages/commits.scss5
-rw-r--r--app/assets/stylesheets/pages/diff.scss17
-rw-r--r--app/assets/stylesheets/pages/help.scss3
-rw-r--r--app/assets/stylesheets/pages/labels.scss58
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss79
-rw-r--r--app/assets/stylesheets/pages/note_form.scss11
-rw-r--r--app/assets/stylesheets/pages/notes.scss29
-rw-r--r--app/controllers/application_controller.rb10
-rw-r--r--app/controllers/projects/merge_requests_controller.rb6
-rw-r--r--app/helpers/blob_helper.rb6
-rw-r--r--app/helpers/commits_helper.rb6
-rw-r--r--app/helpers/form_helper.rb18
-rw-r--r--app/helpers/gitlab_markdown_helper.rb23
-rw-r--r--app/helpers/issues_helper.rb33
-rw-r--r--app/helpers/notes_helper.rb7
-rw-r--r--app/views/abuse_reports/new.html.haml6
-rw-r--r--app/views/admin/appearances/_form.html.haml5
-rw-r--r--app/views/admin/application_settings/_form.html.haml6
-rw-r--r--app/views/admin/applications/_form.html.haml7
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml6
-rw-r--r--app/views/admin/deploy_keys/new.html.haml6
-rw-r--r--app/views/admin/groups/_form.html.haml5
-rw-r--r--app/views/admin/hooks/index.html.haml6
-rw-r--r--app/views/admin/identities/_form.html.haml6
-rw-r--r--app/views/admin/labels/_form.html.haml8
-rw-r--r--app/views/admin/users/_form.html.haml6
-rw-r--r--app/views/doorkeeper/applications/_form.html.haml6
-rw-r--r--app/views/groups/edit.html.haml4
-rw-r--r--app/views/groups/new.html.haml5
-rw-r--r--app/views/layouts/_collapse_button.html.haml4
-rw-r--r--app/views/layouts/_page.html.haml18
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml5
-rw-r--r--app/views/layouts/nav/_group.html.haml10
-rw-r--r--app/views/layouts/nav/_profile.html.haml8
-rw-r--r--app/views/layouts/nav/_project.html.haml17
-rw-r--r--app/views/profiles/keys/_form.html.haml6
-rw-r--r--app/views/profiles/notifications/show.html.haml6
-rw-r--r--app/views/profiles/passwords/edit.html.haml7
-rw-r--r--app/views/profiles/passwords/new.html.haml7
-rw-r--r--app/views/profiles/show.html.haml7
-rw-r--r--app/views/projects/_errors.html.haml5
-rw-r--r--app/views/projects/_md_preview.html.haml6
-rw-r--r--app/views/projects/commits/_commit.html.haml9
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml6
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml12
-rw-r--r--app/views/projects/hooks/index.html.haml6
-rw-r--r--app/views/projects/labels/_form.html.haml8
-rw-r--r--app/views/projects/labels/_label.html.haml17
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml116
-rw-r--r--app/views/projects/merge_requests/branch_from.html.haml1
-rw-r--r--app/views/projects/merge_requests/branch_from.js.haml3
-rw-r--r--app/views/projects/merge_requests/branch_to.html.haml1
-rw-r--r--app/views/projects/merge_requests/branch_to.js.haml3
-rw-r--r--app/views/projects/merge_requests/update_branches.html.haml5
-rw-r--r--app/views/projects/merge_requests/update_branches.js.haml9
-rw-r--r--app/views/projects/milestones/_form.html.haml7
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply.html.haml3
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml6
-rw-r--r--app/views/projects/notes/_note.html.haml4
-rw-r--r--app/views/projects/protected_branches/index.html.haml6
-rw-r--r--app/views/projects/variables/show.html.haml8
-rw-r--r--app/views/projects/wikis/_form.html.haml6
-rw-r--r--app/views/shared/_label_row.html.haml3
-rw-r--r--app/views/shared/_service_settings.html.haml7
-rw-r--r--app/views/shared/issuable/_form.html.haml23
-rw-r--r--app/views/shared/snippets/_form.html.haml6
-rw-r--r--app/views/users/calendar.html.haml2
-rw-r--r--app/views/votes/_votes_block.html.haml4
-rw-r--r--config/application.rb8
-rw-r--r--config/environments/production.rb3
-rw-r--r--config/environments/test.rb1
-rw-r--r--config/initializers/metrics.rb24
-rw-r--r--config/initializers/session_store.rb2
-rw-r--r--config/initializers/sidekiq.rb4
-rw-r--r--config/mail_room.yml4
-rw-r--r--db/fixtures/development/07_milestones.rb2
-rw-r--r--db/migrate/20130315124931_user_color_scheme.rb4
-rw-r--r--db/migrate/20130403003950_add_last_activity_column_into_project.rb16
-rw-r--r--db/migrate/20131112220935_add_visibility_level_to_projects.rb6
-rw-r--r--db/migrate/20140313092127_migrate_already_imported_projects.rb8
-rw-r--r--db/migrate/20141007100818_add_visibility_level_to_snippet.rb14
-rw-r--r--db/schema.rb3
-rw-r--r--doc/api/issues.md17
-rw-r--r--doc/api/merge_requests.md19
-rw-r--r--doc/ci/build_artifacts/README.md13
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/development/README.md3
-rw-r--r--doc/development/code_review.md78
-rw-r--r--doc/development/instrumentation.md37
-rw-r--r--doc/development/testing.md136
-rw-r--r--doc/development/ui_guide.md4
-rw-r--r--doc/integration/README.md17
-rw-r--r--doc/project_services/project_services.md19
-rw-r--r--features/groups.feature4
-rw-r--r--features/project/forked_merge_requests.feature1
-rw-r--r--features/project/merge_requests.feature1
-rw-r--r--features/project/project.feature9
-rw-r--r--features/steps/group/milestones.rb4
-rw-r--r--features/steps/groups.rb4
-rw-r--r--features/steps/project/active_tab.rb4
-rw-r--r--features/steps/project/fork.rb2
-rw-r--r--features/steps/project/forked_merge_requests.rb22
-rw-r--r--features/steps/project/issues/labels.rb2
-rw-r--r--features/steps/project/merge_requests.rb8
-rw-r--r--features/steps/project/project.rb12
-rw-r--r--features/steps/project/source/browse_files.rb7
-rw-r--r--features/steps/shared/diff_note.rb2
-rw-r--r--features/steps/shared/project_tab.rb2
-rw-r--r--lib/api/entities.rb8
-rw-r--r--lib/api/issues.rb10
-rw-r--r--lib/api/merge_requests.rb14
-rw-r--r--lib/api/milestones.rb2
-rw-r--r--lib/banzai/renderer.rb20
-rw-r--r--lib/gitlab/exclusive_lease.rb4
-rw-r--r--lib/gitlab/metrics.rb34
-rw-r--r--lib/gitlab/metrics/metric.rb22
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb39
-rw-r--r--lib/gitlab/metrics/system.rb11
-rw-r--r--lib/gitlab/redis.rb48
-rw-r--r--lib/gitlab/redis_config.rb30
-rw-r--r--lib/tasks/cache.rake25
-rw-r--r--spec/features/dashboard_issues_spec.rb54
-rw-r--r--spec/features/issues/award_emoji_spec.rb64
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb11
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb2
-rw-r--r--spec/helpers/form_helper_spec.rb46
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb7
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb71
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics_spec.rb19
157 files changed, 1628 insertions, 744 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 71273ce6098..2fda0b03119 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -691,7 +691,7 @@ Style/ZeroLengthPredicate:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
- Max: 70
+ Max: 60
# Avoid excessive block nesting.
Metrics/BlockNesting:
diff --git a/CHANGELOG b/CHANGELOG
index 151875249d8..3561c541df0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,8 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
+ - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
+ - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu)
- Improved Markdown rendering performance !3389 (Yorick Peterse)
@@ -12,6 +14,7 @@ v 8.7.0 (unreleased)
- Allow back dating on issues when created through the API
- Fix Error 500 after renaming a project path (Stan Hu)
- Fix avatar stretching by providing a cropping feature
+ - API: Expose `subscribed` for issues and merge requests (Robert Schilling)
- Allow SAML to handle external users based on user's information !3530
- Add endpoints to archive or unarchive a project !3372
- Add links to CI setup documentation from project settings and builds pages
@@ -26,19 +29,24 @@ v 8.7.0 (unreleased)
- Fix creation of merge requests for orphaned branches (Stan Hu)
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- - Improved UX of the navigation sidebar
- Fix admin/projects when using visibility levels on search (PotHix)
- Build status notifications
- API: Expose user location (Robert Schilling)
- ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591
- Update number of Todos in the sidebar when it's marked as "Done". !3600
-v 8.6.5 (unreleased)
- - Only update repository language if it is not set to improve performance
- - Check permissions when user attempts to import members from another project
+v 8.6.5
+ - Fix importing from GitHub Enterprise. !3529
+ - Perform the language detection after updating merge requests in `GitPushService`, leading to faster visual feedback for the end-user. !3533
+ - Check permissions when user attempts to import members from another project. !3535
+ - Only update repository language if it is not set to improve performance. !3556
+ - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583
+ - Unblock user when active_directory is disabled and it can be found !3550
+ - Fix a 2FA authentication spoofing vulnerability.
v 8.6.4
- Don't attempt to fetch any tags from a forked repo (Stan Hu)
+ - Redesign the Labels page
v 8.6.3
- Mentions on confidential issues doesn't create todos for non-members. !3374
@@ -155,6 +163,9 @@ v 8.6.0
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
+v 8.5.10
+ - Fix a 2FA authentication spoofing vulnerability.
+
v 8.5.9
- Don't attempt to fetch any tags from a forked repo (Stan Hu).
@@ -299,6 +310,9 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos
+v 8.4.8
+ - Fix a 2FA authentication spoofing vulnerability.
+
v 8.4.7
- Don't attempt to fetch any tags from a forked repo (Stan Hu).
@@ -418,6 +432,9 @@ v 8.4.0
- Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
+v 8.3.7
+ - Fix a 2FA authentication spoofing vulnerability.
+
v 8.3.6
- Don't attempt to fetch any tags from a forked repo (Stan Hu).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 511336f384c..1f26a5d7eaf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -448,7 +448,7 @@ merge request:
- multi-line method chaining style **Option B**: dot `.` on previous line
- string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide)
-1. [Testing](https://github.com/thoughtbot/guides/tree/master/style/testing)
+1. [Testing](doc/development/testing.md)
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript)
1. [SCSS styleguide][scss-styleguide]
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 24ba9a38de6..37c2961c243 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.7.0
+2.7.2
diff --git a/Gemfile b/Gemfile
index 0279b4ac47e..258b5612cd5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
source "https://rubygems.org"
-gem 'rails', '4.2.5.2'
+gem 'rails', '4.2.6'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
@@ -8,7 +8,7 @@ gem 'responders', '~> 2.0'
# Specify a sprockets version due to increased performance
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
-gem 'sprockets', '~> 3.3.5'
+gem 'sprockets', '~> 3.6.0'
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
@@ -149,6 +149,10 @@ gem 'version_sorter', '~> 2.0.0'
# Cache
gem "redis-rails", '~> 4.0.0'
+# Redis
+gem 'redis', '~> 3.2'
+gem 'connection_pool', '~> 2.0'
+
# Campfire integration
gem 'tinder', '~> 1.10.0'
@@ -229,14 +233,13 @@ group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
gem 'method_source', '~> 0.8', require: false
gem 'influxdb', '~> 0.2', require: false
- gem 'connection_pool', '~> 2.0', require: false
end
group :development do
gem "foreman"
gem 'brakeman', '~> 3.2.0', require: false
- gem "annotate", "~> 2.6.0"
+ gem "annotate", "~> 2.7.0"
gem "letter_opener", '~> 1.1.2'
gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 1ba8d748db1..9da44a46583 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -4,41 +4,41 @@ GEM
CFPropertyList (2.3.2)
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
- actionmailer (4.2.5.2)
- actionpack (= 4.2.5.2)
- actionview (= 4.2.5.2)
- activejob (= 4.2.5.2)
+ actionmailer (4.2.6)
+ actionpack (= 4.2.6)
+ actionview (= 4.2.6)
+ activejob (= 4.2.6)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.5.2)
- actionview (= 4.2.5.2)
- activesupport (= 4.2.5.2)
+ actionpack (4.2.6)
+ actionview (= 4.2.6)
+ activesupport (= 4.2.6)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.5.2)
- activesupport (= 4.2.5.2)
+ actionview (4.2.6)
+ activesupport (= 4.2.6)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (4.2.5.2)
- activesupport (= 4.2.5.2)
+ activejob (4.2.6)
+ activesupport (= 4.2.6)
globalid (>= 0.3.0)
- activemodel (4.2.5.2)
- activesupport (= 4.2.5.2)
+ activemodel (4.2.6)
+ activesupport (= 4.2.6)
builder (~> 3.1)
- activerecord (4.2.5.2)
- activemodel (= 4.2.5.2)
- activesupport (= 4.2.5.2)
+ activerecord (4.2.6)
+ activemodel (= 4.2.6)
+ activesupport (= 4.2.6)
arel (~> 6.0)
activerecord-deprecated_finders (1.0.4)
activerecord-session_store (0.1.2)
actionpack (>= 4.0.0, < 5)
activerecord (>= 4.0.0, < 5)
railties (>= 4.0.0, < 5)
- activesupport (4.2.5.2)
+ activesupport (4.2.6)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -51,8 +51,8 @@ GEM
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.4)
- annotate (2.6.10)
- activerecord (>= 3.2, <= 4.3)
+ annotate (2.7.0)
+ activerecord (>= 3.2, < 6.0)
rake (~> 10.4)
arel (6.0.3)
asana (0.4.0)
@@ -145,7 +145,7 @@ GEM
crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
- css_parser (1.3.7)
+ css_parser (1.4.1)
addressable
d3_rails (3.5.11)
railties (>= 3.1.0)
@@ -459,8 +459,8 @@ GEM
nokogiri (>= 1.5.9)
macaddr (1.7.1)
systemu (~> 2.6.2)
- mail (2.6.3)
- mime-types (>= 1.16, < 3)
+ mail (2.6.4)
+ mime-types (>= 1.16, < 4)
mail_room (0.6.1)
method_source (0.8.2)
mime-types (1.25.1)
@@ -559,8 +559,8 @@ GEM
premailer (1.8.6)
css_parser (>= 1.3.6)
htmlentities (>= 4.0.0)
- premailer-rails (1.9.0)
- actionmailer (>= 3, < 5)
+ premailer-rails (1.9.2)
+ actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
pry (0.10.3)
coderay (~> 1.1.0)
@@ -589,16 +589,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.5.2)
- actionmailer (= 4.2.5.2)
- actionpack (= 4.2.5.2)
- actionview (= 4.2.5.2)
- activejob (= 4.2.5.2)
- activemodel (= 4.2.5.2)
- activerecord (= 4.2.5.2)
- activesupport (= 4.2.5.2)
+ rails (4.2.6)
+ actionmailer (= 4.2.6)
+ actionpack (= 4.2.6)
+ actionview (= 4.2.6)
+ activejob (= 4.2.6)
+ activemodel (= 4.2.6)
+ activerecord (= 4.2.6)
+ activesupport (= 4.2.6)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.5.2)
+ railties (= 4.2.6)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@@ -608,9 +608,9 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- railties (4.2.5.2)
- actionpack (= 4.2.5.2)
- activesupport (= 4.2.5.2)
+ railties (4.2.6)
+ actionpack (= 4.2.6)
+ activesupport (= 4.2.6)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
@@ -776,12 +776,13 @@ GEM
spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
- sprockets (3.3.5)
+ sprockets (3.6.0)
+ concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (2.3.3)
- actionpack (>= 3.0)
- activesupport (>= 3.0)
- sprockets (>= 2.8, < 4.0)
+ sprockets-rails (3.0.4)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0)
state_machines (0.4.0)
state_machines-activemodel (0.3.0)
activemodel (~> 4.1)
@@ -887,7 +888,7 @@ DEPENDENCIES
after_commit_queue
akismet (~> 2.0)
allocations (~> 1.0)
- annotate (~> 2.6.0)
+ annotate (~> 2.7.0)
asana (~> 0.4.0)
asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4)
@@ -992,13 +993,14 @@ DEPENDENCIES
rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
- rails (= 4.2.5.2)
+ rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3)
raphael-rails (~> 2.1.2)
rblineprof
rdoc (~> 3.6)
recaptcha
redcarpet (~> 3.3.3)
+ redis (~> 3.2)
redis-namespace
redis-rails (~> 4.0.0)
request_store (~> 1.3.0)
@@ -1032,7 +1034,7 @@ DEPENDENCIES
spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2)
- sprockets (~> 3.3.5)
+ sprockets (~> 3.6.0)
state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2)
teaspoon (~> 1.1.0)
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 6a670d5e887..af4462ece38 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -22,8 +22,19 @@ class @AwardsHandler
emoji = $(this)
.find(".icon")
.data "emoji"
+
+ if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown"
+ awards_handler.addAward "thumbsdown"
+
+ else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup"
+ awards_handler.addAward "thumbsup"
+
awards_handler.addAward emoji
+ didUserClickEmoji: (that, emoji) ->
+ if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
+ $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
+
showEmojiMenu: ->
if $(".emoji-menu").length
if $(".emoji-menu").is ".is-visible"
@@ -105,7 +116,7 @@ class @AwardsHandler
if origTitle
authors = origTitle.split(', ')
authors.push("me")
- award_block.attr("title", authors.join(", "))
+ award_block.attr("data-original-title", authors.join(", "))
@resetTooltip(award_block)
resetTooltip: (award) ->
@@ -122,7 +133,7 @@ class @AwardsHandler
nodes = []
nodes.push(
- "<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>",
+ "<button class='btn award-control js-emoji-btn has-tooltip active' data-original-title='me'>",
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
"<span class='award-control-text js-counter'>1</span>",
"</button>"
diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee
new file mode 100644
index 00000000000..f20992ead3e
--- /dev/null
+++ b/app/assets/javascripts/compare.js.coffee
@@ -0,0 +1,67 @@
+class @Compare
+ constructor: (@opts) ->
+ @source_loading = $ ".js-source-loading"
+ @target_loading = $ ".js-target-loading"
+
+ $('.js-compare-dropdown').each (i, dropdown) =>
+ $dropdown = $(dropdown)
+
+ $dropdown.glDropdown(
+ selectable: true
+ fieldName: $dropdown.data 'field-name'
+ filterable: true
+ id: (obj, $el) ->
+ $el.data 'id'
+ toggleLabel: (obj, $el) ->
+ $el.text().trim()
+ clicked: (e, el) =>
+ if $dropdown.is '.js-target-branch'
+ @getTargetHtml()
+ else if $dropdown.is '.js-source-branch'
+ @getSourceHtml()
+ else if $dropdown.is '.js-target-project'
+ @getTargetProject()
+ )
+
+ @initialState()
+
+ initialState: ->
+ @getSourceHtml()
+ @getTargetHtml()
+
+ getTargetProject: ->
+ $.ajax(
+ url: @opts.targetProjectUrl
+ data:
+ target_project_id: $("input[name='merge_request[target_project_id]']").val()
+ beforeSend: ->
+ $('.mr_target_commit').empty()
+ success: (html) ->
+ $('.js-target-branch-dropdown .dropdown-content').html html
+ )
+
+ getSourceHtml: ->
+ @sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit',
+ ref: $("input[name='merge_request[source_branch]']").val()
+ )
+
+ getTargetHtml: ->
+ @sendAjax(@opts.targetBranchUrl, @target_loading, '.mr_target_commit',
+ target_project_id: $("input[name='merge_request[target_project_id]']").val()
+ ref: $("input[name='merge_request[target_branch]']").val()
+ )
+
+ sendAjax: (url, loading, target, data) ->
+ $target = $(target)
+
+ $.ajax(
+ url: url
+ data: data
+ beforeSend: ->
+ loading.show()
+ $target.empty()
+ success: (html) ->
+ loading.hide()
+ $target.html html
+ $('.js-timeago', $target).timeago()
+ )
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index e8d25591f63..ee1d0fad289 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -57,14 +57,30 @@ class GitLabDropdownFilter
filter: (search_text) ->
data = @options.data()
- results = data
- if search_text isnt ""
- results = fuzzaldrinPlus.filter(data, search_text,
- key: @options.keys
- )
+ if data?
+ results = data
- @options.callback results
+ if search_text isnt ''
+ results = fuzzaldrinPlus.filter(data, search_text,
+ key: @options.keys
+ )
+
+ @options.callback results
+ else
+ elements = @options.elements()
+
+ if search_text
+ elements.each ->
+ $el = $(@)
+ matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
+
+ if matches.length
+ $el.show()
+ else
+ $el.hide()
+ else
+ elements.show()
class GitLabDropdownRemote
constructor: (@dataEndpoint, @options) ->
@@ -123,7 +139,7 @@ class GitLabDropdown
if _.isString(@filterInput)
@filterInput = @getElement(@filterInput)
- search_fields = if @options.search then @options.search.fields else [];
+ searchFields = if @options.search then @options.search.fields else [];
if @options.data
# If data is an array
@@ -147,7 +163,14 @@ class GitLabDropdown
filterInputBlur: @filterInputBlur
remote: @options.filterRemote
query: @options.data
- keys: @options.search.fields
+ keys: searchFields
+ elements: =>
+ selector = '.dropdown-content li:not(.divider)'
+
+ if @dropdown.find('.dropdown-toggle-page').length
+ selector = ".dropdown-page-one #{selector}"
+
+ return $(selector)
data: =>
return @fullData
callback: (data) =>
@@ -376,7 +399,7 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
- $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
+ $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject, el)
if value?
if !field.length and fieldName
# Create hidden input for form
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 839e6ec2c08..9946249adbf 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -73,7 +73,8 @@ class @MergeRequestTabs
@expandView()
else if action == 'diffs'
@loadDiff($target.attr('href'))
- @shrinkView()
+ if bp? and bp.getBreakpointSize() isnt 'lg'
+ @shrinkView()
else if action == 'builds'
@loadBuilds($target.attr('href'))
@expandView()
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index f73127f49f0..6bd4e885a03 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -85,15 +85,21 @@ class @MilestoneSelect
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
clicked: (selected) ->
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is page is 'projects:merge_requests:index'
+
if $dropdown.hasClass 'js-filter-bulk-update'
return
- if $dropdown.hasClass('js-filter-submit')
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
if selected.name?
selectedMilestone = selected.name
else
selectedMilestone = ''
Issues.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass('js-filter-submit')
+ $dropdown.closest('form').submit()
else
selected = $selectbox
.find('input[type="hidden"]')
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index e1778511240..860d4f438d0 100644
--- a/app/assets/javascripts/sidebar.js.coffee
+++ b/app/assets/javascripts/sidebar.js.coffee
@@ -4,6 +4,7 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
+ $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( ->
diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee
index 084f0e0dc65..e4b7a3172ec 100644
--- a/app/assets/javascripts/subscription.js.coffee
+++ b/app/assets/javascripts/subscription.js.coffee
@@ -10,10 +10,10 @@ class @Subscription
btn = $(event.currentTarget)
action = btn.find('span').text()
current_status = @subscription_status.attr('data-status')
- btn.prop('disabled', true)
+ btn.addClass('disabled')
$.post @url, =>
- btn.prop('disabled', false)
+ btn.removeClass('disabled')
status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed'
@subscription_status.attr('data-status', status)
action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
index f39184777ac..f16b735ece0 100644
--- a/app/assets/javascripts/todos.js.coffee
+++ b/app/assets/javascripts/todos.js.coffee
@@ -108,5 +108,10 @@ class @Todos
uri + separator + key + '=' + value
- goToTodoUrl: ->
- Turbolinks.visit($(this).data('url'))
+ goToTodoUrl: (e)->
+ todoLink = $(this).data('url')
+ if e.metaKey
+ e.preventDefault()
+ window.open(todoLink,'_blank')
+ else
+ Turbolinks.visit(todoLink)
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 657c5f033c7..e8c0172680d 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -7,6 +7,7 @@
&:focus,
&:active {
outline: none;
+ background-color: $btn-active-gray;
@include box-shadow($gl-btn-active-background);
}
}
@@ -27,7 +28,8 @@
color: $color;
}
- &:active {
+ &:active,
+ &.active {
@include box-shadow ($gl-btn-active-background);
background-color: $dark;
@@ -61,7 +63,7 @@
}
@mixin btn-white {
- @include btn-color($white-light, $border-white-light, $white-normal, $border-white-normal, $white-dark, $border-white-dark, #313236);
+ @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active);
}
.btn {
@@ -218,3 +220,26 @@
margin-right: 5px;
}
}
+
+.btn-text-field {
+ width: 100%;
+ text-align: left;
+ padding: 6px 16px;
+ border-color: $border-color;
+ color: $btn-placeholder-gray;
+ background-color: $background-color;
+
+ &:hover,
+ &:active,
+ &:focus {
+ cursor: text;
+ box-shadow: none;
+ border-color: $border-color;
+ color: $btn-placeholder-gray;
+ background-color: $background-color;
+ }
+}
+
+.btn-file-option {
+ background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 82dc1acbd01..ba6c7930cdc 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -248,7 +248,7 @@
.dropdown-title {
position: relative;
- padding: 0 0 15px;
+ padding: 0 25px 15px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
@@ -275,7 +275,7 @@
}
.dropdown-menu-close {
- right: 7px;
+ right: 5px;
width: 20px;
height: 20px;
top: -1px;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index b15f4e7bd5e..789df42fb66 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -15,12 +15,13 @@
.file-title {
position: relative;
- background: $background-color;
+ background-color: $background-color;
border-bottom: 1px solid $border-color;
margin: 0;
text-align: left;
padding: 10px $gl-padding;
word-wrap: break-word;
+ border-radius: 3px 3px 0 0;
.file-actions {
float: right;
@@ -49,7 +50,7 @@
}
}
- a {
+ a:not(.btn) {
color: $gl-dark-link-color;
}
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index fa9038ebaca..c83cf881596 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -33,15 +33,10 @@
background: $color;
}
- .complex-sidebar .nav-primary {
- border-right: 1px solid lighten($color, 3%);
- }
-
.sidebar-wrapper {
background: $color-darker;
.sidebar-user {
- border-top: 1px solid lighten($color, 3%);
background: $color-darker;
color: $color-light;
@@ -67,6 +62,7 @@
.count {
color: $color-light;
+ background: $color-dark;
}
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 724980b2208..b3397d16016 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -123,11 +123,11 @@ header {
}
@mixin collapsed-header {
- margin-left: 40px;
+ margin-left: $sidebar_collapsed_width;
}
.header-collapsed {
- margin-left: 40px;
+ margin-left: $sidebar_collapsed_width;
@media (min-width: $screen-md-min) {
@include collapsed-header;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 1d49249dd80..18189e985c4 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -144,7 +144,7 @@
}
a {
- padding: 7px 12px;
+ padding: 7px 15px;
font-size: $gl-font-size;
line-height: 24px;
color: $gray;
@@ -169,12 +169,10 @@
}
.count {
- &:before {
- content: '(';
- }
- &:after {
- content: ')';
- }
+ float: right;
+ background: #eee;
+ padding: 0 8px;
+ @include border-radius(6px);
}
&.back-link i {
@@ -193,27 +191,6 @@
}
}
-.expand-nav a {
- color: $gl-icon-color;
- width: 60px;
- position: fixed;
- top: 0;
- left: 0;
- font-size: 20px;
- background: #fff;
- height: 59px;
- text-align: center;
- line-height: 59px;
- border-bottom: 1px solid #eee;
- transition-duration: .3s;
- outline: none;
- z-index: 100;
-
- &:hover {
- text-decoration: none;
- }
-}
-
.collapse-nav a {
width: $sidebar_width;
position: fixed;
@@ -233,12 +210,55 @@
}
.page-sidebar-collapsed {
+ padding-left: $sidebar_collapsed_width;
+
.sidebar-wrapper {
- display: none;
+ width: $sidebar_collapsed_width;
+
+ .header-logo {
+ width: $sidebar_collapsed_width;
+
+ a {
+ padding-left: ($sidebar_collapsed_width - 36) / 2;
+
+ .gitlab-text-container {
+ display: none;
+ }
+ }
+ }
+
+ .nav-sidebar {
+ width: $sidebar_collapsed_width;
+
+ li {
+ width: auto;
+
+ a {
+ span {
+ display: none;
+ }
+ }
+ }
+ }
+
+ .collapse-nav a {
+ width: $sidebar_collapsed_width;
+ }
+
+ .sidebar-user {
+ padding-left: ($sidebar_collapsed_width - 36) / 2;
+ width: $sidebar_collapsed_width;
+
+ .username {
+ display: none;
+ }
+ }
}
}
.page-sidebar-expanded {
+ padding-left: $sidebar_collapsed_width;
+
@media (min-width: $screen-md-min) {
padding-left: $sidebar_width;
}
@@ -289,48 +309,3 @@
padding-right: $sidebar_collapsed_width;
}
}
-
-.complex-sidebar {
- display: inline-block;
-
- .nav-primary {
- width: 61px;
- float: left;
- height: 100vh;
-
- .nav-sidebar {
- width: 60px;
-
- li a {
- width: 60px;
-
- span {
- display: none;
- }
- }
- }
- }
-
- .nav-secondary {
- $nav-secondary-width: 168px;
-
- float: left;
- width: $nav-secondary-width;
-
- .nav-sidebar {
- width: $nav-secondary-width;
-
- li {
- width: $nav-secondary-width;
-
- a {
- width: $nav-secondary-width;
-
- i {
- display: none;
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index aa244fe548d..b91f2f6f898 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -14,10 +14,6 @@
background: $row-hover;
}
- &:last-child {
- border-bottom: none;
- }
-
.avatar {
margin-right: 15px;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 8d3ad934a50..1ebbd9b0e57 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -10,10 +10,10 @@ $gutter_inner_width: 258px;
/*
* UI elements
*/
-$border-color: #efeff1;
+$border-color: #e5e5e5;
$focus-border-color: #3aabf0;
$table-border-color: #eef0f2;
-$background-color: #faf9f9;
+$background-color: #fafafa;
/*
* Text
@@ -81,7 +81,7 @@ $provider-btn-not-active-color: #4688f1;
$white-light: #fff;
$white-normal: #ededed;
-$white-dark: #ededed;
+$white-dark: #ececec;
$gray-light: #faf9f9;
$gray-normal: #f5f5f5;
@@ -108,6 +108,8 @@ $red-light: #e52c5a;
$red-normal: #d22852;
$red-dark: darken($red-normal, 5%);
+$black-transparent: rgba(0, 0, 0, 0.3);
+
$border-white-light: #f1f2f4;
$border-white-normal: #d6dae2;
$border-white-dark: #c6cacf;
@@ -150,15 +152,22 @@ $gl-success: $green-normal;
$gl-info: $blue-normal;
$gl-warning: $orange-normal;
$gl-danger: $red-normal;
-$gl-btn-active-background: rgba(0, 0, 0, 0.12);
-$gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background;
+$gl-btn-active-background: rgba(0, 0, 0, 0.16);
+$gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
/*
* Commit Diff Colors
*/
$added: #63c363;
$deleted: #f77;
-
+$line-added: #ecfdf0;
+$line-added-dark: #c7f0d2;
+$line-removed: #fbe9eb;
+$line-removed-dark: #fac5cd;
+$line-number-old: #f9d7dc;
+$line-number-new: #ddfbe6;
+$match-line: #fafafa;
+$table-border-gray: #f0f0f0;
/*
* Fonts
*/
@@ -192,6 +201,13 @@ $dropdown-toggle-icon-color: #c4c4c4;
$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
/*
+* Buttons
+*/
+$btn-active-gray: #ececec;
+$btn-placeholder-gray: #c7c7c7;
+$btn-white-active: #848484;
+
+/*
* Award emoji
*/
$award-emoji-menu-bg: #fff;
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index b90c95c62d1..c482a1258f7 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -6,7 +6,7 @@
}
.diff-line-num, .diff-line-num a {
- color: rgba(0, 0, 0, 0.3);
+ color: $black-transparent;
}
// Code itself
@@ -30,7 +30,7 @@
}
.line_content.match {
- color: rgba(0, 0, 0, 0.3);
+ color: $black-transparent;
background: rgba(255, 255, 255, 0.4);
}
}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 8c1b0cd84ec..28331f59754 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -6,12 +6,12 @@
}
.diff-line-num, .diff-line-num a {
- color: rgba(0, 0, 0, 0.3);
+ color: $black-transparent;
}
// Code itself
pre.code, .diff-line-num {
- border-color: $border-color;
+ border-color: $table-border-gray;
}
&, pre.code, .line_holder .line_content {
@@ -23,36 +23,36 @@
.line_holder {
.diff-line-num {
&.old {
- background: #fdd;
- border-color: #f1c0c0;
+ background-color: $line-number-old;
+ border-color: $line-removed-dark;
}
&.new {
- background: #dbffdb;
- border-color: #c1e9c1;
+ background-color: $line-number-new;
+ border-color: $line-added-dark;
}
}
.line_content {
&.old {
- background: #ffecec;
+ background: $line-removed;
span.idiff {
- background-color: #f8cbcb;
+ background-color: $line-removed-dark;
}
}
&.new {
- background: #eaffea;
+ background-color: $line-added;
span.idiff {
- background-color: #a6f3a6;
+ background-color: $line-added-dark;
}
}
&.match {
- color: rgba(0, 0, 0, 0.3);
- background: #fafafa;
+ color: $black-transparent;
+ background: $match-line;
}
}
}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 082911bd118..358d2f4ab9d 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -20,6 +20,8 @@
margin: 0;
padding: 0;
margin-top: 10px;
+ word-break: normal;
+ white-space: pre-wrap;
}
.commit-info-row {
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 8272615768d..6453c91d955 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -47,6 +47,7 @@ li.commit {
.commit_short_id {
min-width: 65px;
+ color: $gl-dark-link-color;
font-family: $monospace_font;
}
@@ -88,6 +89,10 @@ li.commit {
padding: 0;
margin: 0;
}
+
+ a {
+ color: $gl-dark-link-color;
+ }
}
.commit-row-info {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 939555bb260..d0855f66911 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -2,6 +2,7 @@
.diff-file {
border: 1px solid $border-color;
margin-bottom: $gl-padding;
+ border-radius: 3px;
.diff-header {
position: relative;
@@ -10,6 +11,7 @@
padding: 10px 16px;
color: #555;
z-index: 10;
+ border-radius: 3px 3px 0 0;
.diff-title {
font-family: $monospace_font;
@@ -31,6 +33,7 @@
overflow-y: hidden;
background: #fff;
color: #333;
+ border-radius: 0 0 3px 3px;
.unfold {
cursor: pointer;
@@ -109,6 +112,10 @@
display: table-cell;
}
}
+
+ .text-file.diff-wrap-lines table .line_holder td span {
+ white-space: pre-wrap;
+ }
}
.image {
background: #ddd;
@@ -321,6 +328,16 @@
float: right;
}
+.diffs {
+ .content-block {
+ border-bottom: none;
+ }
+}
+
+.files-changed {
+ border-bottom: none;
+}
+
// Mobile
@media (max-width: 480px) {
.diff-title {
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index bd224705f04..604f1700cf8 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -59,6 +59,9 @@
position: relative;
overflow-y: auto;
padding: 15px;
+ .form-actions {
+ margin: -$gl-padding+1;
+ }
}
body.modal-open {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 4e02ec4e891..3e0a3140be7 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -49,6 +49,15 @@
}
.label-row {
+ .label-name {
+ display: inline-block;
+ width: 200px;
+
+ @media (max-width: $screen-xs-min) {
+ display: block;
+ }
+ }
+
.label {
padding: 9px;
font-size: 14px;
@@ -69,3 +78,52 @@
background-color: $gl-danger;
color: $white-light;
}
+
+.manage-labels-list {
+
+ .prepend-left-10 {
+ display: inline-block;
+ width: 40%;
+ vertical-align: middle;
+
+ @media (max-width: $screen-xs-min) {
+ display: block;
+ width: 100%;
+ margin-left: 0;
+ padding: 10px 0;
+ }
+ }
+
+ .pull-info-right {
+ float: right;
+
+ @media (max-width: $screen-xs-min) {
+ float: none;
+ }
+
+ .action-buttons {
+ border-color: transparent;
+ padding: 6px;
+ color: $gl-text-color;
+
+ &.subscribe-button {
+ padding-left: 0;
+ }
+ }
+
+ i {
+ color: $gl-text-color;
+ }
+
+ .append-right-20 {
+ a {
+ color: $gl-text-color;
+ }
+
+ @media (max-width: $screen-xs-min) {
+ display: block;
+ margin-bottom: 10px;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 1c6a4208974..b79335eab91 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -123,6 +123,8 @@
.mr_source_commit,
.mr_target_commit {
+ margin-bottom: 0;
+
.commit {
margin: 0;
padding: 2px 0;
@@ -174,10 +176,6 @@
display: none;
}
-.merge-request-form .select2-container {
- width: 250px !important;
-}
-
#modal_merge_info .modal-dialog {
width: 600px;
@@ -200,3 +198,76 @@
overflow-x: scroll;
}
}
+
+.panel-new-merge-request {
+ .panel-heading {
+ padding: 5px 10px;
+ font-weight: 600;
+ line-height: 25px;
+ }
+
+ .panel-body {
+ padding: 10px 5px;
+ }
+
+ .panel-footer {
+ padding: 5px 10px;
+ }
+
+ .commit {
+ .commit-row-title {
+ margin-bottom: 4px;
+ }
+
+ .avatar {
+ width: 20px;
+ height: 20px;
+ margin-right: 5px;
+ }
+
+ .commit-row-info {
+ line-height: 20px;
+ }
+ }
+
+ .btn-clipboard {
+ margin-right: 5px;
+ padding: 0;
+ background: transparent;
+ }
+
+ .ci-status-link {
+ margin-right: 5px;
+ }
+}
+
+.merge-request-select {
+ padding-left: 5px;
+ padding-right: 5px;
+ margin-bottom: 10px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ width: 50%;
+ margin-bottom: 0;
+ }
+
+ .dropdown-menu-toggle {
+ width: 100%;
+ }
+
+ .dropdown-menu {
+ left: 5px;
+ right: 5px;
+ width: auto;
+ }
+}
+
+.issuable-form-select-holder {
+ display: inline-block;
+ width: 250px;
+}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index a909776b437..4d4d508396d 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -1,10 +1,10 @@
/**
* Note Form
*/
-.reply-btn {
- @extend .btn-primary;
- margin: 10px $gl-padding;
+.comment-btn {
+ @extend .btn-create;
}
+
.diff-file .diff-content {
tr.line_holder:hover > td .line_note_link {
opacity: 1.0;
@@ -113,13 +113,12 @@
.discussion-body,
.diff-file {
.notes .note {
- border-color: #ddd;
padding: 10px 15px;
}
.discussion-reply-holder {
- background: $background-color;
- border-top: 1px solid $border-color;
+ background-color: $white-light;
+ padding: 10px 16px;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index aca86457c70..7295fe51121 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -58,6 +58,7 @@ ul.notes {
.note {
display: block;
position: relative;
+ border-bottom: 1px solid $table-border-gray;
&.is-editting {
.note-header,
@@ -117,9 +118,6 @@ ul.notes {
padding-bottom: 3px;
}
- &:last-child {
- border-bottom: 1px solid $border-color;
- }
}
}
@@ -137,14 +135,14 @@ ul.notes {
font-family: $regular_font;
td {
- border: 1px solid #ddd;
+ border: 1px solid $table-border-gray;
border-left: none;
&.notes_line {
vertical-align: middle;
text-align: center;
padding: 10px 0;
- background: #fff;
+ background: $background-color;
color: $text-color;
}
&.notes_line2 {
@@ -175,9 +173,6 @@ ul.notes {
}
}
- .author_link {
- font-weight: 600;
- }
}
.note-headline-light,
@@ -203,14 +198,26 @@ ul.notes {
line-height: 24px;
.fa {
+ color: $notes-action-color;
position: relative;
top: 1px;
font-size: 17px;
}
- .fa-trash-o {
- top: 0;
- font-size: 16px;
+ &.js-note-delete {
+ i {
+ &:hover {
+ color: $gl-text-red;
+ }
+ }
+ }
+
+ &.js-note-edit {
+ i {
+ &:hover {
+ color: $gl-link-color;
+ }
+ }
}
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c81cb85dc1b..97d53acde94 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -47,6 +47,16 @@ class ApplicationController < ActionController::Base
email: current_user.email,
username: current_user.username,
)
+
+ Raven.tags_context(program: sentry_program_context)
+ end
+ end
+
+ def sentry_program_context
+ if Sidekiq.server?
+ 'sidekiq'
+ else
+ 'rails'
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 49064f5d505..ae613f5e093 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -207,20 +207,20 @@ class Projects::MergeRequestsController < Projects::ApplicationController
#This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
@commit = @repository.commit(params[:ref]) if params[:ref].present?
+ render layout: false
end
def branch_to
@target_project = selected_target_project
@commit = @target_project.commit(params[:ref]) if params[:ref].present?
+ render layout: false
end
def update_branches
@target_project = selected_target_project
@target_branches = @target_project.repository.branch_names
- respond_to do |format|
- format.js
- end
+ render layout: false
end
def ci_status
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 820d69c230b..9e59a295fc4 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -27,9 +27,9 @@ module BlobHelper
link_opts)
if !on_top_of_branch?(project, ref)
- button_tag "Edit", class: "btn btn-default disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
+ button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
- link_to "Edit", edit_path, class: 'btn'
+ link_to "Edit", edit_path, class: 'btn btn-file-option'
elsif can?(current_user, :fork_project, project)
continue_params = {
to: edit_path,
@@ -38,7 +38,7 @@ module BlobHelper
}
fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
- link_to "Edit", fork_path, class: 'btn', method: :post
+ link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post
end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index bde0799f3de..35ba543cef1 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -28,7 +28,7 @@ module CommitsHelper
def commit_to_html(commit, project, inline = true)
template = inline ? "inline_commit" : "commit"
- escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
+ render "projects/commits/#{template}", commit: commit, project: project unless commit.nil?
end
# Breadcrumb links for a Project and, if applicable, a tree path
@@ -117,7 +117,7 @@ module CommitsHelper
end
end
link_to(
- "Browse Files »",
+ "Browse Files",
namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right"
)
@@ -197,7 +197,7 @@ module CommitsHelper
link_to(
namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff.new_path)),
- class: 'btn view-file js-view-file'
+ class: 'btn view-file js-view-file btn-file-option'
) do
raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id')
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
new file mode 100644
index 00000000000..6a43be2cf3e
--- /dev/null
+++ b/app/helpers/form_helper.rb
@@ -0,0 +1,18 @@
+module FormHelper
+ def form_errors(model)
+ return unless model.errors.any?
+
+ pluralized = 'error'.pluralize(model.errors.count)
+ headline = "The form contains the following #{pluralized}:"
+
+ content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
+ content_tag(:h4, headline) <<
+ content_tag(:ul) do
+ model.errors.full_messages.
+ map { |msg| content_tag(:li, msg) }.
+ join.
+ html_safe
+ end
+ end
+ end
+end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 2f760af02fd..3a45205563e 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -116,29 +116,6 @@ module GitlabMarkdownHelper
end
end
- MARKDOWN_TIPS = [
- "End a line with two or more spaces for a line-break, or soft-return",
- "Inline code can be denoted by `surrounding it with backticks`",
- "Blocks of code can be denoted by three backticks ``` or four leading spaces",
- "Emoji can be added by :emoji_name:, for example :thumbsup:",
- "Notify other participants using @user_name",
- "Notify a specific group using @group_name",
- "Notify the entire team using @all",
- "Reference an issue using a hash, for example issue #123",
- "Reference a merge request using an exclamation point, for example MR !123",
- "Italicize words or phrases using *asterisks* or _underscores_",
- "Bold words or phrases using **double asterisks** or __double underscores__",
- "Strikethrough words or phrases using ~~two tildes~~",
- "Make a bulleted list using + pluses, - minuses, or * asterisks",
- "Denote blockquotes using > at the beginning of a line",
- "Make a horizontal line using three or more hyphens ---, asterisks ***, or underscores ___"
- ].freeze
-
- # Returns a random markdown tip for use as a textarea placeholder
- def random_markdown_tip
- MARKDOWN_TIPS.sample
- end
-
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 24b90fef4fe..bcf8639c829 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -115,17 +115,32 @@ module IssuesHelper
icon('eye-slash') if issue.confidential?
end
- def emoji_icon(name, unicode = nil, aliases = [])
+ def emoji_icon(name, unicode = nil, aliases = [], sprite: true)
unicode ||= Emoji.emoji_filename(name) rescue ""
- content_tag :div, "",
- class: "icon emoji-icon emoji-#{unicode}",
- title: name,
- data: {
- aliases: aliases.join(' '),
- emoji: name,
- unicode_name: unicode
- }
+ data = {
+ aliases: aliases.join(" "),
+ emoji: name,
+ unicode_name: unicode
+ }
+
+ if sprite
+ # Emoji icons for the emoji menu, these use a spritesheet.
+ content_tag :div, "",
+ class: "icon emoji-icon emoji-#{unicode}",
+ title: name,
+ data: data
+ else
+ # Emoji icons displayed separately, used for the awards already given
+ # to an issue or merge request.
+ content_tag :img, "",
+ class: "icon emoji",
+ title: name,
+ height: "20px",
+ width: "20px",
+ src: url_to_image("#{unicode}.png"),
+ data: data
+ end
end
def emoji_author_list(notes, current_user)
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 698f90cb27a..95072b5373f 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -69,10 +69,7 @@ module NotesHelper
line_type: line_type
}
- button_tag class: 'btn btn-nr reply-btn js-discussion-reply-button',
- data: data, title: 'Add a reply' do
- link_text = icon('comment')
- link_text << ' Reply'
- end
+ button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
+ data: data, title: 'Add a reply'
end
end
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index 3bc1b24b5e2..06be1a53318 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -3,11 +3,9 @@
%p Please use this form to report users who create spam issues, comments or behave inappropriately.
%hr
= form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f|
+ = form_errors(@abuse_report)
+
= f.hidden_field :user_id
- - if @abuse_report.errors.any?
- .alert.alert-danger
- - @abuse_report.errors.full_messages.each do |msg|
- %p= msg
.form-group
= f.label :user_id, class: 'control-label'
.col-sm-10
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index 6f325914d14..d88f3ad314d 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -1,8 +1,5 @@
= form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f|
- - if @appearance.errors.any?
- .alert.alert-danger
- - @appearance.errors.full_messages.each do |msg|
- %p= msg
+ = form_errors(@appearance)
%fieldset.sign-in
%legend
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index de86dacbb12..a8cca1a81cb 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -1,9 +1,5 @@
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
- - if @application_setting.errors.any?
- #error_explanation
- .alert.alert-danger
- - @application_setting.errors.full_messages.each do |msg|
- %p= msg
+ = form_errors(@application_setting)
%fieldset
%legend Visibility and Access Controls
diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml
index e18f7b499dd..4aacbb8cd77 100644
--- a/app/views/admin/applications/_form.html.haml
+++ b/app/views/admin/applications/_form.html.haml
@@ -1,9 +1,6 @@
= form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f|
- - if application.errors.any?
- .alert.alert-danger
- %button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
- - application.errors.full_messages.each do |msg|
- %p= msg
+ = form_errors(application)
+
= content_tag :div, class: 'form-group' do
= f.label :name, class: 'col-sm-2 control-label'
.col-sm-10
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index b748460a9f7..6b157abf842 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -4,10 +4,8 @@
= render_broadcast_message(@broadcast_message.message.presence || "Your message here")
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f|
- -if @broadcast_message.errors.any?
- .alert.alert-danger
- - @broadcast_message.errors.full_messages.each do |msg|
- %p= msg
+ = form_errors(@broadcast_message)
+
.form-group
= f.label :message, class: 'control-label'
.col-sm-10
diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml
index 5b46b3222a9..15aa059c93d 100644
--- a/app/views/admin/deploy_keys/new.html.haml
+++ b/app/views/admin/deploy_keys/new.html.haml
@@ -4,11 +4,7 @@
%div
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f|
- -if @deploy_key.errors.any?
- .alert.alert-danger
- %ul
- - @deploy_key.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@deploy_key)
.form-group
= f.label :title, class: "control-label"
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 7f2b1cd235d..0cc405401cf 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -1,8 +1,5 @@
= form_for [:admin, @group], html: { class: "form-horizontal" } do |f|
- - if @group.errors.any?
- .alert.alert-danger
- %span= @group.errors.full_messages.first
-
+ = form_errors(@group)
= render 'shared/group_form', f: f
.form-group.group-description-holder
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index 53b3cd04c68..ad952052f25 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -10,10 +10,8 @@
= form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f|
- -if @hook.errors.any?
- .alert.alert-danger
- - @hook.errors.full_messages.each do |msg|
- %p= msg
+ = form_errors(@hook)
+
.form-group
= f.label :url, "URL:", class: 'control-label'
.col-sm-10
diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml
index 3a788558226..112a201fafa 100644
--- a/app/views/admin/identities/_form.html.haml
+++ b/app/views/admin/identities/_form.html.haml
@@ -1,9 +1,5 @@
= form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f|
- - if @identity.errors.any?
- #error_explanation
- .alert.alert-danger
- - @identity.errors.full_messages.each do |msg|
- %p= msg
+ = form_errors(@identity)
.form-group
= f.label :provider, class: 'control-label'
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index 8c6b389bf15..448aa953548 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -1,11 +1,5 @@
= form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f|
- -if @label.errors.any?
- .row
- .col-sm-offset-2.col-sm-10
- .alert.alert-danger
- - @label.errors.full_messages.each do |msg|
- %span= msg
- %br
+ = form_errors(@label)
.form-group
= f.label :title, class: 'control-label'
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index d2527ede995..b05fdbd5552 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -1,10 +1,6 @@
.user_new
= form_for [:admin, @user], html: { class: 'form-horizontal fieldset-form' } do |f|
- -if @user.errors.any?
- #error_explanation
- .alert.alert-danger
- - @user.errors.full_messages.each do |msg|
- %p= msg
+ = form_errors(@user)
%fieldset
%legend Account
diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml
index 906b0676150..5c98265727a 100644
--- a/app/views/doorkeeper/applications/_form.html.haml
+++ b/app/views/doorkeeper/applications/_form.html.haml
@@ -1,9 +1,5 @@
= form_for application, url: doorkeeper_submit_path(application), html: {role: 'form'} do |f|
- - if application.errors.any?
- .alert.alert-danger
- %ul
- - application.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(application)
.form-group
= f.label :name, class: 'label-light'
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index ea5a0358392..a698cbbe9db 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -5,9 +5,7 @@
Group settings
.panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- - if @group.errors.any?
- .alert.alert-danger
- %span= @group.errors.full_messages.first
+ = form_errors(@group)
= render 'shared/group_form', f: f
.form-group
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 30ab8aeba13..2b8bc269e64 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -6,10 +6,7 @@
%hr
= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
- - if @group.errors.any?
- .alert.alert-danger
- %span= @group.errors.full_messages.first
-
+ = form_errors(@group)
= render 'shared/group_form', f: f, autofocus: true
.form-group.group-description-holder
diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml
new file mode 100644
index 00000000000..2ed51d87ca1
--- /dev/null
+++ b/app/views/layouts/_collapse_button.html.haml
@@ -0,0 +1,4 @@
+- if nav_menu_collapsed?
+ = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
+- else
+ = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 9be36273c7d..c799e9c588d 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,7 +1,5 @@
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast"
- .expand-nav
- = link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open sidebar"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
%a#logo
@@ -10,19 +8,15 @@
.gitlab-text-container
%h3 GitLab
- - primary_sidebar = current_user ? 'dashboard' : 'explore'
-
- - if defined?(sidebar) && sidebar && sidebar != primary_sidebar
- .complex-sidebar
- .nav-primary
- = render "layouts/nav/#{primary_sidebar}"
- .nav-secondary
- = render "layouts/nav/#{sidebar}"
+ - if defined?(sidebar) && sidebar
+ = render "layouts/nav/#{sidebar}"
+ - elsif current_user
+ = render 'layouts/nav/dashboard'
- else
- = render "layouts/nav/#{primary_sidebar}"
+ = render 'layouts/nav/explore'
.collapse-nav
- = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Hide sidebar"
+ = render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 22d1d4d8597..280a1b93729 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -95,7 +95,7 @@
Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
- = nav_link(controller: :application_settings) do
+ = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
%span
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index f1052eadf81..5cef652da14 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -15,12 +15,12 @@
= icon('dashboard fw')
%span
Activity
- = nav_link(path: ['dashboard/groups#index', 'explore/groups#index']) do
+ = nav_link(controller: :groups) do
= link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
- = nav_link(path: 'dashboard#milestones') do
+ = nav_link(controller: :milestones) do
= link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
%span
@@ -48,6 +48,7 @@
%span
Help
+ %li.separate-item
= nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 0b7de9633ec..55940741dc0 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,4 +1,12 @@
%ul.nav.nav-sidebar
+ = nav_link do
+ = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Go to dashboard
+
+ %li.separate-item
+
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
= icon('group fw')
@@ -34,7 +42,7 @@
%span
Members
- if can?(current_user, :admin_group, @group)
- = nav_link do
+ = nav_link(html_options: { class: "separate-item" }) do
= link_to edit_group_path(@group), title: 'Settings' do
= icon ('cogs fw')
%span
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index cc119fd64e6..3b9d31a6fc5 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,4 +1,12 @@
%ul.nav.nav-sidebar
+ = nav_link do
+ = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Go to dashboard
+
+ %li.separate-item
+
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index d0f82b5f57f..86b46e8c75e 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,4 +1,19 @@
%ul.nav.nav-sidebar
+ - if @project.group
+ = nav_link do
+ = link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Go to group
+ - else
+ = nav_link do
+ = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Go to dashboard
+
+ %li.separate-item
+
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('bookmark fw')
@@ -98,7 +113,7 @@
Snippets
- if project_nav_tab? :settings
- = nav_link(html_options: {class: "#{project_tab_class}"}) do
+ = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
= link_to edit_project_path(@project), title: 'Settings' do
= icon('cogs fw')
%span
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index 4d78215ed3c..b3ed59a1a4a 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -1,10 +1,6 @@
%div
= form_for [:profile, @key], html: { class: 'js-requires-input' } do |f|
- - if @key.errors.any?
- .alert.alert-danger
- %ul
- - @key.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@key)
.form-group
= f.label :key, class: 'label-light'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 3d15c0d932b..6609295a2a5 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -2,11 +2,7 @@
- header_title page_title, profile_notifications_path
= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
- -if @user.errors.any?
- %div.alert.alert-danger
- %ul
- - @user.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@user)
= hidden_field_tag :notification_type, 'global'
.row
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 44d758dceb3..5ac8a8b9d09 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -13,11 +13,8 @@
- unless @user.password_automatically_set?
or recover your current one
= form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
- -if @user.errors.any?
- .alert.alert-danger
- %ul
- - @user.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@user)
+
- unless @user.password_automatically_set?
.form-group
= f.label :current_password, class: 'label-light'
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index d165f758c81..2eb9fac57c3 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -7,11 +7,8 @@
Please set a new password before proceeding.
%br
After a successful password update you will be redirected to login screen.
- -if @user.errors.any?
- .alert.alert-danger
- %ul
- - @user.errors.full_messages.each do |msg|
- %li= msg
+
+ = form_errors(@user)
- unless @user.password_automatically_set?
.form-group
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index dcb3be9585d..f59d27f7ed0 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,9 +1,6 @@
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f|
- -if @user.errors.any?
- %div.alert.alert-danger
- %ul
- - @user.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@user)
+
.row
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
diff --git a/app/views/projects/_errors.html.haml b/app/views/projects/_errors.html.haml
index 7c8bb33ed7e..2dba22d3be6 100644
--- a/app/views/projects/_errors.html.haml
+++ b/app/views/projects/_errors.html.haml
@@ -1,4 +1 @@
-- if @project.errors.any?
- .alert.alert-danger
- %button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
- = @project.errors.full_messages.first
+= form_errors(@project)
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 4920910fee1..7a78d61a611 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -2,13 +2,13 @@
.md-header
%ul.nav-links
%li.active
- %a.js-md-write-button{ href: "#md-write-holder" }
+ %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
Write
%li
- %a.js-md-preview-button{ href: "#md-preview-holder" }
+ %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview
%li.pull-right
- %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button' }
+ %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
Go full screen
.md-write-holder
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 7f2903589a9..7da89231243 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -19,24 +19,17 @@
.pull-right
- if ci_commit
= render_ci_status(ci_commit)
- &nbsp;
= clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
- .notes_count
- - if note_count > 0
- %span.light
- %i.fa.fa-comments
- = note_count
-
- if commit.description?
.commit-row-description.js-toggle-content
%pre
= preserve(markdown(escape_once(commit.description), pipeline: :single_line))
.commit-row-info
+ by
= commit_author_link(commit, avatar: true, size: 24)
- authored
.committed_ago
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} &nbsp;
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 5e182af2669..f6565f85836 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,10 +1,6 @@
%div
= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
- -if @key.errors.any?
- .alert.alert-danger
- %ul
- - @key.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@key)
.form-group
= f.label :title, class: "control-label"
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 2e1a37aa06d..eaab99973a4 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -3,7 +3,7 @@
- diff_files = safe_diff_files(diffs, diff_refs)
-.content-block.oneline-block
+.content-block.oneline-block.files-changed
.inline-parallel-buttons
.btn-group
= inline_diff_btn
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 698ed02ea0e..83a8d7ae9bf 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -3,7 +3,7 @@
- if diff_file.diff.submodule?
%span
= icon('archive fw')
- %strong
+ %span
= submodule_link(blob, @commit.id, project.repository)
- else
= blob_icon blob.mode, blob.name
@@ -11,13 +11,13 @@
= link_to "#diff-#{i}" do
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- %strong.filename.old
+ .filename.old
= old_path
&rarr;
- %strong.filename.new
+ .filename.new
= new_path
- else
- %strong
+ %span
= diff_file.new_path
- if diff_file.deleted_file
deleted
@@ -28,8 +28,8 @@
.file-actions.hidden-xs
- if blob_text_viewable?(blob)
- = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file" do
- = icon('comments')
+ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do
+ = icon('comment')
\
- if editable_diff?(diff_file)
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 67d016bd871..e39224d86c6 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -9,10 +9,8 @@
%hr.clearfix
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
- -if @hook.errors.any?
- .alert.alert-danger
- - @hook.errors.full_messages.each do |msg|
- %p= msg
+ = form_errors(@hook)
+
.form-group
= f.label :url, "URL", class: 'control-label'
.col-sm-10
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index be7a0bb5628..aa143e54ffe 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -1,11 +1,5 @@
= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f|
- -if @label.errors.any?
- .row
- .col-sm-offset-2.col-sm-10
- .alert.alert-danger
- - @label.errors.full_messages.each do |msg|
- %span= msg
- %br
+ = form_errors(@label)
.form-group
= f.label :title, class: 'control-label'
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 0612863296a..097a65969a6 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -1,24 +1,27 @@
%li{id: dom_id(label)}
= render "shared/label_row", label: label
- .pull-right
- %strong.append-right-20
+ .pull-info-right
+ %span.append-right-20
= link_to_label(label, type: :merge_request) do
- = pluralize label.open_merge_requests_count, 'open merge request'
+ = pluralize label.open_merge_requests_count, 'merge request'
- %strong.append-right-20
+ %span.append-right-20
= link_to_label(label) do
= pluralize label.open_issues_count(current_user), 'open issue'
- if current_user
.label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
.subscription-status{data: {status: label_subscription_status(label)}}
- %button.btn.btn-sm.btn-info.subscribe-button
+
+ %a.subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}}
%span= label_subscription_toggle_button_text(label)
- if can? current_user, :admin_label, @project
- = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm'
- = link_to 'Delete', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+ = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn action-buttons', data: {toggle: "tooltip"} do
+ %i.fa.fa-pencil-square-o
+ = link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn action-buttons remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do
+ %i.fa.fa-trash-o
- if current_user
:javascript
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 01dc7519bee..7d7c487e970 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -5,33 +5,74 @@
.hide.alert.alert-danger.mr-compare-errors
.merge-request-branches.row
.col-md-6
- .panel.panel-default
+ .panel.panel-default.panel-new-merge-request
.panel-heading
- %strong Source branch
- .panel-body
- = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true })
- &nbsp;
- = f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } })
+ Source branch
+ .panel-body.clearfix
+ .merge-request-select.dropdown
+ = f.hidden_field :source_project_id
+ = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" }
+ .dropdown-menu.dropdown-menu-selectable.dropdown-source-project
+ = dropdown_title("Select source project")
+ = dropdown_filter("Search projects")
+ = dropdown_content do
+ - is_active = f.object.source_project_id == @merge_request.source_project.id
+ %ul
+ %li
+ %a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
+ = @merge_request.source_project_path
+ .merge-request-select.dropdown
+ = f.hidden_field :source_branch
+ = dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
+ .dropdown-menu.dropdown-menu-selectable.dropdown-source-branch
+ = dropdown_title("Select source branch")
+ = dropdown_filter("Search branches")
+ = dropdown_content do
+ %ul
+ - @merge_request.source_branches.each do |branch|
+ %li
+ %a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
+ = branch
.panel-footer
- .mr_source_commit
+ = icon('spinner spin', class: 'js-source-loading')
+ %ul.list-unstyled.mr_source_commit
.col-md-6
- .panel.panel-default
+ .panel.panel-default.panel-new-merge-request
.panel-heading
- %strong Target branch
- .panel-body
+ Target branch
+ .panel-body.clearfix
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
- = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true })
- &nbsp;
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } })
+ .merge-request-select.dropdown
+ = f.hidden_field :target_project_id
+ = dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" }
+ .dropdown-menu.dropdown-menu-selectable.dropdown-target-project
+ = dropdown_title("Select target project")
+ = dropdown_filter("Search projects")
+ = dropdown_content do
+ %ul
+ - projects.each do |project|
+ %li
+ %a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
+ = project.path_with_namespace
+ .merge-request-select.dropdown
+ = f.hidden_field :target_branch
+ = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
+ .dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown
+ = dropdown_title("Select target branch")
+ = dropdown_filter("Search branches")
+ = dropdown_content do
+ %ul
+ - @merge_request.target_branches.each do |branch|
+ %li
+ %a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
+ = branch
.panel-footer
- .mr_target_commit
+ = icon('spinner spin', class: "js-target-loading")
+ %ul.list-unstyled.mr_target_commit
- if @merge_request.errors.any?
- .alert.alert-danger
- - @merge_request.errors.full_messages.each do |msg|
- %div= msg
-
+ = form_errors(@merge_request)
- elsif @merge_request.source_branch.present? && @merge_request.target_branch.present?
.light-well.append-bottom-default
.center
@@ -45,40 +86,11 @@
and
%span.label-branch #{@merge_request.target_branch}
are the same.
-
-
- .form-actions
- = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
-
-:javascript
- var source_branch = $("#merge_request_source_branch")
- , target_branch = $("#merge_request_target_branch")
- , target_project = $("#merge_request_target_project_id");
-
- $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: source_branch.val() });
- $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
-
- target_project.on("change", function() {
- $.get("#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: $(this).val() });
- });
- source_branch.on("change", function() {
- $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: $(this).val() });
- $(".mr-compare-errors").fadeOut();
- $(".mr-compare-btn").enable();
- });
- target_branch.on("change", function() {
- $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
- $(".mr-compare-errors").fadeOut();
- $(".mr-compare-btn").enable();
- });
-
+ = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
:javascript
- $(".merge-request-form").on('submit', function () {
- if ($("#merge_request_source_branch").val() === "" || $('#merge_request_target_branch').val() === "") {
- $(".mr-compare-errors").html("You must select source and target branch to proceed");
- $(".mr-compare-errors").fadeIn();
- event.preventDefault();
- return;
- }
+ new Compare({
+ targetProjectUrl: "#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
+ sourceBranchUrl: "#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
+ targetBranchUrl: "#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}"
});
diff --git a/app/views/projects/merge_requests/branch_from.html.haml b/app/views/projects/merge_requests/branch_from.html.haml
new file mode 100644
index 00000000000..4f90dde6fa8
--- /dev/null
+++ b/app/views/projects/merge_requests/branch_from.html.haml
@@ -0,0 +1 @@
+= commit_to_html(@commit, @source_project, false)
diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml
deleted file mode 100644
index 9210798f39c..00000000000
--- a/app/views/projects/merge_requests/branch_from.js.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-:plain
- $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
- $('.js-timeago').timeago()
diff --git a/app/views/projects/merge_requests/branch_to.html.haml b/app/views/projects/merge_requests/branch_to.html.haml
new file mode 100644
index 00000000000..67a7a6bcec9
--- /dev/null
+++ b/app/views/projects/merge_requests/branch_to.html.haml
@@ -0,0 +1 @@
+= commit_to_html(@commit, @target_project, false)
diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml
deleted file mode 100644
index 32fe2d535f3..00000000000
--- a/app/views/projects/merge_requests/branch_to.js.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-:plain
- $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
- $('.js-timeago').timeago()
diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/update_branches.html.haml
new file mode 100644
index 00000000000..1b93188a10c
--- /dev/null
+++ b/app/views/projects/merge_requests/update_branches.html.haml
@@ -0,0 +1,5 @@
+%ul
+ - @target_branches.each do |branch|
+ %li
+ %a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
+ = branch
diff --git a/app/views/projects/merge_requests/update_branches.js.haml b/app/views/projects/merge_requests/update_branches.js.haml
deleted file mode 100644
index ca21b3bc0de..00000000000
--- a/app/views/projects/merge_requests/update_branches.js.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-:plain
- $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}");
-
- $('select.target_branch').select2({
- width: 'resolve',
- dropdownAutoWidth: true
- });
-
- $(".mr_target_commit").html("");
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 23f2bca7baf..b2dae1c70ee 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,9 +1,6 @@
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f|
- -if @milestone.errors.any?
- .alert.alert-danger
- %ul
- - @milestone.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@milestone)
+
.row
.col-md-6
.form-group
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index 11f9859a90f..39be072855a 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -3,9 +3,6 @@
- if !defined?(line) || line == note.diff_line
%tr.notes_holder
%td.notes_line{ colspan: 2 }
- %span.discussion-notes-count
- %i.fa.fa-comment
- = notes.count
%td.notes_content
%ul.notes{ data: { discussion_id: note.discussion_id } }
= render notes
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index bb761ed2f94..f8aa5e2fa7d 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -4,9 +4,6 @@
%tr.notes_holder
- if note1
%td.notes_line.old
- %span.btn.disabled
- %i.fa.fa-comment
- = notes_left.count
%td.notes_content.parallel.old
%ul.notes{ data: { discussion_id: note1.discussion_id } }
= render notes_left
@@ -19,9 +16,6 @@
- if note2
%td.notes_line.new
- %span.btn.disabled
- %i.fa.fa-comment
- = notes_right.count
%td.notes_content.parallel.new
%ul.notes{ data: { discussion_id: note2.discussion_id } }
= render notes_right
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index a681d6dece4..5c42423541e 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -17,8 +17,8 @@
%span.note-role
= access
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
- = icon('pencil-square-o')
- = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete' do
+ = icon('pencil')
+ = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
= icon('trash-o')
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index cfd7e1534ca..653b02da4db 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -13,11 +13,7 @@
- if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f|
- -if @protected_branch.errors.any?
- .alert.alert-danger
- %ul
- - @protected_branch.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@protected_branch)
.form-group
= f.label :name, "Branch", class: 'control-label'
diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml
index efe1e6f24c2..ca284b84d39 100644
--- a/app/views/projects/variables/show.html.haml
+++ b/app/views/projects/variables/show.html.haml
@@ -13,13 +13,7 @@
= nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
- - if @project.errors.any?
- #error_explanation
- %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:"
- .alert.alert-error
- %ul
- - @project.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@project)
= f.fields_for :variables do |variable_form|
.form-group
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index f0d1932e23c..812876e2835 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,9 +1,5 @@
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f|
- -if @page.errors.any?
- #error_explanation
- .alert.alert-danger
- - @page.errors.full_messages.each do |msg|
- %p= msg
+ = form_errors(@page)
= f.hidden_field :title, value: @page.title
.form-group
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index 4b47b0291be..b38c5e18efb 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -1,4 +1,5 @@
%span.label-row
- = link_to_label(label, tooltip: false)
+ %span.label-name
+ = link_to_label(label, tooltip: false)
%span.prepend-left-10
= markdown(label.description, pipeline: :single_line)
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 5a60ff5a5da..fc935166bf6 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -1,9 +1,4 @@
-- if @service.errors.any?
- #error_explanation
- .alert.alert-danger
- %ul
- - @service.errors.full_messages.each do |msg|
- %li= msg
+= form_errors(@service)
- if @service.help.present?
.well
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index e2a9e5bfb92..757a3812deb 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -1,10 +1,5 @@
-- if issuable.errors.any?
- .row
- .col-sm-offset-2.col-sm-10
- .alert.alert-danger
- - issuable.errors.full_messages.each do |msg|
- %span= msg
- %br
+= form_errors(issuable)
+
.form-group
= f.label :title, class: 'control-label'
.col-sm-10
@@ -53,10 +48,11 @@
.issue-assignee
= f.label :assignee_id, "Assignee", class: 'control-label'
.col-sm-10
- = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
- placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
- selected: issuable.assignee_id, project: @target_project || @project,
- first_user: true, current_user: true, include_blank: true)
+ .issuable-form-select-holder
+ = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
+ placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
+ selected: issuable.assignee_id, project: @target_project || @project,
+ first_user: true, current_user: true, include_blank: true)
&nbsp;
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
.form-group
@@ -64,8 +60,9 @@
= f.label :milestone_id, "Milestone", class: 'control-label'
.col-sm-10
- if milestone_options(issuable).present?
- = f.select(:milestone_id, milestone_options(issuable),
- { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
+ .issuable-form-select-holder
+ = f.select(:milestone_id, milestone_options(issuable),
+ { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
- else
.prepend-top-10
%span.light No open milestones available.
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 1041eccd1df..47ec09f62c6 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -1,10 +1,6 @@
.snippet-form-holder
= form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f|
- - if @snippet.errors.any?
- .alert.alert-danger
- %ul
- - @snippet.errors.full_messages.each do |msg|
- %li= msg
+ = form_errors(@snippet)
.form-group
= f.label :title, class: 'control-label'
diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml
index 7f29918dba3..1de71f37d1a 100644
--- a/app/views/users/calendar.html.haml
+++ b/app/views/users/calendar.html.haml
@@ -7,4 +7,4 @@
'#{user_calendar_activities_path}'
);
-.calendar-hint Summary of issues, merge requests and push events
+.calendar-hint Summary of issues, merge requests, and push events
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 8ffcdc4a327..dc249155b92 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,7 +1,7 @@
.awards.votes-block
- awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes|
- %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}}
- = emoji_icon(emoji)
+ %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), data: {placement: "top", original_title: emoji_author_list(notes, current_user)}}
+ = emoji_icon(emoji, sprite: false)
%span.award-control-text.js-counter
= notes.count
diff --git a/config/application.rb b/config/application.rb
index 5a0ac70aa2a..2e2ed48db07 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -4,11 +4,9 @@ require 'rails/all'
require 'devise'
I18n.config.enforce_available_locales = false
Bundler.require(:default, Rails.env)
-require_relative '../lib/gitlab/redis_config'
+require_relative '../lib/gitlab/redis'
module Gitlab
- REDIS_CACHE_NAMESPACE = 'cache:gitlab'
-
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
@@ -69,8 +67,8 @@ module Gitlab
end
end
- redis_config_hash = Gitlab::RedisConfig.redis_store_options
- redis_config_hash[:namespace] = REDIS_CACHE_NAMESPACE
+ redis_config_hash = Gitlab::Redis.redis_store_options
+ redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 909526605a1..a9d8ac4b6d4 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -21,6 +21,9 @@ Rails.application.configure do
# Generate digests for assets URLs
config.assets.digest = true
+ # Enable compression of compiled assets using gzip.
+ config.assets.compress = true
+
# Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH
diff --git a/config/environments/test.rb b/config/environments/test.rb
index f96ac6f9753..a703c0934f7 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -8,6 +8,7 @@ Rails.application.configure do
config.cache_classes = false
# Configure static asset server for tests with Cache-Control for performance
+ config.assets.digest = false
config.serve_static_files = true
config.static_cache_control = "public, max-age=3600"
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 3e1deb8d306..1b445bbbd10 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -7,6 +7,7 @@ if Gitlab::Metrics.enabled?
# ActiveSupport.
require 'gitlab/metrics/subscribers/action_view'
require 'gitlab/metrics/subscribers/active_record'
+ require 'gitlab/metrics/subscribers/rails_cache'
Gitlab::Application.configure do |config|
config.middleware.use(Gitlab::Metrics::RackMiddleware)
@@ -74,6 +75,29 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
+
+ # Instruments all Banzai filters
+ Dir[Rails.root.join('lib', 'banzai', 'filter', '*.rb')].each do |file|
+ klass = File.basename(file, File.extname(file)).camelize
+ const = Banzai::Filter.const_get(klass)
+
+ config.instrument_methods(const)
+ config.instrument_instance_methods(const)
+ end
+
+ config.instrument_methods(Banzai::ReferenceExtractor)
+ config.instrument_instance_methods(Banzai::ReferenceExtractor)
+
+ config.instrument_methods(Banzai::Renderer)
+ config.instrument_methods(Banzai::Querying)
+
+ [Issuable, Mentionable, Participable].each do |klass|
+ config.instrument_instance_methods(klass)
+ config.instrument_instance_methods(klass::ClassMethods)
+ end
+
+ config.instrument_methods(Gitlab::ReferenceExtractor)
+ config.instrument_instance_methods(Gitlab::ReferenceExtractor)
end
GC::Profiler.enable
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 3da5d46be92..70285255877 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -13,7 +13,7 @@ end
if Rails.env.test?
Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session"
else
- redis_config = Gitlab::RedisConfig.redis_store_options
+ redis_config = Gitlab::Redis.redis_store_options
redis_config[:namespace] = 'session:gitlab'
Gitlab::Application.config.session_store(
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index cc83137745a..9182d929809 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -2,7 +2,7 @@ SIDEKIQ_REDIS_NAMESPACE = 'resque:gitlab'
Sidekiq.configure_server do |config|
config.redis = {
- url: Gitlab::RedisConfig.url,
+ url: Gitlab::Redis.url,
namespace: SIDEKIQ_REDIS_NAMESPACE
}
@@ -29,7 +29,7 @@ end
Sidekiq.configure_client do |config|
config.redis = {
- url: Gitlab::RedisConfig.url,
+ url: Gitlab::Redis.url,
namespace: SIDEKIQ_REDIS_NAMESPACE
}
end
diff --git a/config/mail_room.yml b/config/mail_room.yml
index 60257329f3e..761a32adb9e 100644
--- a/config/mail_room.yml
+++ b/config/mail_room.yml
@@ -2,7 +2,7 @@
<%
require "yaml"
require "json"
-require_relative "lib/gitlab/redis_config"
+require_relative "lib/gitlab/redis"
rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
@@ -18,7 +18,7 @@ if File.exists?(config_file)
config['mailbox'] = "inbox" if config['mailbox'].nil?
if config['enabled'] && config['address']
- redis_url = Gitlab::RedisConfig.new(rails_env).url
+ redis_url = Gitlab::Redis.new(rails_env).url
%>
-
:host: <%= config['host'].to_json %>
diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb
index e028ac82ba3..540e4e68259 100644
--- a/db/fixtures/development/07_milestones.rb
+++ b/db/fixtures/development/07_milestones.rb
@@ -4,7 +4,7 @@ Gitlab::Seeder.quiet do
milestone_params = {
title: "v#{i}.0",
description: FFaker::Lorem.sentence,
- state: ['opened', 'closed'].sample,
+ state: [:active, :closed].sample,
}
milestone = Milestones::CreateService.new(
diff --git a/db/migrate/20130315124931_user_color_scheme.rb b/db/migrate/20130315124931_user_color_scheme.rb
index fe139e32ea7..56c9a31ee3c 100644
--- a/db/migrate/20130315124931_user_color_scheme.rb
+++ b/db/migrate/20130315124931_user_color_scheme.rb
@@ -1,7 +1,9 @@
class UserColorScheme < ActiveRecord::Migration
+ include Gitlab::Database
+
def up
add_column :users, :color_scheme_id, :integer, null: false, default: 1
- User.where(dark_scheme: true).update_all(color_scheme_id: 2)
+ execute("UPDATE users SET color_scheme_id = 2 WHERE dark_scheme = #{true_value}")
remove_column :users, :dark_scheme
end
diff --git a/db/migrate/20130403003950_add_last_activity_column_into_project.rb b/db/migrate/20130403003950_add_last_activity_column_into_project.rb
index 2a036bd9993..85e31608d79 100644
--- a/db/migrate/20130403003950_add_last_activity_column_into_project.rb
+++ b/db/migrate/20130403003950_add_last_activity_column_into_project.rb
@@ -3,14 +3,16 @@ class AddLastActivityColumnIntoProject < ActiveRecord::Migration
add_column :projects, :last_activity_at, :datetime
add_index :projects, :last_activity_at
- Project.find_each do |project|
- last_activity_date = if project.last_activity
- project.last_activity.created_at
- else
- project.updated_at
- end
+ select_all('SELECT id, updated_at FROM projects').each do |project|
+ project_id = project['id']
+ update_date = project['updated_at']
+ event = select_one("SELECT created_at FROM events WHERE project_id = #{project_id} ORDER BY created_at DESC LIMIT 1")
- project.update_attribute(:last_activity_at, last_activity_date)
+ if event && event['created_at']
+ update_date = event['created_at']
+ end
+
+ execute("UPDATE projects SET last_activity_at = '#{update_date}' WHERE id = #{project_id}")
end
end
diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
index cf1e9f912a0..89421cbedad 100644
--- a/db/migrate/20131112220935_add_visibility_level_to_projects.rb
+++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
@@ -1,13 +1,15 @@
class AddVisibilityLevelToProjects < ActiveRecord::Migration
+ include Gitlab::Database
+
def self.up
add_column :projects, :visibility_level, :integer, :default => 0, :null => false
- Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ execute("UPDATE projects SET visibility_level = #{Gitlab::VisibilityLevel::PUBLIC} WHERE public = #{true_value}")
remove_column :projects, :public
end
def self.down
add_column :projects, :public, :boolean, :default => false, :null => false
- Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true)
+ execute("UPDATE projects SET public = #{true_value} WHERE visibility_level = #{Gitlab::VisibilityLevel::PUBLIC}")
remove_column :projects, :visibility_level
end
end
diff --git a/db/migrate/20140313092127_migrate_already_imported_projects.rb b/db/migrate/20140313092127_migrate_already_imported_projects.rb
index f4392c0f05e..0a9f73a5758 100644
--- a/db/migrate/20140313092127_migrate_already_imported_projects.rb
+++ b/db/migrate/20140313092127_migrate_already_imported_projects.rb
@@ -1,12 +1,14 @@
class MigrateAlreadyImportedProjects < ActiveRecord::Migration
+ include Gitlab::Database
+
def up
- Project.where(imported: true).update_all(import_status: "finished")
- Project.where(imported: false).update_all(import_status: "none")
+ execute("UPDATE projects SET import_status = 'finished' WHERE imported = #{true_value}")
+ execute("UPDATE projects SET import_status = 'none' WHERE imported = #{false_value}")
remove_column :projects, :imported
end
def down
add_column :projects, :imported, :boolean, default: false
- Project.where(import_status: 'finished').update_all(imported: true)
+ execute("UPDATE projects SET imported = #{true_value} WHERE import_status = 'finished'")
end
end
diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
index 7f125acb5d1..93826185e8b 100644
--- a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
+++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
@@ -1,9 +1,11 @@
class AddVisibilityLevelToSnippet < ActiveRecord::Migration
+ include Gitlab::Database
+
def up
add_column :snippets, :visibility_level, :integer, :default => 0, :null => false
- Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
- Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::PRIVATE} WHERE private = #{true_value}")
+ execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::INTERNAL} WHERE private = #{false_value}")
add_index :snippets, :visibility_level
@@ -12,10 +14,10 @@ class AddVisibilityLevelToSnippet < ActiveRecord::Migration
def down
add_column :snippets, :private, :boolean, :default => false, :null => false
-
- Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false)
- Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true)
-
+
+ execute("UPDATE snippets SET private = #{false_value} WHERE visibility_level = #{Gitlab::VisibilityLevel::INTERNAL}")
+ execute("UPDATE snippets SET private = #{true_value} WHERE visibility_level = #{Gitlab::VisibilityLevel::PRIVATE}")
+
remove_column :snippets, :visibility_level
end
end
diff --git a/db/schema.rb b/db/schema.rb
index e63e22ce864..ec235c19131 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160331133914) do
+ActiveRecord::Schema.define(version: 20160331223143) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -44,7 +44,6 @@ ActiveRecord::Schema.define(version: 20160331133914) do
t.datetime "updated_at"
t.string "home_page_url"
t.integer "default_branch_protection", default: 2
- t.boolean "twitter_sharing_enabled", default: true
t.text "restricted_visibility_levels"
t.boolean "version_check_enabled", default: true
t.integer "max_attachment_size", default: 10, null: false
diff --git a/doc/api/issues.md b/doc/api/issues.md
index cc6355d34ef..1c635a6cdcf 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -76,8 +76,9 @@ Example response:
"title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
- "labels" : []
- },
+ "labels" : [],
+ "subscribed" : false
+ }
]
```
@@ -152,7 +153,8 @@ Example response:
"id" : 41,
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
- "created_at" : "2016-01-04T15:31:46.176Z"
+ "created_at" : "2016-01-04T15:31:46.176Z",
+ "subscribed" : false
}
]
```
@@ -213,7 +215,8 @@ Example response:
"id" : 41,
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
- "created_at" : "2016-01-04T15:31:46.176Z"
+ "created_at" : "2016-01-04T15:31:46.176Z",
+ "subscribed": false
}
```
@@ -267,7 +270,8 @@ Example response:
},
"description" : null,
"updated_at" : "2016-01-07T12:44:33.959Z",
- "milestone" : null
+ "milestone" : null,
+ "subscribed" : true
}
```
@@ -323,7 +327,8 @@ Example response:
],
"id" : 85,
"assignee" : null,
- "milestone" : null
+ "milestone" : null,
+ "subscribed" : true
}
```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index b20a6300b7a..20db73ea6c0 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -66,7 +66,8 @@ Parameters:
"due_date": null
},
"merge_when_build_succeeds": true,
- "merge_status": "can_be_merged"
+ "merge_status": "can_be_merged",
+ "subscribed" : false
}
]
```
@@ -128,7 +129,8 @@ Parameters:
"due_date": null
},
"merge_when_build_succeeds": true,
- "merge_status": "can_be_merged"
+ "merge_status": "can_be_merged",
+ "subscribed" : true
}
```
@@ -227,6 +229,7 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
+ "subscribed" : true,
"changes": [
{
"old_path": "VERSION",
@@ -304,7 +307,8 @@ Parameters:
"due_date": null
},
"merge_when_build_succeeds": true,
- "merge_status": "can_be_merged"
+ "merge_status": "can_be_merged",
+ "subscribed" : true
}
```
@@ -373,7 +377,8 @@ Parameters:
"due_date": null
},
"merge_when_build_succeeds": true,
- "merge_status": "can_be_merged"
+ "merge_status": "can_be_merged",
+ "subscribed" : true
}
```
@@ -466,7 +471,8 @@ Parameters:
"due_date": null
},
"merge_when_build_succeeds": true,
- "merge_status": "can_be_merged"
+ "merge_status": "can_be_merged",
+ "subscribed" : true
}
```
@@ -530,7 +536,8 @@ Parameters:
"due_date": null
},
"merge_when_build_succeeds": true,
- "merge_status": "can_be_merged"
+ "merge_status": "can_be_merged",
+ "subscribed" : true
}
```
diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md
index 71db5aa5dc8..9553bb11e9d 100644
--- a/doc/ci/build_artifacts/README.md
+++ b/doc/ci/build_artifacts/README.md
@@ -1,7 +1,10 @@
# Introduction to build artifacts
Artifacts is a list of files and directories which are attached to a build
-after it completes successfully.
+after it completes successfully. This feature is enabled by default in all GitLab installations.
+
+_If you are searching for ways to use artifacts, jump to
+[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._
Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by
GitLab Runner are uploaded to GitLab and are downloadable as a single archive
@@ -16,13 +19,9 @@ The artifacts browser will be available only for new artifacts that are sent
to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
browse old artifacts already uploaded to GitLab.
-## Enabling build artifacts
-
-_If you are searching for ways to use artifacts, jump to
-[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._
+## Disabling build artifacts
-The artifacts feature is enabled by default in all GitLab installations.
-To disable it site-wide, follow the steps below.
+To disable artifacts site-wide, follow the steps below.
---
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index 210f9c3e849..d790015aca1 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -57,7 +57,7 @@ before_script:
# WARNING: Use this only with the Docker executor, if you use it with shell
# you will overwrite your user's SSH config.
- mkdir -p ~/.ssh
- - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config`
+ - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
```
As a final step, add the _public_ key from the one you created earlier to the
diff --git a/doc/development/README.md b/doc/development/README.md
index 8940b558fb6..3f3ef068f96 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -2,6 +2,8 @@
- [Architecture](architecture.md) of GitLab
- [CI setup](ci_setup.md) for testing GitLab
+- [Code review guidelines](code_review.md) for reviewing code and having code
+ reviewed.
- [Gotchas](gotchas.md) to avoid
- [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
@@ -10,4 +12,5 @@
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Sidekiq debugging](sidekiq_debugging.md)
- [SQL guidelines](sql.md) for SQL guidelines
+- [Testing standards and style guidelines](testing.md)
- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
new file mode 100644
index 00000000000..40ae55ab905
--- /dev/null
+++ b/doc/development/code_review.md
@@ -0,0 +1,78 @@
+# Code Review Guidelines
+
+This guide contains advice and best practices for performing code review, and
+having your code reviewed.
+
+All merge requests for GitLab CE and EE, whether written by a GitLab team member
+or a volunteer contributor, must go through a code review process to ensure the
+code is effective, understandable, and maintainable.
+
+Any developer can, and is encouraged to, perform code review on merge requests
+of colleagues and contributors. However, the final decision to accept a merge
+request is up to one of our merge request "endbosses", denoted on the
+[team page](https://about.gitlab.com/team).
+
+## Everyone
+
+- Accept that many programming decisions are opinions. Discuss tradeoffs, which
+ you prefer, and reach a resolution quickly.
+- Ask questions; don't make demands. ("What do you think about naming this
+ `:user_id`?")
+- Ask for clarification. ("I didn't understand. Can you clarify?")
+- Avoid selective ownership of code. ("mine", "not mine", "yours")
+- Avoid using terms that could be seen as referring to personal traits. ("dumb",
+ "stupid"). Assume everyone is attractive, intelligent, and well-meaning.
+- Be explicit. Remember people don't always understand your intentions online.
+- Be humble. ("I'm not sure - let's look it up.")
+- Don't use hyperbole. ("always", "never", "endlessly", "nothing")
+- Be careful about the use of sarcasm. Everything we do is public; what seems
+ like good-natured ribbing to you and a long-time colleague might come off as
+ mean and unwelcoming to a person new to the project.
+- Consider one-on-one chats or video calls if there are too many "I didn't
+ understand" or "Alternative solution:" comments. Post a follow-up comment
+ summarizing one-on-one discussion.
+
+## Having your code reviewed
+
+- The first reviewer of your code is _you_. Before you perform that first push
+ of your shiny new branch, read through the entire diff. Does it make sense?
+ Did you include something unrelated to the overall purpose of the changes? Did
+ you forget to remove any debugging code?
+- Be grateful for the reviewer's suggestions. ("Good call. I'll make that
+ change.")
+- Don't take it personally. The review is of the code, not of you.
+- Explain why the code exists. ("It's like that because of these reasons. Would
+ it be more clear if I rename this class/file/method/variable?")
+- Extract unrelated changes and refactorings into future merge requests/issues.
+- Seek to understand the reviewer's perspective.
+- Try to respond to every comment.
+- Push commits based on earlier rounds of feedback as isolated commits to the
+ branch. Do not squash until the branch is ready to merge. Reviewers should be
+ able to read individual updates based on their earlier feedback.
+
+## Reviewing code
+
+Understand why the change is necessary (fixes a bug, improves the user
+experience, refactors the existing code). Then:
+
+- Communicate which ideas you feel strongly about and those you don't.
+- Identify ways to simplify the code while still solving the problem.
+- Offer alternative implementations, but assume the author already considered
+ them. ("What do you think about using a custom validator here?")
+- Seek to understand the author's perspective.
+- If you don't understand a piece of code, _say so_. There's a good chance
+ someone else would be confused by it as well.
+- After a round of line notes, it can be helpful to post a summary note such as
+ "LGTM :thumbsup:", or "Just a couple things to address."
+- Avoid accepting a merge request before the build succeeds ("Merge when build
+ succeeds" is fine).
+
+## Credits
+
+Largely based on the [thoughtbot code review guide].
+
+[thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
+
+---
+
+[Return to Development documentation](README.md)
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index c0192bd6709..c1cf2e77c26 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -2,36 +2,35 @@
GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby
code. This can be used to measure the time spent in a specific part of a larger
-chunk of code. The resulting data is written to a separate series.
+chunk of code. The resulting data is stored as a field in the transaction that
+executed the block.
-To start measuring a block of Ruby code you should use
-`Gitlab::Metrics.measure` and give it a name for the series to store the data
-in:
+To start measuring a block of Ruby code you should use `Gitlab::Metrics.measure`
+and give it a name:
```ruby
-Gitlab::Metrics.measure(:user_logins) do
+Gitlab::Metrics.measure(:foo) do
...
end
```
-The first argument of this method is the series name and should be plural. This
-name will be prefixed with `rails_` or `sidekiq_` depending on whether the code
-was run in the Rails application or one of the Sidekiq workers. In the
-above example the final series names would be as follows:
+3 values are measured for a block:
-- rails_user_logins
-- sidekiq_user_logins
+1. The real time elapsed, stored in NAME_real_time.
+2. The CPU time elapsed, stored in NAME_cpu_time.
+3. The call count, stored in NAME_call_count.
-Series names should be plural as this keeps the naming style in line with the
-other series names.
+Both the real and CPU timings are measured in milliseconds.
-By default metrics measured using a block contain a single value, "duration",
-which contains the number of milliseconds it took to execute the block. Custom
-values can be added by passing a Hash as the 2nd argument. Custom tags can be
-added by passing a Hash as the 3rd argument. A simple example is as follows:
+Multiple calls to the same block will result in the final values being the sum
+of all individual values. Take this code for example:
```ruby
-Gitlab::Metrics.measure(:example_series, { number: 10 }, { class: self.class.to_s }) do
- ...
+3.times do
+ Gitlab::Metrics.measure(:sleep) do
+ sleep 1
+ end
end
```
+
+Here the final value of `sleep_real_time` will be `3`, _not_ `1`.
diff --git a/doc/development/testing.md b/doc/development/testing.md
new file mode 100644
index 00000000000..672e3fb4649
--- /dev/null
+++ b/doc/development/testing.md
@@ -0,0 +1,136 @@
+# Testing Standards and Style Guidelines
+
+This guide outlines standards and best practices for automated testing of GitLab
+CE and EE.
+
+It is meant to be an _extension_ of the [thoughtbot testing
+styleguide](https://github.com/thoughtbot/guides/tree/master/style/testing). If
+this guide defines a rule that contradicts the thoughtbot guide, this guide
+takes precedence. Some guidelines may be repeated verbatim to stress their
+importance.
+
+## Factories
+
+GitLab uses [factory_girl] as a test fixture replacement.
+
+- Factory definitions live in `spec/factories/`, named using the pluralization
+ of their corresponding model (`User` factories are defined in `users.rb`).
+- There should be only one top-level factory definition per file.
+- FactoryGirl methods are mixed in to all RSpec groups. This means you can (and
+ should) call `create(...)` instead of `FactoryGirl.create(...)`.
+- Make use of [traits] to clean up definitions and usages.
+- When defining a factory, don't define attributes that are not required for the
+ resulting record to pass validation.
+- When instantiating from a factory, don't supply attributes that aren't
+ required by the test.
+- Factories don't have to be limited to `ActiveRecord` objects.
+ [See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d).
+
+[factory_girl]: https://github.com/thoughtbot/factory_girl
+[traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits
+
+## JavaScript
+
+GitLab uses [Teaspoon] to run its [Jasmine] JavaScript specs. They can be run on
+the command line via `bundle exec teaspoon`, or via a web browser at
+`http://localhost:3000/teaspoon` when the Rails server is running.
+
+- JavaScript tests live in `spec/javascripts/`, matching the folder structure of
+ `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js.coffee` has a corresponding
+ `spec/javascripts/behaviors/autosize_spec.js.coffee` file.
+- Haml fixtures required for JavaScript tests live in
+ `spec/javascripts/fixtures`. They should contain the bare minimum amount of
+ markup necessary for the test.
+
+ > **Warning:** Keep in mind that a Rails view may change and
+ invalidate your test, but everything will still pass because your fixture
+ doesn't reflect the latest view.
+
+- Keep in mind that in a CI environment, these tests are run in a headless
+ browser and you will not have access to certain APIs, such as
+ [`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification),
+ which will have to be stubbed.
+
+[Teaspoon]: https://github.com/modeset/teaspoon
+[Jasmine]: https://github.com/jasmine/jasmine
+
+## RSpec
+
+### General Guidelines
+
+- Use a single, top-level `describe ClassName` block.
+- Use `described_class` instead of repeating the class name being described.
+- Use `.method` to describe class methods and `#method` to describe instance
+ methods.
+- Use `context` to test branching logic.
+- Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)).
+- Prefer `not_to` to `to_not`.
+- Try to match the ordering of tests to the ordering within the class.
+- Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines
+ to separate phases.
+
+[four-phase-test]: https://robots.thoughtbot.com/four-phase-test
+
+### `let` variables
+
+GitLab's RSpec suite has made extensive use of `let` variables to reduce
+duplication. However, this sometimes [comes at the cost of clarity][lets-not],
+so we need to set some guidelines for their use going forward:
+
+- `let` variables are preferable to instance variables. Local variables are
+ preferable to `let` variables.
+- Use `let` to reduce duplication throughout an entire spec file.
+- Don't use `let` to define variables used by a single test; define them as
+ local variables inside the test's `it` block.
+- Don't define a `let` variable inside the top-level `describe` block that's
+ only used in a more deeply-nested `context` or `describe` block. Keep the
+ definition as close as possible to where it's used.
+- Try to avoid overriding the definition of one `let` variable with another.
+- Don't define a `let` variable that's only used by the definition of another.
+ Use a helper method instead.
+
+[lets-not]: https://robots.thoughtbot.com/lets-not
+
+### Test speed
+
+GitLab has a massive test suite that, without parallelization, can take more
+than an hour to run. It's important that we make an effort to write tests that
+are accurate and effective _as well as_ fast.
+
+Here are some things to keep in mind regarding test performance:
+
+- `double` and `spy` are faster than `FactoryGirl.build(...)`
+- `FactoryGirl.build(...)` and `.build_stubbed` are faster than `.create`.
+- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`,
+ `spy`, or `double` will do. Database persistence is slow!
+- Use `create(:empty_project)` instead of `create(:project)` when you don't need
+ the underlying Git repository. Filesystem operations are slow!
+- Don't mark a feature as requiring JavaScript (through `@javascript` in
+ Spinach or `js: true` in RSpec) unless it's _actually_ required for the test
+ to be valid. Headless browser testing is slow!
+
+### Features / Integration
+
+- Feature specs live in `spec/features/` and should be named
+ `ROLE_ACTION_spec.rb`, such as `user_changes_password_spec.rb`.
+- Use only one `feature` block per feature spec file.
+- Use scenario titles that describe the success and failure paths.
+- Avoid scenario titles that add no information, such as "successfully."
+- Avoid scenario titles that repeat the feature title.
+
+## Spinach (feature) tests
+
+GitLab [moved from Cucumber to Spinach](https://github.com/gitlabhq/gitlabhq/pull/1426)
+for its feature/integration tests in September 2012.
+
+As of March 2016, we are [trying to avoid adding new Spinach
+tests](https://gitlab.com/gitlab-org/gitlab-ce/issues/14121) going forward,
+opting for [RSpec feature](#features-integration) specs.
+
+Adding new Spinach scenarios is acceptable _only if_ the new scenario requires
+no more than one new `step` definition. If more than that is required, the
+test should be re-implemented using RSpec instead.
+
+---
+
+[Return to Development documentation](README.md)
diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md
index 2f01defc11d..a3e260a5f89 100644
--- a/doc/development/ui_guide.md
+++ b/doc/development/ui_guide.md
@@ -1,9 +1,5 @@
# UI Guide for building GitLab
-## Best practices for creating new pages in GitLab
-
-TODO: write some best practices when develop GitLab features.
-
## GitLab UI development kit
We created a page inside GitLab where you can check commonly used html and css elements.
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 7c8f785a61f..6fe04aa2a06 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -19,26 +19,15 @@ See the documentation below for details on how to configure these services.
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
+[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
+
+
## Project services
Integration with services such as Campfire, Flowdock, Gemnasium, HipChat,
Pivotal Tracker, and Slack are available in the form of a [Project Service][].
-You can find these within GitLab in the Services page under Project Settings if
-you are at least a master on the project.
-Project Services are a bit like plugins in that they allow a lot of freedom in
-adding functionality to GitLab. For example there is also a service that can
-send an email every time someone pushes new commits.
-Because GitLab is open source we can ship with the code and tests for all
-plugins. This allows the community to keep the plugins up to date so that they
-always work in newer GitLab versions.
-
-For an overview of what projects services are available without logging in,
-please see the [project_services directory][projects-code].
-
-[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
[Project Service]: ../project_services/project_services.md
-[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services
## SSL certificate errors
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index 3fea2cff0b9..a5af620d9be 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -1,7 +1,24 @@
# Project Services
Project services allow you to integrate GitLab with other applications. Below
-is list of the currently supported ones. Click on the service links to see
+is list of the currently supported ones.
+
+You can find these within GitLab in the Services page under Project Settings if
+you are at least a master on the project.
+Project Services are a bit like plugins in that they allow a lot of freedom in
+adding functionality to GitLab. For example there is also a service that can
+send an email every time someone pushes new commits.
+
+Because GitLab is open source we can ship with the code and tests for all
+plugins. This allows the community to keep the plugins up to date so that they
+always work in newer GitLab versions.
+
+For an overview of what projects services are available without logging in,
+please see the [project_services directory][projects-code].
+
+[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services
+
+Click on the service links to see
further configuration instructions and details. Contributions are welcome.
## Services
diff --git a/features/groups.feature b/features/groups.feature
index 49e939807b5..419a5d3963d 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -7,6 +7,10 @@ Feature: Groups
When I visit group "NonExistentGroup" page
Then page status code should be 404
+ Scenario: I should have back to group button
+ When I visit group "Owned" page
+ Then I should see back to dashboard button
+
@javascript
Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page
diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature
index 10bd6fec803..67f1e117f7f 100644
--- a/features/project/forked_merge_requests.feature
+++ b/features/project/forked_merge_requests.feature
@@ -4,6 +4,7 @@ Feature: Project Forked Merge Requests
And I am a member of project "Shop"
And I have a project forked off of "Shop" called "Forked Shop"
+ @javascript
Scenario: I submit new unassigned merge request to a forked project
Given I visit project "Forked Shop" merge requests page
And I click link "New Merge Request"
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 823658b4f24..ecda4ea8240 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -70,6 +70,7 @@ Feature: Project Merge Requests
When I click link "Reopen"
Then I should see reopened merge request "Bug NS-04"
+ @javascript
Scenario: I submit new unassigned merge request
Given I click link "New Merge Request"
And I submit new merge request "Wiki Feature"
diff --git a/features/project/project.feature b/features/project/project.feature
index aa22401c88e..f1f3ed26065 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -18,6 +18,15 @@ Feature: Project
Then I should see the default project avatar
And I should not see the "Remove avatar" button
+ Scenario: I should have back to group button
+ And project "Shop" belongs to group
+ And I visit project "Shop" page
+ Then I should see back to group button
+
+ Scenario: I should have back to group button
+ And I visit project "Shop" page
+ Then I should see back to dashboard button
+
Scenario: I should have readme on page
And I visit project "Shop" page
Then I should see project "Shop" README
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index b6ce5bc9cec..a167d259837 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -5,9 +5,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
include SharedUser
step 'I click on group milestones' do
- page.within '.nav-secondary' do
- click_link("Milestones")
- end
+ click_link 'Milestones'
end
step 'I should see group milestones index page has no milestones' do
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 483370f41c6..e5b7db4c5e3 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -4,6 +4,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedGroup
include SharedUser
+ step 'I should see back to dashboard button' do
+ expect(page).to have_content 'Go to dashboard'
+ end
+
step 'I should see group "Owned"' do
expect(page).to have_content '@owned'
end
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index 4584fc4d754..19d81453d8c 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -82,9 +82,7 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
# Sub Tabs: Issues
step 'I click the "Milestones" tab' do
- page.within '.nav-secondary' do
- click_link('Milestones')
- end
+ click_link('Milestones')
end
step 'I click the "Labels" tab' do
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index d9b16afa9b8..527f7853da9 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end
step 'I goto the Merge Requests page' do
- page.within '.nav-secondary' do
+ page.within '.page-sidebar-expanded' do
click_link "Merge Requests"
end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 7e4425ff662..612bb8fd8b1 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -34,10 +34,14 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I fill out a "Merge Request On Forked Project" merge request' do
- select @forked_project.path_with_namespace, from: "merge_request_source_project_id"
- select @project.path_with_namespace, from: "merge_request_target_project_id"
- select "fix", from: "merge_request_source_branch"
- select "master", from: "merge_request_target_branch"
+ first('.js-source-project').click
+ first('.dropdown-source-project a', text: @forked_project.path_with_namespace)
+
+ first('.js-target-project').click
+ first('.dropdown-target-project a', text: @project.path_with_namespace)
+
+ first('.js-source-branch').click
+ first('.dropdown-source-branch .dropdown-content a', text: 'fix').click
click_button "Compare branches and continue"
@@ -115,10 +119,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
- expect(find(:select, "merge_request_source_project_id", {}).value).to eq @forked_project.id.to_s
- expect(find(:select, "merge_request_target_project_id", {}).value).to eq @project.id.to_s
- expect(find(:select, "merge_request_source_branch", {}).value).to eq ""
- expect(find(:select, "merge_request_target_branch", {}).value).to eq "master"
+ expect(find_by_id("merge_request_source_project_id", visible: false).value).to eq @forked_project.id.to_s
+ expect(find_by_id("merge_request_target_project_id", visible: false).value).to eq @project.id.to_s
+ expect(find_by_id("merge_request_source_branch", visible: false).value).to eq nil
+ expect(find_by_id("merge_request_target_branch", visible: false).value).to eq "master"
click_button "Compare branches"
end
@@ -127,7 +131,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'the target repository should be the original repository' do
- expect(page).to have_select("merge_request_target_project_id", selected: @project.path_with_namespace)
+ expect(find_by_id("merge_request_target_project_id").value).to eq "#{@project.id}"
end
step 'I click "Assign to" dropdown"' do
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index 2ab8956867b..0ca2d6257c3 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -15,7 +15,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I delete all labels' do
page.within '.labels' do
- page.all('.btn-remove').each do |remove|
+ page.all('.remove-row').each do |remove|
remove.click
sleep 0.05
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index a4f02b590ea..f0af0d097fa 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -93,8 +93,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I submit new merge request "Wiki Feature"' do
- select "fix", from: "merge_request_source_branch"
- select "feature", from: "merge_request_target_branch"
+ find('.js-source-branch').click
+ find('.dropdown-source-branch .dropdown-content a', text: 'fix').click
+
+ find('.js-target-branch').click
+ first('.dropdown-target-branch .dropdown-content a', text: 'feature').click
+
click_button "Compare branches"
fill_in "merge_request_title", with: "Wiki Feature"
click_button "Submit merge request"
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 8f1d4a223a9..ef185861e00 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -114,9 +114,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should not see "Snippets" button' do
- page.within '.nav-secondary' do
- expect(page).not_to have_link 'Snippets'
- end
+ expect(page).not_to have_link 'Snippets'
end
step 'project "Shop" belongs to group' do
@@ -125,6 +123,14 @@ class Spinach::Features::Project < Spinach::FeatureSteps
@project.save!
end
+ step 'I should see back to dashboard button' do
+ expect(page).to have_content 'Go to dashboard'
+ end
+
+ step 'I should see back to group button' do
+ expect(page).to have_content 'Go to group'
+ end
+
step 'I click notifications drop down button' do
click_link 'notifications-button'
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 243469b8e7d..e072505e5d7 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -213,13 +213,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I see Browse file link' do
- expect(page).to have_link 'Browse File »'
- expect(page).not_to have_link 'Browse Files »'
+ expect(page).to have_link 'Browse File'
+ expect(page).not_to have_link 'Browse Files'
end
step 'I see Browse code link' do
- expect(page).to have_link 'Browse Files »'
- expect(page).not_to have_link 'Browse File »'
+ expect(page).to have_link 'Browse Files'
expect(page).not_to have_link 'Browse Directory »'
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 32c3e99f450..1448c3f44cc 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -155,7 +155,7 @@ module SharedDiffNote
step 'I should see a discussion reply button' do
page.within(diff_file_selector) do
- expect(page).to have_button('Reply')
+ expect(page).to have_button('Reply...')
end
end
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index fa7d24ce611..4fc2ece79ff 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -41,7 +41,7 @@ module SharedProjectTab
end
step 'the active main tab should be Settings' do
- page.within '.nav-secondary' do
+ page.within '.nav-sidebar' do
expect(page).to have_content('Go to project')
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 4c49442bf8b..d76b46b8836 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -170,6 +170,10 @@ module API
expose :label_names, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
+
+ expose :subscribed do |issue, options|
+ issue.subscribed?(options[:current_user])
+ end
end
class MergeRequest < ProjectEntity
@@ -183,6 +187,10 @@ module API
expose :milestone, using: Entities::Milestone
expose :merge_when_build_succeeds
expose :merge_status
+
+ expose :subscribed do |merge_request, options|
+ merge_request.subscribed?(options[:current_user])
+ end
end
class MergeRequestChanges < MergeRequest
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 1fee1dee1a6..c4ea05ee6cf 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -55,7 +55,7 @@ module API
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)
- present paginate(issues), with: Entities::Issue
+ present paginate(issues), with: Entities::Issue, current_user: current_user
end
end
@@ -92,7 +92,7 @@ module API
end
issues.reorder(issuable_order_by => issuable_sort)
- present paginate(issues), with: Entities::Issue
+ present paginate(issues), with: Entities::Issue, current_user: current_user
end
# Get a single project issue
@@ -105,7 +105,7 @@ module API
get ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
not_found! unless can?(current_user, :read_issue, @issue)
- present @issue, with: Entities::Issue
+ present @issue, with: Entities::Issue, current_user: current_user
end
# Create a new project issue
@@ -149,7 +149,7 @@ module API
issue.add_labels_by_names(params[:labels].split(','))
end
- present issue, with: Entities::Issue
+ present issue, with: Entities::Issue, current_user: current_user
else
render_validation_error!(issue)
end
@@ -189,7 +189,7 @@ module API
issue.add_labels_by_names(params[:labels].split(','))
end
- present issue, with: Entities::Issue
+ present issue, with: Entities::Issue, current_user: current_user
else
render_validation_error!(issue)
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 93052fba06b..4e7de8867b4 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -56,7 +56,7 @@ module API
end
merge_requests = merge_requests.reorder(issuable_order_by => issuable_sort)
- present paginate(merge_requests), with: Entities::MergeRequest
+ present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user
end
# Create MR
@@ -94,7 +94,7 @@ module API
merge_request.add_labels_by_names(params[:labels].split(","))
end
- present merge_request, with: Entities::MergeRequest
+ present merge_request, with: Entities::MergeRequest, current_user: current_user
else
handle_merge_request_errors! merge_request.errors
end
@@ -130,7 +130,7 @@ module API
authorize! :read_merge_request, merge_request
- present merge_request, with: Entities::MergeRequest
+ present merge_request, with: Entities::MergeRequest, current_user: current_user
end
# Show MR commits
@@ -162,7 +162,7 @@ module API
merge_request = user_project.merge_requests.
find(params[:merge_request_id])
authorize! :read_merge_request, merge_request
- present merge_request, with: Entities::MergeRequestChanges
+ present merge_request, with: Entities::MergeRequestChanges, current_user: current_user
end
# Update MR
@@ -204,7 +204,7 @@ module API
merge_request.add_labels_by_names(params[:labels].split(","))
end
- present merge_request, with: Entities::MergeRequest
+ present merge_request, with: Entities::MergeRequest, current_user: current_user
else
handle_merge_request_errors! merge_request.errors
end
@@ -246,7 +246,7 @@ module API
execute(merge_request)
end
- present merge_request, with: Entities::MergeRequest
+ present merge_request, with: Entities::MergeRequest, current_user: current_user
end
# Cancel Merge if Merge When build succeeds is enabled
@@ -325,7 +325,7 @@ module API
get "#{path}/closes_issues" do
merge_request = user_project.merge_requests.find(params[:merge_request_id])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
- present paginate(issues), with: Entities::Issue
+ present paginate(issues), with: Entities::Issue, current_user: current_user
end
end
end
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index afb6ffa3609..0f3f505fa05 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -103,7 +103,7 @@ module API
authorize! :read_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
- present paginate(@milestone.issues), with: Entities::Issue
+ present paginate(@milestone.issues), with: Entities::Issue, current_user: current_user
end
end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index ae714c87dc5..c14a9c4c722 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -19,8 +19,10 @@ module Banzai
cache_key = full_cache_key(cache_key, context[:pipeline])
if cache_key
- Rails.cache.fetch(cache_key) do
- cacheless_render(text, context)
+ Gitlab::Metrics.measure(:banzai_cached_render) do
+ Rails.cache.fetch(cache_key) do
+ cacheless_render(text, context)
+ end
end
else
cacheless_render(text, context)
@@ -64,13 +66,15 @@ module Banzai
private
def self.cacheless_render(text, context = {})
- result = render_result(text, context)
+ Gitlab::Metrics.measure(:banzai_cacheless_render) do
+ result = render_result(text, context)
- output = result[:output]
- if output.respond_to?(:to_html)
- output.to_html
- else
- output.to_s
+ output = result[:output]
+ if output.respond_to?(:to_html)
+ output.to_html
+ else
+ output.to_s
+ end
end
end
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index c73eca832d7..c2260a5f7ac 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -43,7 +43,9 @@ module Gitlab
# false if the lease is already taken.
def try_obtain
# Performing a single SET is atomic
- !!redis.set(redis_key, '1', nx: true, ex: @timeout)
+ Gitlab::Redis.with do |redis|
+ !!redis.set(redis_key, '1', nx: true, ex: @timeout)
+ end
end
# No #cancel method. See comments above!
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 4a3f47b5a95..2a0a5629be5 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -74,24 +74,32 @@ module Gitlab
#
# Example:
#
- # Gitlab::Metrics.measure(:find_by_username_timings) do
+ # Gitlab::Metrics.measure(:find_by_username_duration) do
# User.find_by_username(some_username)
# end
#
- # series - The name of the series to store the data in.
- # values - A Hash containing extra values to add to the metric.
- # tags - A Hash containing extra tags to add to the metric.
+ # name - The name of the field to store the execution time in.
#
# Returns the value yielded by the supplied block.
- def self.measure(series, values = {}, tags = {})
- return yield unless Transaction.current
+ def self.measure(name)
+ trans = current_transaction
+
+ return yield unless trans
+
+ real_start = Time.now.to_f
+ cpu_start = System.cpu_time
- start = Time.now.to_f
retval = yield
- duration = (Time.now.to_f - start) * 1000.0
- values = values.merge(duration: duration)
- Transaction.current.add_metric(series, values, tags)
+ cpu_stop = System.cpu_time
+ real_stop = Time.now.to_f
+
+ real_time = (real_stop - real_start) * 1000.0
+ cpu_time = cpu_stop - cpu_start
+
+ trans.increment("#{name}_real_time", real_time)
+ trans.increment("#{name}_cpu_time", cpu_time)
+ trans.increment("#{name}_call_count", 1)
retval
end
@@ -107,5 +115,11 @@ module Gitlab
new(udp: { host: host, port: port })
end
end
+
+ private
+
+ def self.current_transaction
+ Transaction.current
+ end
end
end
diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb
index 7ea9555cc8c..1cd1ca30f70 100644
--- a/lib/gitlab/metrics/metric.rb
+++ b/lib/gitlab/metrics/metric.rb
@@ -2,6 +2,8 @@ module Gitlab
module Metrics
# Class for storing details of a single metric (label, value, etc).
class Metric
+ JITTER_RANGE = 0.000001..0.001
+
attr_reader :series, :values, :tags, :created_at
# series - The name of the series (as a String) to store the metric in.
@@ -16,11 +18,29 @@ module Gitlab
# Returns a Hash in a format that can be directly written to InfluxDB.
def to_hash
+ # InfluxDB overwrites an existing point if a new point has the same
+ # series, tag set, and timestamp. In a highly concurrent environment
+ # this means that using the number of seconds since the Unix epoch is
+ # inevitably going to collide with another timestamp. For example, two
+ # Rails requests processed by different processes may end up generating
+ # metrics using the _exact_ same timestamp (in seconds).
+ #
+ # Due to the way InfluxDB is set up there's no solution to this problem,
+ # all we can do is lower the amount of collisions. We do this by using
+ # Time#to_f which returns the seconds as a Float providing greater
+ # accuracy. We then add a small random value that is large enough to
+ # distinguish most timestamps but small enough to not alter the amount
+ # of seconds.
+ #
+ # See https://gitlab.com/gitlab-com/operations/issues/175 for more
+ # information.
+ time = @created_at.to_f + rand(JITTER_RANGE)
+
{
series: @series,
tags: @tags,
values: @values,
- timestamp: @created_at.to_i * 1_000_000_000
+ timestamp: (time * 1_000_000_000).to_i
}
end
end
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
new file mode 100644
index 00000000000..49e5f86e6e6
--- /dev/null
+++ b/lib/gitlab/metrics/subscribers/rails_cache.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module Metrics
+ module Subscribers
+ # Class for tracking the total time spent in Rails cache calls
+ class RailsCache < ActiveSupport::Subscriber
+ attach_to :active_support
+
+ def cache_read(event)
+ increment(:cache_read_duration, event.duration)
+ end
+
+ def cache_write(event)
+ increment(:cache_write_duration, event.duration)
+ end
+
+ def cache_delete(event)
+ increment(:cache_delete_duration, event.duration)
+ end
+
+ def cache_exist?(event)
+ increment(:cache_exists_duration, event.duration)
+ end
+
+ def increment(key, duration)
+ return unless current_transaction
+
+ current_transaction.increment(:cache_duration, duration)
+ current_transaction.increment(key, duration)
+ end
+
+ private
+
+ def current_transaction
+ Transaction.current
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 83371265278..a7d183b2f94 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -30,6 +30,17 @@ module Gitlab
0
end
end
+
+ # THREAD_CPUTIME is not supported on OS X
+ if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
+ def self.cpu_time
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
+ end
+ else
+ def self.cpu_time
+ Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb
new file mode 100644
index 00000000000..319447669dc
--- /dev/null
+++ b/lib/gitlab/redis.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ class Redis
+ CACHE_NAMESPACE = 'cache:gitlab'
+
+ attr_reader :url
+
+ # To be thread-safe we must be careful when writing the class instance
+ # variables @url and @pool. Because @pool depends on @url we need two
+ # mutexes to prevent deadlock.
+ URL_MUTEX = Mutex.new
+ POOL_MUTEX = Mutex.new
+ private_constant :URL_MUTEX, :POOL_MUTEX
+
+ def self.url
+ @url || URL_MUTEX.synchronize { @url = new.url }
+ end
+
+ def self.with
+ if @pool.nil?
+ POOL_MUTEX.synchronize do
+ @pool = ConnectionPool.new { ::Redis.new(url: url) }
+ end
+ end
+ @pool.with { |redis| yield redis }
+ end
+
+ def self.redis_store_options
+ url = new.url
+ redis_config_hash = ::Redis::Store::Factory.extract_host_options_from_uri(url)
+ # Redis::Store does not handle Unix sockets well, so let's do it for them
+ redis_uri = URI.parse(url)
+ if redis_uri.scheme == 'unix'
+ redis_config_hash[:path] = redis_uri.path
+ end
+ redis_config_hash
+ end
+
+ def initialize(rails_env=nil)
+ rails_env ||= Rails.env
+ config_file = File.expand_path('../../../config/resque.yml', __FILE__)
+
+ @url = "redis://localhost:6379"
+ if File.exists?(config_file)
+ @url =YAML.load_file(config_file)[rails_env]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/redis_config.rb b/lib/gitlab/redis_config.rb
deleted file mode 100644
index 4949c6db539..00000000000
--- a/lib/gitlab/redis_config.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module Gitlab
- class RedisConfig
- attr_reader :url
-
- def self.url
- new.url
- end
-
- def self.redis_store_options
- url = new.url
- redis_config_hash = Redis::Store::Factory.extract_host_options_from_uri(url)
- # Redis::Store does not handle Unix sockets well, so let's do it for them
- redis_uri = URI.parse(url)
- if redis_uri.scheme == 'unix'
- redis_config_hash[:path] = redis_uri.path
- end
- redis_config_hash
- end
-
- def initialize(rails_env=nil)
- rails_env ||= Rails.env
- config_file = File.expand_path('../../../config/resque.yml', __FILE__)
-
- @url = "redis://localhost:6379"
- if File.exists?(config_file)
- @url =YAML.load_file(config_file)[rails_env]
- end
- end
- end
-end
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index 51e746ef923..2214f855200 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -4,18 +4,19 @@ namespace :cache do
desc "GitLab | Clear redis cache"
task :clear => :environment do
- redis = Redis.new(url: Gitlab::RedisConfig.url)
- cursor = REDIS_SCAN_START_STOP
- loop do
- cursor, keys = redis.scan(
- cursor,
- match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*",
- count: CLEAR_BATCH_SIZE
- )
-
- redis.del(*keys) if keys.any?
-
- break if cursor == REDIS_SCAN_START_STOP
+ Gitlab::Redis.with do |redis|
+ cursor = REDIS_SCAN_START_STOP
+ loop do
+ cursor, keys = redis.scan(
+ cursor,
+ match: "#{Gitlab::Redis::CACHE_NAMESPACE}*",
+ count: CLEAR_BATCH_SIZE
+ )
+
+ redis.del(*keys) if keys.any?
+
+ break if cursor == REDIS_SCAN_START_STOP
+ end
end
end
end
diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb
new file mode 100644
index 00000000000..39805da9d0b
--- /dev/null
+++ b/spec/features/dashboard_issues_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe "Dashboard Issues filtering", feature: true, js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ context 'filtering by milestone' do
+ before do
+ project.team << [user, :master]
+ login_as(user)
+
+ create(:issue, project: project, author: user, assignee: user)
+ create(:issue, project: project, author: user, assignee: user, milestone: milestone)
+
+ visit_issues
+ end
+
+ it 'should show all issues with no milestone' do
+ show_milestone_dropdown
+
+ click_link 'No Milestone'
+
+ expect(page).to have_selector('.issue', count: 1)
+ end
+
+ it 'should show all issues with any milestone' do
+ show_milestone_dropdown
+
+ click_link 'Any Milestone'
+
+ expect(page).to have_selector('.issue', count: 2)
+ end
+
+ it 'should show all issues with the selected milestone' do
+ show_milestone_dropdown
+
+ page.within '.dropdown-content' do
+ click_link milestone.title
+ end
+
+ expect(page).to have_selector('.issue', count: 1)
+ end
+ end
+
+ def show_milestone_dropdown
+ click_button 'Milestone'
+ expect(page).to have_selector('.dropdown-content', visible: true)
+ end
+
+ def visit_issues
+ visit issues_dashboard_path
+ end
+end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
new file mode 100644
index 00000000000..41af789aae2
--- /dev/null
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -0,0 +1,64 @@
+require 'rails_helper'
+
+describe 'Awards Emoji', feature: true do
+ let!(:project) { create(:project) }
+ let!(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ describe 'Click award emoji from issue#show' do
+ let!(:issue) do
+ create(:issue,
+ author: @user,
+ assignee: @user,
+ project: project)
+ end
+
+ before do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it 'should increment the thumbsdown emoji', js: true do
+ find('[data-emoji="thumbsdown"]').click
+ sleep 2
+ expect(thumbsdown_emoji).to have_text("1")
+ end
+
+ context 'click the thumbsup emoji' do
+
+ it 'should increment the thumbsup emoji', js: true do
+ find('[data-emoji="thumbsup"]').click
+ sleep 2
+ expect(thumbsup_emoji).to have_text("1")
+ end
+
+ it 'should decrement the thumbsdown emoji', js: true do
+ expect(thumbsdown_emoji).to have_text("0")
+ end
+ end
+
+ context 'click the thumbsdown emoji' do
+
+ it 'should increment the thumbsdown emoji', js: true do
+ find('[data-emoji="thumbsdown"]').click
+ sleep 2
+ expect(thumbsdown_emoji).to have_text("1")
+ end
+
+ it 'should decrement the thumbsup emoji', js: true do
+ expect(thumbsup_emoji).to have_text("0")
+ end
+ end
+ end
+
+ def thumbsup_emoji
+ page.all('span.js-counter').first
+ end
+
+ def thumbsdown_emoji
+ page.all('span.js-counter').last
+ end
+end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index fd02d584848..00b60bd0e75 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Create New Merge Request', feature: true, js: false do
+feature 'Create New Merge Request', feature: true, js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
@@ -13,9 +13,12 @@ feature 'Create New Merge Request', feature: true, js: false do
it 'generates a diff for an orphaned branch' do
click_link 'New Merge Request'
- select "orphaned-branch", from: "merge_request_source_branch"
- select "master", from: "merge_request_target_branch"
+
+ first('.js-source-branch').click
+ first('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click
+
click_button "Compare branches"
+ click_link "Changes"
expect(page).to have_content "README.md"
expect(page).to have_content "wm.png"
@@ -23,6 +26,8 @@ feature 'Create New Merge Request', feature: true, js: false do
fill_in "merge_request_title", with: "Orphaned MR test"
click_button "Submit merge request"
+ click_link "Check out branch"
+
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 70d0864783d..5f855ccc701 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -210,7 +210,7 @@ describe 'Comments', feature: true do
is_expected.to have_content('Another comment on line 10')
is_expected.to have_css('.notes_holder')
is_expected.to have_css('.notes_holder .note', count: 1)
- is_expected.to have_button('Reply')
+ is_expected.to have_button('Reply...')
end
end
end
diff --git a/spec/helpers/form_helper_spec.rb b/spec/helpers/form_helper_spec.rb
new file mode 100644
index 00000000000..b20373a96fb
--- /dev/null
+++ b/spec/helpers/form_helper_spec.rb
@@ -0,0 +1,46 @@
+require 'rails_helper'
+
+describe FormHelper do
+ describe 'form_errors' do
+ it 'returns nil when model has no errors' do
+ model = double(errors: [])
+
+ expect(helper.form_errors(model)).to be_nil
+ end
+
+ it 'renders an alert div' do
+ model = double(errors: errors_stub('Error 1'))
+
+ expect(helper.form_errors(model)).
+ to include('<div class="alert alert-danger" id="error_explanation">')
+ end
+
+ it 'contains a summary message' do
+ single_error = double(errors: errors_stub('A'))
+ multi_errors = double(errors: errors_stub('A', 'B', 'C'))
+
+ expect(helper.form_errors(single_error)).
+ to include('<h4>The form contains the following error:')
+ expect(helper.form_errors(multi_errors)).
+ to include('<h4>The form contains the following errors:')
+ end
+
+ it 'renders each message' do
+ model = double(errors: errors_stub('Error 1', 'Error 2', 'Error 3'))
+
+ errors = helper.form_errors(model)
+
+ aggregate_failures do
+ expect(errors).to include('<li>Error 1</li>')
+ expect(errors).to include('<li>Error 2</li>')
+ expect(errors).to include('<li>Error 3</li>')
+ end
+ end
+
+ def errors_stub(*messages)
+ ActiveModel::Errors.new(double).tap do |errors|
+ messages.each { |msg| errors.add(:base, msg) }
+ end
+ end
+ end
+end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 9adcd916ced..13de88e2f21 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -150,13 +150,6 @@ describe GitlabMarkdownHelper do
end
end
- describe 'random_markdown_tip' do
- it 'returns a random Markdown tip' do
- stub_const("#{described_class}::MARKDOWN_TIPS", ['Random tip'])
- expect(random_markdown_tip).to eq 'Random tip'
- end
- end
-
describe '#first_line_in_markdown' do
let(:text) { "@#{user.username}, can you look at this?\nHello world\n"}
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
new file mode 100644
index 00000000000..e01b0b4bd21
--- /dev/null
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Subscribers::RailsCache do
+ let(:transaction) { Gitlab::Metrics::Transaction.new }
+ let(:subscriber) { described_class.new }
+
+ let(:event) { double(:event, duration: 15.2) }
+
+ describe '#cache_read' do
+ it 'increments the cache_read duration' do
+ expect(subscriber).to receive(:increment).
+ with(:cache_read_duration, event.duration)
+
+ subscriber.cache_read(event)
+ end
+ end
+
+ describe '#cache_write' do
+ it 'increments the cache_write duration' do
+ expect(subscriber).to receive(:increment).
+ with(:cache_write_duration, event.duration)
+
+ subscriber.cache_write(event)
+ end
+ end
+
+ describe '#cache_delete' do
+ it 'increments the cache_delete duration' do
+ expect(subscriber).to receive(:increment).
+ with(:cache_delete_duration, event.duration)
+
+ subscriber.cache_delete(event)
+ end
+ end
+
+ describe '#cache_exist?' do
+ it 'increments the cache_exists duration' do
+ expect(subscriber).to receive(:increment).
+ with(:cache_exists_duration, event.duration)
+
+ subscriber.cache_exist?(event)
+ end
+ end
+
+ describe '#increment' do
+ context 'without a transaction' do
+ it 'returns' do
+ expect(transaction).not_to receive(:increment)
+
+ subscriber.increment(:foo, 15.2)
+ end
+ end
+
+ context 'with a transaction' do
+ before do
+ allow(subscriber).to receive(:current_transaction).
+ and_return(transaction)
+ end
+
+ it 'increments the total and specific cache duration' do
+ expect(transaction).to receive(:increment).
+ with(:cache_duration, event.duration)
+
+ expect(transaction).to receive(:increment).
+ with(:cache_delete_duration, event.duration)
+
+ subscriber.increment(:cache_delete_duration, event.duration)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index f8c1d956ca1..d6ae54e25e8 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -26,4 +26,10 @@ describe Gitlab::Metrics::System do
end
end
end
+
+ describe '.cpu_time' do
+ it 'returns a Fixnum' do
+ expect(described_class.cpu_time).to be_an_instance_of(Fixnum)
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 8f63a5f2043..3dee13e27f4 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -74,24 +74,21 @@ describe Gitlab::Metrics do
let(:transaction) { Gitlab::Metrics::Transaction.new }
before do
- allow(Gitlab::Metrics::Transaction).to receive(:current).
+ allow(Gitlab::Metrics).to receive(:current_transaction).
and_return(transaction)
end
it 'adds a metric to the current transaction' do
- expect(transaction).to receive(:add_metric).
- with(:foo, { duration: a_kind_of(Numeric) }, { tag: 'value' })
+ expect(transaction).to receive(:increment).
+ with('foo_real_time', a_kind_of(Numeric))
- Gitlab::Metrics.measure(:foo, {}, tag: 'value') { 10 }
- end
-
- it 'supports adding of custom values' do
- values = { duration: a_kind_of(Numeric), number: 10 }
+ expect(transaction).to receive(:increment).
+ with('foo_cpu_time', a_kind_of(Numeric))
- expect(transaction).to receive(:add_metric).
- with(:foo, values, { tag: 'value' })
+ expect(transaction).to receive(:increment).
+ with('foo_call_count', 1)
- Gitlab::Metrics.measure(:foo, { number: 10 }, tag: 'value') { 10 }
+ Gitlab::Metrics.measure(:foo) { 10 }
end
it 'returns the return value of the block' do