Commit d2971315 authored by Felipe Artur's avatar Felipe Artur

Merge branch 'master' into issue_3359_3

parents 36d48120 bef4294c
...@@ -18,6 +18,7 @@ variables: ...@@ -18,6 +18,7 @@ variables:
SIMPLECOV: "true" SIMPLECOV: "true"
USE_DB: "true" USE_DB: "true"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20"
before_script: before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
...@@ -134,6 +135,11 @@ spinach 9 10: *spinach-knapsack ...@@ -134,6 +135,11 @@ spinach 9 10: *spinach-knapsack
image: "ruby:2.3" image: "ruby:2.3"
only: only:
- master - master
cache:
key: "ruby-23"
paths:
- vendor/apt
- vendor/ruby
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23 .rspec-knapsack-ruby23: &rspec-knapsack-ruby23
<<: *rspec-knapsack <<: *rspec-knapsack
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.10.0(unreleased)
- Add notifications dropdown for groups
v 8.10.0 (unreleased)
- Fix commit builds API, return all builds for all pipelines for given commit. !4849
- Replace Haml with Hamlit to make view rendering faster. !3666
- Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- Display last commit of deleted branch in push events !4699 (winniehell)
- Add Sidekiq queue duration to transaction metrics.
- Let Workhorse serve format-patch diffs
- Make images fit to the size of the viewport !4810
- Fix check for New Branch button on Issue page !4630 (winniehell)
- Fix MR-auto-close text added to description. !4836
- Fix pagination when sorting by columns with lots of ties (like priority)
- Exclude email check from the standard health check
- Fix changing issue state columns in milestone view
- Add notification settings dropdown for groups
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- PipelinesFinder uses git cache data
- Check for conflicts with existing Project's wiki path when creating a new project.
- Remove unused front-end variable -> default_issues_tracker
- Add API endpoint for a group issues !4520 (mahcsig)
- Add Bugzilla integration !4930 (iamtjg)
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
- Add basic system information like memory and disk usage to the admin panel
v 8.9.3 (unreleased)
- MergeRequestDiff reload content use update_columns to avoid multiple YAML de/serializations
- Decreased min width of screen to 1280px for pinned sidebar
- Fix encrypted data backwards compatibility after upgrading attr_encrypted gem
- Update mobile button icons to be more inline with typical UI paradigms
- Fixes missing avatar on system notes. !4954
- Improve performance of obtaining the maximum access of a user in a project
v 8.9.2
- Fix visibility of snippets when searching.
- Fix an information disclosure when requesting access to a group containing private projects.
- Update omniauth-saml to 1.6.0 !4951
v 8.9.1
- Refactor labels documentation. !3347
- Eager load award emoji on notes. !4628
- Fix some CI wording in documentation. !4660
- Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720
- Add documentation for the export & import features. !4732
- Add some docs for Docker Registry configuration. !4738
- Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744
- Display group/project access requesters separately in the admin area. !4798
- Add documentation and examples for configuring cloud storage for registry images. !4812
- Clarifies documentation about artifact expiry. !4831
- Fix the Network graph links. !4832
- Fix MR-auto-close text added to description. !4836
- Add documentation for award emoji now that comments can be awarded with emojis. !4839
- Fix typo in export failure email. !4847
- Fix header vertical centering. !4170
- Fix subsequent SAML sign ins. !4718
- Set button label when picking an option from status dropdown. !4771
- Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775
- Handle external issues in IssueReferenceFilter. !4789
- Support for rendering/redacting multiple documents. !4828
- Update Todos documentation and screenshots to include new functionality. !4840
- Hide nav arrows by default. !4843
- Added bottom padding to label color suggestion link. !4845
- Use jQuery objects in ref dropdown. !4850
- Fix GitLab project import issues related to notes and builds. !4855
- Restrict header logo to 36px so it doesn't overflow. !4861
- Fix unwanted label unassignment. !4863
- Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869
- Restore old behavior around diff notes to outdated discussions. !4870
- Fix merge requests project settings help link anchor. !4873
- Fix 404 when accessing pipelines as guest user on public projects. !4881
- Remove width restriction for logo on sign-in page. !4888
- Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884
- Apply selected value as label. !4886
- Fix temp file being deleted after the request while importing a GitLab project. !4894
- Fix pagination when sorting by columns with lots of ties (like priority)
- Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912
- Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915
- Remove duplicate 'New Page' button on edit wiki page
v 8.9.0
v 8.9.0 (unreleased) v 8.9.0 (unreleased)
- Fix group visibility form layout in application settings
- Fix builds API response not including commit data - Fix builds API response not including commit data
- Fix error when CI job variables key specified but not defined - Fix error when CI job variables key specified but not defined
- Fix pipeline status when there are no builds in pipeline - Fix pipeline status when there are no builds in pipeline
...@@ -15,7 +94,6 @@ v 8.9.0 (unreleased) ...@@ -15,7 +94,6 @@ v 8.9.0 (unreleased)
- Fix endless redirections when accessing user OAuth applications when they are disabled - Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI - Allow enabling wiki page events from Webhook management UI
- Bump rouge to 1.11.0 - Bump rouge to 1.11.0
- Fix MR-auto-close text added to description
- Fix issue with arrow keys not working in search autocomplete dropdown - Fix issue with arrow keys not working in search autocomplete dropdown
- Fix an issue where note polling stopped working if a window was in the - Fix an issue where note polling stopped working if a window was in the
background during a refresh. background during a refresh.
...@@ -39,7 +117,6 @@ v 8.9.0 (unreleased) ...@@ -39,7 +117,6 @@ v 8.9.0 (unreleased)
- Implement a fair usage of shared runners - Implement a fair usage of shared runners
- Remove project notification settings associated with deleted projects - Remove project notification settings associated with deleted projects
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects - Fix 404 page when viewing TODOs that contain milestones or labels in different projects
- Wrap code blocks on Activies and Todos page !4783 (winniehell)
- Add a metric for the number of new Redis connections created by a transaction - Add a metric for the number of new Redis connections created by a transaction
- Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark
- Redesign navigation for project pages - Redesign navigation for project pages
...@@ -100,6 +177,7 @@ v 8.9.0 (unreleased) ...@@ -100,6 +177,7 @@ v 8.9.0 (unreleased)
- Add Application Setting to configure Container Registry token expire delay (default 5min) - Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav - Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment - Use Knapsack only in CI environment
- Updated project creation page to match new UI #2542
- Cache project build count in sidebar nav - Cache project build count in sidebar nav
- Add milestone expire date to the right sidebar - Add milestone expire date to the right sidebar
- Manually mark a issue or merge request as a todo - Manually mark a issue or merge request as a todo
...@@ -155,6 +233,10 @@ v 8.9.0 (unreleased) ...@@ -155,6 +233,10 @@ v 8.9.0 (unreleased)
- Add tooltip to pin/unpin navbar - Add tooltip to pin/unpin navbar
- Add new sub nav style to Wiki and Graphs sub navigation - Add new sub nav style to Wiki and Graphs sub navigation
v 8.8.6
- Fix visibility of snippets when searching.
- Update omniauth-saml to 1.6.0 !4951
v 8.8.5 v 8.8.5
- Import GitHub repositories respecting the API rate limit !4166 - Import GitHub repositories respecting the API rate limit !4166
- Fix todos page throwing errors when you have a project pending deletion !4300 - Fix todos page throwing errors when you have a project pending deletion !4300
...@@ -285,6 +367,10 @@ v 8.8.0 ...@@ -285,6 +367,10 @@ v 8.8.0
- When creating a .gitignore file a dropdown with templates will be provided - When creating a .gitignore file a dropdown with templates will be provided
- Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562 - Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562
v 8.7.8
- Fix visibility of snippets when searching.
- Update omniauth-saml to 1.6.0 !4951
v 8.7.7 v 8.7.7
- Fix import by `Any Git URL` broken if the URL contains a space - Fix import by `Any Git URL` broken if the URL contains a space
- Prevent unauthorized access to other projects build traces - Prevent unauthorized access to other projects build traces
......
...@@ -30,7 +30,7 @@ gem 'omniauth-github', '~> 1.1.1' ...@@ -30,7 +30,7 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0' gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.5.0' gem 'omniauth-saml', '~> 1.6.0'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
...@@ -76,7 +76,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' ...@@ -76,7 +76,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
gem "kaminari", "~> 0.17.0" gem "kaminari", "~> 0.17.0"
# HAML # HAML
gem "haml-rails", '~> 0.9.0' gem 'hamlit', '~> 2.5'
# Files attachments # Files attachments
gem "carrierwave", '~> 0.10.0' gem "carrierwave", '~> 0.10.0'
...@@ -91,6 +91,7 @@ gem 'fog-core', '~> 1.40' ...@@ -91,6 +91,7 @@ gem 'fog-core', '~> 1.40'
gem 'fog-local', '~> 0.3' gem 'fog-local', '~> 0.3'
gem 'fog-google', '~> 0.3' gem 'fog-google', '~> 0.3'
gem 'fog-openstack', '~> 0.1' gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1'
# for aws storage # for aws storage
gem "unf", '~> 0.1.4' gem "unf", '~> 0.1.4'
...@@ -234,7 +235,7 @@ gem 'net-ssh', '~> 3.0.1' ...@@ -234,7 +235,7 @@ gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0' gem 'base32', '~> 0.3.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 0.15' gem 'sentry-raven', '~> 1.1.0'
gem 'premailer-rails', '~> 1.9.0' gem 'premailer-rails', '~> 1.9.0'
...@@ -346,3 +347,6 @@ gem "paranoia", "~> 2.0" ...@@ -346,3 +347,6 @@ gem "paranoia", "~> 2.0"
# Health check # Health check
gem 'health_check', '~> 1.5.1' gem 'health_check', '~> 1.5.1'
# System information
gem 'vmstat', '~> 2.1.0'
...@@ -243,6 +243,11 @@ GEM ...@@ -243,6 +243,11 @@ GEM
fog-core (>= 1.39) fog-core (>= 1.39)
fog-json (>= 1.0) fog-json (>= 1.0)
ipaddress (>= 0.8) ipaddress (>= 0.8)
fog-rackspace (0.1.1)
fog-core (>= 1.35)
fog-json (>= 1.0)
fog-xml (>= 0.1)
ipaddress (>= 0.8)
fog-xml (0.1.2) fog-xml (0.1.2)
fog-core fog-core
nokogiri (~> 1.5, >= 1.5.11) nokogiri (~> 1.5, >= 1.5.11)
...@@ -277,7 +282,7 @@ GEM ...@@ -277,7 +282,7 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.3.1) gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.2.0) gitlab_git (10.2.3)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -320,14 +325,10 @@ GEM ...@@ -320,14 +325,10 @@ GEM
grape-entity (0.4.8) grape-entity (0.4.8)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
haml (4.0.7) hamlit (2.5.0)
temple (~> 0.7.6)
thor
tilt tilt
haml-rails (0.9.0)
actionpack (>= 4.0.1)
activesupport (>= 4.0.1)
haml (>= 4.0.6, < 5.0)
html2haml (>= 1.0.1)
railties (>= 4.0.1)
hashie (3.4.3) hashie (3.4.3)
health_check (1.5.1) health_check (1.5.1)
rails (>= 2.3.0) rails (>= 2.3.0)
...@@ -337,11 +338,6 @@ GEM ...@@ -337,11 +338,6 @@ GEM
html-pipeline (1.11.0) html-pipeline (1.11.0)
activesupport (>= 2) activesupport (>= 2)
nokogiri (~> 1.4) nokogiri (~> 1.4)
html2haml (2.0.0)
erubis (~> 2.7.0)
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
htmlentities (4.3.4) htmlentities (4.3.4)
http_parser.rb (0.5.3) http_parser.rb (0.5.3)
httparty (0.13.7) httparty (0.13.7)
...@@ -468,9 +464,9 @@ GEM ...@@ -468,9 +464,9 @@ GEM
omniauth-oauth2 (1.3.1) omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0) oauth2 (~> 1.0)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-saml (1.5.0) omniauth-saml (1.6.0)
omniauth (~> 1.3) omniauth (~> 1.3)
ruby-saml (~> 1.1, >= 1.1.1) ruby-saml (~> 1.3)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.2.1)
...@@ -631,9 +627,8 @@ GEM ...@@ -631,9 +627,8 @@ GEM
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-progressbar (1.8.1) ruby-progressbar (1.8.1)
ruby-saml (1.1.2) ruby-saml (1.3.0)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby_parser (3.8.2) ruby_parser (3.8.2)
sexp_processor (~> 4.1) sexp_processor (~> 4.1)
rubyntlm (0.5.2) rubyntlm (0.5.2)
...@@ -665,7 +660,7 @@ GEM ...@@ -665,7 +660,7 @@ GEM
activesupport (>= 3.1, < 4.3) activesupport (>= 3.1, < 4.3)
select2-rails (3.5.9.3) select2-rails (3.5.9.3)
thor (~> 0.14) thor (~> 0.14)
sentry-raven (0.15.6) sentry-raven (1.1.0)
faraday (>= 0.7.6) faraday (>= 0.7.6)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.7.0) sexp_processor (4.7.0)
...@@ -733,6 +728,7 @@ GEM ...@@ -733,6 +728,7 @@ GEM
railties (>= 3.2.5, < 6) railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0) teaspoon (>= 1.0.0)
temple (0.7.7)
term-ansicolor (1.3.2) term-ansicolor (1.3.2)
tins (~> 1.0) tins (~> 1.0)
test_after_commit (0.4.2) test_after_commit (0.4.2)
...@@ -789,6 +785,7 @@ GEM ...@@ -789,6 +785,7 @@ GEM
coercible (~> 1.0) coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3) descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9) equalizer (~> 0.0, >= 0.0.9)
vmstat (2.1.0)
warden (1.2.6) warden (1.2.6)
rack (>= 1.0) rack (>= 1.0)
web-console (2.3.0) web-console (2.3.0)
...@@ -866,6 +863,7 @@ DEPENDENCIES ...@@ -866,6 +863,7 @@ DEPENDENCIES
fog-google (~> 0.3) fog-google (~> 0.3)
fog-local (~> 0.3) fog-local (~> 0.3)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.6.1) font-awesome-rails (~> 4.6.1)
foreman foreman
fuubar (~> 2.0.0) fuubar (~> 2.0.0)
...@@ -882,7 +880,7 @@ DEPENDENCIES ...@@ -882,7 +880,7 @@ DEPENDENCIES
gon (~> 6.0.1) gon (~> 6.0.1)
grape (~> 0.13.0) grape (~> 0.13.0)
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
haml-rails (~> 0.9.0) hamlit (~> 2.5)
health_check (~> 1.5.1) health_check (~> 1.5.1)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
...@@ -920,7 +918,7 @@ DEPENDENCIES ...@@ -920,7 +918,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0) omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0) omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.5.0) omniauth-saml (~> 1.6.0)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
...@@ -960,7 +958,7 @@ DEPENDENCIES ...@@ -960,7 +958,7 @@ DEPENDENCIES
sdoc (~> 0.3.20) sdoc (~> 0.3.20)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
sentry-raven (~> 0.15) sentry-raven (~> 1.1.0)
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack sham_rack
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
...@@ -993,6 +991,7 @@ DEPENDENCIES ...@@ -993,6 +991,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.2) unicorn-worker-killer (~> 0.4.2)
version_sorter (~> 2.0.0) version_sorter (~> 2.0.0)
virtus (~> 1.0.1) virtus (~> 1.0.1)
vmstat (~> 2.1.0)
web-console (~> 2.0) web-console (~> 2.0)
webmock (~> 1.21.0) webmock (~> 1.21.0)
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
......
8.9.0-pre 8.10.0-pre
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
#= require_directory ./ci #= require_directory ./ci
#= require_directory ./commit #= require_directory ./commit
#= require_directory ./extensions #= require_directory ./extensions
#= require_directory ./lib #= require_directory ./lib/utils
#= require_directory ./u2f #= require_directory ./u2f
#= require_directory . #= require_directory .
#= require fuzzaldrin-plus #= require fuzzaldrin-plus
...@@ -199,7 +199,6 @@ $ -> ...@@ -199,7 +199,6 @@ $ ->
$('.header-content .header-logo').toggle() $('.header-content .header-logo').toggle()
$('.header-content .navbar-collapse').toggle() $('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active') $('.navbar-toggle').toggleClass('active')
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff # Show/hide comments on diff
$body.on "click", ".js-toggle-diff-comments", (e) -> $body.on "click", ".js-toggle-diff-comments", (e) ->
...@@ -261,7 +260,7 @@ $ -> ...@@ -261,7 +260,7 @@ $ ->
new Aside() new Aside()
# Sidenav pinning # Sidenav pinning
if $window.width() < 1440 and $.cookie('pin_nav') is 'true' if $window.width() < 1280 and $.cookie('pin_nav') is 'true'
$.cookie('pin_nav', 'false', { path: '/' }) $.cookie('pin_nav', 'false', { path: '/' })
$('.page-with-sidebar') $('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded') .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
......
...@@ -341,7 +341,9 @@ class @AwardsHandler ...@@ -341,7 +341,9 @@ class @AwardsHandler
for emoji in frequentlyUsedEmojis for emoji in frequentlyUsedEmojis
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul) $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
$('input.emoji-search').after(ul).after($('<h5>').text('Frequently used')) $('.emoji-menu-content')
.prepend(ul)
.prepend($('<h5>').text('Frequently used'))
@frequentEmojiBlockRendered = true @frequentEmojiBlockRendered = true
...@@ -356,7 +358,7 @@ class @AwardsHandler ...@@ -356,7 +358,7 @@ class @AwardsHandler
if term if term
# Generate a search result block # Generate a search result block
h5 = $('<h5>').text('Search results').addClass('emoji-search') h5 = $('<h5>').text('Search results')
found_emojis = @searchEmojis(term).show() found_emojis = @searchEmojis(term).show()
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis) ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis)
$('.emoji-menu-content ul, .emoji-menu-content h5').hide() $('.emoji-menu-content ul, .emoji-menu-content h5').hide()
......
...@@ -19,6 +19,7 @@ class @TemplateSelector ...@@ -19,6 +19,7 @@ class @TemplateSelector
data: @data, data: @data,
filterable: true, filterable: true,
selectable: true, selectable: true,
toggleLabel: @toggleLabel,
search: search:
fields: ['name'] fields: ['name']
clicked: @onClick clicked: @onClick
...@@ -31,6 +32,9 @@ class @TemplateSelector ...@@ -31,6 +32,9 @@ class @TemplateSelector
@onFilenameUpdate() @onFilenameUpdate()
) )
toggleLabel: (item) ->
item.name
onFilenameUpdate: -> onFilenameUpdate: ->
return unless @$input.length return unless @$input.length
......
...@@ -186,6 +186,8 @@ class GitLabDropdown ...@@ -186,6 +186,8 @@ class GitLabDropdown
@fullData = data @fullData = data
@parseData @fullData @parseData @fullData
@filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input
} }
# Init filterable # Init filterable
...@@ -280,7 +282,7 @@ class GitLabDropdown ...@@ -280,7 +282,7 @@ class GitLabDropdown
html = @renderData(data) html = @renderData(data)
# Render the full menu # Render the full menu
full_html = @renderMenu(html.join("")) full_html = @renderMenu(html)
@appendMenu(full_html) @appendMenu(full_html)
...@@ -351,7 +353,8 @@ class GitLabDropdown ...@@ -351,7 +353,8 @@ class GitLabDropdown
if @options.renderMenu if @options.renderMenu
menu_html = @options.renderMenu(html) menu_html = @options.renderMenu(html)
else else
menu_html = "<ul>#{html}</ul>" menu_html = $('<ul />')
.append(html)
return menu_html return menu_html
...@@ -360,7 +363,9 @@ class GitLabDropdown ...@@ -360,7 +363,9 @@ class GitLabDropdown
selector = '.dropdown-content' selector = '.dropdown-content'
if @dropdown.find(".dropdown-toggle-page").length if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content" selector = ".dropdown-page-one .dropdown-content"
$(selector, @dropdown).html html $(selector, @dropdown)
.empty()
.append(html)
# Render the row # Render the row
renderItem: (data, group = false, index = false) -> renderItem: (data, group = false, index = false) ->
...@@ -459,7 +464,7 @@ class GitLabDropdown ...@@ -459,7 +464,7 @@ class GitLabDropdown
# Toggle the dropdown label # Toggle the dropdown label
if @options.toggleLabel if @options.toggleLabel
@updateLabel() @updateLabel(selectedObject, el, @)
else else
selectedObject selectedObject
else if el.hasClass(INDETERMINATE_CLASS) else if el.hasClass(INDETERMINATE_CLASS)
...@@ -486,7 +491,7 @@ class GitLabDropdown ...@@ -486,7 +491,7 @@ class GitLabDropdown
# Toggle the dropdown label # Toggle the dropdown label
if @options.toggleLabel if @options.toggleLabel
@updateLabel(selectedObject, el) @updateLabel(selectedObject, el, @)
if value? if value?
if !field.length and fieldName if !field.length and fieldName
@addInput(fieldName, value) @addInput(fieldName, value)
...@@ -585,8 +590,8 @@ class GitLabDropdown ...@@ -585,8 +590,8 @@ class GitLabDropdown
# Scroll the dropdown content up # Scroll the dropdown content up
$dropdownContent.scrollTop(listItemTop - dropdownContentTop) $dropdownContent.scrollTop(listItemTop - dropdownContentTop)
updateLabel: (selected = null, el = null) => updateLabel: (selected = null, el = null, instance = null) =>
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el) $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance)
$.fn.glDropdown = (opts) -> $.fn.glDropdown = (opts) ->
return @.each -> return @.each ->
......
...@@ -4,5 +4,4 @@ ...@@ -4,5 +4,4 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file. # the compiled file.
# #
#= require Chart
#= require_tree . #= require_tree .
...@@ -59,21 +59,23 @@ issuable_created = false ...@@ -59,21 +59,23 @@ issuable_created = false
filterResults: (form) => filterResults: (form) =>
formData = form.serialize() formData = form.serialize()
$('.issues-holder, .merge-requests-holder').css('opacity', '0.5')
formAction = form.attr('action') formAction = form.attr('action')
issuesUrl = formAction issuesUrl = formAction
issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}") issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
issuesUrl += formData issuesUrl += formData
Turbolinks.visit(issuesUrl); Turbolinks.visit(issuesUrl)
initChecks: -> initChecks: ->
@issuableBulkActions = $('.bulk-update').data('bulkActions')
$('.check_all_issues').off('click').on('click', -> $('.check_all_issues').off('click').on('click', ->
$('.selected_issue').prop('checked', @checked) $('.selected_issue').prop('checked', @checked)
Issuable.checkChanged() Issuable.checkChanged()
) )
$('.selected_issue').off('change').on('change', Issuable.checkChanged) $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(@))
checkChanged: -> checkChanged: ->
checked_issues = $('.selected_issue:checked') checked_issues = $('.selected_issue:checked')
...@@ -88,3 +90,6 @@ issuable_created = false ...@@ -88,3 +90,6 @@ issuable_created = false
$('#update_issues_ids').val [] $('#update_issues_ids').val []
$('.issues_bulk_update').hide() $('.issues_bulk_update').hide()
$('.issues-other-filters').show() $('.issues-other-filters').show()
@issuableBulkActions.willUpdateLabels = false
return true
...@@ -99,7 +99,7 @@ class @Issue ...@@ -99,7 +99,7 @@ class @Issue
# If the user doesn't have the required permissions the container isn't # If the user doesn't have the required permissions the container isn't
# rendered at all. # rendered at all.
return unless $container return if $container.length is 0
$.getJSON($container.data('path')) $.getJSON($container.data('path'))
.error -> .error ->
......
...@@ -6,6 +6,13 @@ class @IssueStatusSelect ...@@ -6,6 +6,13 @@ class @IssueStatusSelect
$(el).glDropdown( $(el).glDropdown(
selectable: true selectable: true
fieldName: fieldName fieldName: fieldName
toggleLabel: (selected, el, instance) =>
label = 'Author'
$item = instance.dropdown.find('.is-active')
label = $item.text() if $item.length
label
clicked: (item, $el, e)->
e.preventDefault()
id: (obj, el) -> id: (obj, el) ->
$(el).data("id") $(el).data("id")
) )
...@@ -7,6 +7,11 @@ class @IssuableBulkActions ...@@ -7,6 +7,11 @@ class @IssuableBulkActions
@issues = @getElement('.issues-list .issue') @issues = @getElement('.issues-list .issue')
} = opts } = opts
# Save instance
@form.data 'bulkActions', @
@willUpdateLabels = false
@bindEvents() @bindEvents()
# Fixes bulk-assign not working when navigating through pages # Fixes bulk-assign not working when navigating through pages
...@@ -87,11 +92,12 @@ class @IssuableBulkActions ...@@ -87,11 +92,12 @@ class @IssuableBulkActions
add_label_ids : [] add_label_ids : []
remove_label_ids : [] remove_label_ids : []
@getLabelsToApply().map (id) -> if @willUpdateLabels
formData.update.add_label_ids.push id @getLabelsToApply().map (id) ->
formData.update.add_label_ids.push id
@getLabelsToRemove().map (id) -> @getLabelsToRemove().map (id) ->
formData.update.remove_label_ids.push id formData.update.remove_label_ids.push id
formData formData
......
...@@ -319,6 +319,8 @@ class @LabelsSelect ...@@ -319,6 +319,8 @@ class @LabelsSelect
multiSelect: $dropdown.hasClass 'js-multiselect' multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) -> clicked: (label) ->
_this.enableBulkLabelDropdown()
if $dropdown.hasClass('js-filter-bulk-update') if $dropdown.hasClass('js-filter-bulk-update')
return return
...@@ -377,3 +379,8 @@ class @LabelsSelect ...@@ -377,3 +379,8 @@ class @LabelsSelect
label_ids.push $("#issue_#{issue_id}").data('labels') label_ids.push $("#issue_#{issue_id}").data('labels')
_.intersection.apply _, label_ids _.intersection.apply _, label_ids
enableBulkLabelDropdown: ->
if $('.selected_issue:checked').length
issuableBulkActions = $('.bulk-update').data('bulkActions')
issuableBulkActions.willUpdateLabels = true
...@@ -3,11 +3,10 @@ hideEndFade = ($scrollingTabs) -> ...@@ -3,11 +3,10 @@ hideEndFade = ($scrollingTabs) ->
$this = $(@) $this = $(@)
$this $this
.find('.fade-right') .siblings('.fade-right')
.toggleClass('end-scroll', $this.width() is $this.prop('scrollWidth')) .toggleClass('scrolling', $this.width() < $this.prop('scrollWidth'))
$ -> $ ->
$('.fade-left').addClass('end-scroll')
hideEndFade($('.scrolling-tabs')) hideEndFade($('.scrolling-tabs'))
...@@ -21,5 +20,5 @@ $ -> ...@@ -21,5 +20,5 @@ $ ->
currentPosition = $this.scrollLeft() currentPosition = $this.scrollLeft()
maxPosition = $this.prop('scrollWidth') - $this.outerWidth() maxPosition = $this.prop('scrollWidth') - $this.outerWidth()
$this.find('.fade-left').toggleClass('end-scroll', currentPosition is 0) $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0)
$this.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition) $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1)
#= require raphael
#= require g.raphael
#= require g.bar
...@@ -10,17 +10,41 @@ ...@@ -10,17 +10,41 @@
gl.text.selectedText = (text, textarea) -> gl.text.selectedText = (text, textarea) ->
text.substring(textarea.selectionStart, textarea.selectionEnd) text.substring(textarea.selectionStart, textarea.selectionEnd)
gl.text.insertText = (textArea, text, tag, selected, wrap) -> gl.text.lineBefore = (text, textarea) ->
split = text.substring(0, textarea.selectionStart).trim().split('\n')
split[split.length - 1]
gl.text.lineAfter = (text, textarea) ->
text.substring(textarea.selectionEnd).trim().split('\n')[0]
gl.text.blockTagText = (text, textArea, blockTag, selected) ->
lineBefore = @lineBefore(text, textArea)
lineAfter = @lineAfter(text, textArea)
if lineBefore is blockTag and lineAfter is blockTag
# To remove the block tag we have to select the line before & after
if blockTag?
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1)
textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1)
selected
else
"#{blockTag}\n#{selected}\n#{blockTag}"
gl.text.insertText = (textArea, text, tag, blockTag, selected, wrap) ->
selectedSplit = selected.split('\n') selectedSplit = selected.split('\n')
startChar = if not wrap and textArea.selectionStart > 0 then '\n' else '' startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
if selectedSplit.length > 1 and not wrap if selectedSplit.length > 1 and (not wrap or blockTag?)
insertText = selectedSplit.map((val) -> if blockTag?
if val.indexOf(tag) is 0 insertText = @blockTagText(text, textArea, blockTag, selected)
"#{val.replace(tag, '')}" else
else insertText = selectedSplit.map((val) ->
"#{tag}#{val}" if val.indexOf(tag) is 0
).join('\n') "#{val.replace(tag, '')}"
else
"#{tag}#{val}"
).join('\n')
else else
insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}" insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
...@@ -51,7 +75,7 @@ ...@@ -51,7 +75,7 @@
textArea.setSelectionRange pos, pos textArea.setSelectionRange pos, pos
gl.text.updateText = (textArea, tag, wrap) -> gl.text.updateText = (textArea, tag, blockTag, wrap) ->
$textArea = $(textArea) $textArea = $(textArea)
oldVal = $textArea.val() oldVal = $textArea.val()
textArea = $textArea.get(0) textArea = $textArea.get(0)
...@@ -59,7 +83,7 @@ ...@@ -59,7 +83,7 @@
selected = @selectedText(text, textArea) selected = @selectedText(text, textArea)
$textArea.focus() $textArea.focus()
@insertText(textArea, text, tag, selected, wrap) @insertText(textArea, text, tag, blockTag, selected, wrap)
gl.text.init = (form) -> gl.text.init = (form) ->
self = @ self = @
...@@ -70,6 +94,7 @@ ...@@ -70,6 +94,7 @@
self.updateText( self.updateText(
$this.closest('.md-area').find('textarea'), $this.closest('.md-area').find('textarea'),
$this.data('md-tag'), $this.data('md-tag'),
$this.data('md-block'),
not $this.data('md-prepend') not $this.data('md-prepend')
) )
......
...@@ -4,18 +4,10 @@ class @Milestone ...@@ -4,18 +4,10 @@ class @Milestone
type: "PUT" type: "PUT"
url: issue_url url: issue_url
data: data data: data
success: (data) -> success: (_data) =>
if data.saved == true @successCallback(_data, li)
if data.assignee_avatar_url error: (data) ->
img_tag = $('<img/>') new Flash("Issue update failed", 'alert')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
else
new Flash("Issue update failed", 'alert')
dataType: "json" dataType: "json"
@sortIssues: (data) -> @sortIssues: (data) ->
...@@ -25,9 +17,10 @@ class @Milestone ...@@ -25,9 +17,10 @@ class @Milestone
type: "PUT" type: "PUT"
url: sort_issues_url url: sort_issues_url
data: data data: data
success: (data) -> success: (_data) =>
if data.saved != true @successCallback(_data)
new Flash("Issues update failed", 'alert') error: ->
new Flash("Issues update failed", 'alert')
dataType: "json" dataType: "json"
@sortMergeRequests: (data) -> @sortMergeRequests: (data) ->
...@@ -37,9 +30,10 @@ class @Milestone ...@@ -37,9 +30,10 @@ class @Milestone
type: "PUT" type: "PUT"
url: sort_mr_url url: sort_mr_url
data: data data: data
success: (data) -> success: (_data) =>
if data.saved != true @successCallback(_data)
new Flash("MR update failed", 'alert') error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json" dataType: "json"
@updateMergeRequest: (li, merge_request_url, data) -> @updateMergeRequest: (li, merge_request_url, data) ->
...@@ -47,20 +41,23 @@ class @Milestone ...@@ -47,20 +41,23 @@ class @Milestone
type: "PUT" type: "PUT"
url: merge_request_url url: merge_request_url
data: data data: data
success: (data) -> success: (_data) =>
if data.saved == true @successCallback(_data, li)
if data.assignee_avatar_url error: (data) ->
img_tag = $('<img/>') new Flash("Issue update failed", 'alert')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
else
new Flash("Issue update failed", 'alert')
dataType: "json" dataType: "json"
@successCallback: (data, element) =>
if data.assignee
img_tag = $('<img/>')
img_tag.attr('src', data.assignee.avatar_url)
img_tag.addClass('avatar s16')
$(element).find('.assignee-icon').html(img_tag)
else
$(element).find('.assignee-icon').html('')
$(element).effect 'highlight'
constructor: -> constructor: ->
oldMouseStart = $.ui.sortable.prototype._mouseStart oldMouseStart = $.ui.sortable.prototype._mouseStart
$.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) -> $.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) ->
...@@ -81,8 +78,10 @@ class @Milestone ...@@ -81,8 +78,10 @@ class @Milestone
stop: (event, ui) -> stop: (event, ui) ->
$(".issues-sortable-list").css "min-height", "0px" $(".issues-sortable-list").css "min-height", "0px"
update: (event, ui) -> update: (event, ui) ->
data = $(this).sortable("serialize") # Prevents sorting from container which element has been removed.
Milestone.sortIssues(data) if $(this).find(ui.item).length > 0
data = $(this).sortable("serialize")
Milestone.sortIssues(data)
receive: (event, ui) -> receive: (event, ui) ->
new_state = $(this).data('state') new_state = $(this).data('state')
......
...@@ -4,9 +4,6 @@ ...@@ -4,9 +4,6 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file. # the compiled file.
# #
#= require raphael
#= require g.raphael
#= require g.bar
#= require_tree . #= require_tree .
$ -> $ ->
......
...@@ -70,17 +70,20 @@ class @Project ...@@ -70,17 +70,20 @@ class @Project
fieldName: 'ref' fieldName: 'ref'
renderRow: (ref) -> renderRow: (ref) ->
if ref.header? if ref.header?
"<li class='dropdown-header'>#{ref.header}</li>" $('<li />')
.addClass('dropdown-header')
.text(ref.header)
else else
isActiveClass = if ref is selected then 'is-active' else '' link = $('<a />')
.attr('href', '#')
"<li> .addClass(if ref is selected then 'is-active' else '')
<a href='#' data-ref='#{escape(ref)}' class='#{isActiveClass}'> .text(ref)
#{ref} .attr('data-ref', escape(ref))
</a>
</li>" $('<li />')
.append(link)
id: (obj, $el) -> id: (obj, $el) ->
$el.data('ref') $el.attr('data-ref')
toggleLabel: (obj, $el) -> toggleLabel: (obj, $el) ->
$el.text().trim() $el.text().trim()
clicked: (e) -> clicked: (e) ->
......
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
# #
#= require d3
#= require_tree . #= require_tree .
...@@ -26,7 +26,6 @@ header { ...@@ -26,7 +26,6 @@ header {
text-align: center; text-align: center;
#tanuki-logo, img { #tanuki-logo, img {
width: 36px;
height: 36px; height: 36px;
} }
} }
...@@ -132,6 +131,10 @@ header { ...@@ -132,6 +131,10 @@ header {
transition-duration: .3s; transition-duration: .3s;
z-index: 999; z-index: 999;
svg, img {
height: 36px;
}
&:hover { &:hover {
cursor: pointer; cursor: pointer;
} }
......
@mixin fade($gradient-direction, $rgba, $gradient-color) { @mixin fade($gradient-direction, $rgba, $gradient-color) {
visibility: visible; visibility: hidden;
opacity: 1; opacity: 0;
z-index: 2; z-index: 2;
position: absolute; position: absolute;
bottom: 12px; bottom: 12px;
...@@ -13,17 +13,16 @@ ...@@ -13,17 +13,16 @@
background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%); background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%); background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
&.end-scroll { &.scrolling {
visibility: hidden; visibility: visible;
opacity: 0; opacity: 1;
transition-duration: .3s; transition-duration: .3s;
} }
.fa { .fa {
position: relative; position: relative;
top: 3px; top: 5px;
font-size: 13px; font-size: 18px;
color: $btn-placeholder-gray;
} }
} }
...@@ -32,6 +31,7 @@ ...@@ -32,6 +31,7 @@
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
...@@ -272,7 +272,7 @@ ...@@ -272,7 +272,7 @@
float: right; float: right;
padding: 7px 0 0; padding: 7px 0 0;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-sm-max) {
display: none; display: none;
} }
...@@ -303,41 +303,9 @@ ...@@ -303,41 +303,9 @@
} }
.nav-links { .nav-links {
@include scrolling-links();
border-bottom: none; border-bottom: none;
height: 51px; height: 51px;
svg {
position: relative;
top: 2px;
margin-right: 2px;
height: 15px;
width: auto;
path,
polygon {
fill: $layout-link-gray;
}
}
.fade-right {
@include fade(left, rgba(250, 250, 250, 0.4), $background-color);
right: 0;
.fa {
right: -7px;
}
}
.fade-left {
@include fade(right, rgba(250, 250, 250, 0.4), $background-color);
left: 0;
.fa {
left: -7px;
}
}
li { li {
a { a {
...@@ -373,18 +341,6 @@ ...@@ -373,18 +341,6 @@
} }
} }
} }
.nav-control {
.fade-right {
@media (min-width: $screen-xs-max) {
right: 68px;
}
@media (max-width: $screen-xs-min) {
right: 0;
}
}
}
} }
.scrolling-tabs-container { .scrolling-tabs-container {
...@@ -392,15 +348,42 @@ ...@@ -392,15 +348,42 @@
.nav-links { .nav-links {
@include scrolling-links(); @include scrolling-links();
}
.fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $background-color);
right: -5px;
.fa {
right: -7px;
}
}
.fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $background-color);
left: -5px;
.fa {
left: -7px;
}
}
&.sub-nav-scroll {
.fade-right { .fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $background-color);
right: 0; right: 0;
.fa {
right: -23px;
}
} }
.fade-left { .fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $background-color);
left: 0; left: 0;
.fa {
left: 10px;
}
} }
} }
} }
...@@ -413,21 +396,19 @@ ...@@ -413,21 +396,19 @@
.fade-right { .fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $white-light); @include fade(left, rgba(255, 255, 255, 0.4), $white-light);
right: 0; right: -5px;
.fa {
right: -7px;
}
} }
.fade-left { .fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $white-light); @include fade(right, rgba(255, 255, 255, 0.4), $white-light);
left: 0; left: -5px;
}
&.event-filter {
.fade-right {
visibility: hidden;
@media (max-width: $screen-xs-max) { .fa {
visibility: visible; left: -7px;
}
} }
} }
} }
......
.page-with-sidebar { .page-with-sidebar {
padding-top: $header-height; padding-top: $header-height;
padding-bottom: 25px;
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
.sidebar-wrapper { .sidebar-wrapper {
......
...@@ -7,7 +7,7 @@ $gutter_collapsed_width: 62px; ...@@ -7,7 +7,7 @@ $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 258px; $gutter_inner_width: 258px;
$sidebar-transition-duration: .15s; $sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1440px; $sidebar-breakpoint: 1280px;
/* /*
* UI elements * UI elements
......
...@@ -8,8 +8,9 @@ ...@@ -8,8 +8,9 @@
.emoji-menu { .emoji-menu {
position: absolute; position: absolute;
margin-top: 3px; margin-top: 3px;
z-index: 1000; padding: $gl-padding;
min-width: 160px; z-index: 9;
width: 300px;
font-size: 14px; font-size: 14px;
background-color: $award-emoji-menu-bg; background-color: $award-emoji-menu-bg;
border: 1px solid $award-emoji-menu-border; border: 1px solid $award-emoji-menu-border;
...@@ -33,20 +34,18 @@ ...@@ -33,20 +34,18 @@
} }
.emoji-menu-content { .emoji-menu-content {
padding: $gl-padding;
width: 300px;
height: 300px; height: 300px;
overflow-y: scroll; overflow-y: scroll;
input.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right 5px center;
background-size: 16px;
}
} }
} }
.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right 5px center;
background-size: 16px;
}
.emoji-menu-list { .emoji-menu-list {
list-style: none; list-style: none;
padding-left: 0; padding-left: 0;
......
...@@ -63,5 +63,6 @@ ...@@ -63,5 +63,6 @@
border: 1px solid $table-border-gray; border: 1px solid $table-border-gray;
padding: 5px; padding: 5px;
margin: 5px; margin: 5px;
max-height: calc(100vh - 100px);
} }
} }
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
border: 1px solid $table-border-gray; border: 1px solid $table-border-gray;
padding: 5px; padding: 5px;
margin: 5px; margin: 5px;
max-height: calc(100vh - 100px);
} }
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
height: 30px; height: 30px;
display: inline-block; display: inline-block;
margin-right: 10px; margin-right: 10px;
margin-bottom: 10px;
} }
&.suggest-colors-dropdown { &.suggest-colors-dropdown {
......
...@@ -264,8 +264,15 @@ ...@@ -264,8 +264,15 @@
margin-bottom: 4px; margin-bottom: 4px;
} }
.item-title {
@media (min-width: $screen-sm-min) {
width: 49%;
}
}
.avatar { .avatar {
margin-left: 0; left: 0;
top: 2px;
} }
.commit-row-info { .commit-row-info {
......
...@@ -41,6 +41,10 @@ ul.notes { ...@@ -41,6 +41,10 @@ ul.notes {
.timeline-icon { .timeline-icon {
.avatar { .avatar {
visibility: hidden; visibility: hidden;
.discussion-body & {
visibility: visible;
}
} }
} }
} }
...@@ -113,6 +117,7 @@ ul.notes { ...@@ -113,6 +117,7 @@ ul.notes {
border: 1px solid $table-border-gray; border: 1px solid $table-border-gray;
padding: 5px; padding: 5px;
margin: 5px 0; margin: 5px 0;
max-height: calc(100vh - 100px);
} }
} }
} }
......
...@@ -13,10 +13,53 @@ ...@@ -13,10 +13,53 @@
.new_project, .new_project,
.edit-project { .edit-project {
fieldset.features { fieldset {
.control-label { &.features .control-label {
font-weight: normal; font-weight: normal;
} }
.form-group {
margin-bottom: 5px;
}
&> .form-group {
padding-left: 0;
}
}
.help-block {
margin-bottom: 10px;
}
.project-path {
padding-right: 0;
.form-control {
border-radius: $border-radius-base;
}
}
.input-group > div {
&:last-child {
padding-right: 0;
}
}
@media (max-width: $screen-xs-max) {
.input-group > div {
margin-bottom: 14px;
&:last-child {
margin-bottom: 0;
}
}
fieldset > .form-group:first-child {
padding-right: 0;
}
}
.input-group-addon {
&.static-namespace {
height: 35px;
border-radius: 3px;
border: 1px solid #e5e5e5;
}
&+ .select2 a {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
} }
} }
...@@ -365,10 +408,28 @@ a.deploy-project-label { ...@@ -365,10 +408,28 @@ a.deploy-project-label {
} }
} }
.project-import .btn { .project-import {
float: left; .form-group {
margin-bottom: 10px; margin-bottom: 0;
margin-right: 10px; }
.import-buttons {
padding-left: 0;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
.btn {
margin-right: 10px;
padding: 8px 12px;
}
&> div {
margin-bottom: 14px;
padding-left: 0;
&:last-child {
margin-bottom: 0;
}
}
}
} }
.project-stats { .project-stats {
......
class Admin::SystemInfoController < Admin::ApplicationController
def show
system_info = Vmstat.snapshot
@cpus = system_info.cpus.length
@mem_used = system_info.memory.active_bytes
@mem_total = system_info.memory.total_bytes
@disk_used = system_info.disks[0].used_bytes
@disk_total = system_info.disks[0].total_bytes
end
end
class Dashboard::GroupsController < Dashboard::ApplicationController class Dashboard::GroupsController < Dashboard::ApplicationController
def index def index
@group_members = current_user.group_members.page(params[:page]) @group_members = current_user.group_members.includes(:source).page(params[:page])
end end
end end
...@@ -12,9 +12,13 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -12,9 +12,13 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end end
imported_file = project_params[:file].path + "-import"
FileUtils.copy_entry(project_params[:file].path, imported_file)
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id], @project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user, current_user,
File.expand_path(project_params[:file].path), File.expand_path(imported_file),
project_params[:path]).execute project_params[:path]).execute
if @project.saved? if @project.saved?
......
...@@ -16,6 +16,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -16,6 +16,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :from_merge_request, only: [:edit, :update] before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff] before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff
def new def new
commit unless @repository.empty? commit unless @repository.empty?
...@@ -146,4 +147,10 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -146,4 +147,10 @@ class Projects::BlobController < Projects::ApplicationController
file_content_encoding: params[:encoding] file_content_encoding: params[:encoding]
} }
end end
def validate_diff_params
if [:since, :to, :offset].any? { |key| params[key].blank? }
render nothing: true
end
end
end end
...@@ -18,9 +18,16 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -18,9 +18,16 @@ class Projects::CommitController < Projects::ApplicationController
apply_diff_view_cookie! apply_diff_view_cookie!
@grouped_diff_notes = commit.notes.grouped_diff_notes @grouped_diff_notes = commit.notes.grouped_diff_notes
@notes = commit.notes.non_diff_notes.fresh
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten + @notes,
@project,
current_user,
)
@note = @project.build_commit_note(commit) @note = @project.build_commit_note(commit)
@notes = commit.notes.non_diff_notes.fresh
@noteable = @commit @noteable = @commit
@comments_target = { @comments_target = {
noteable_type: 'Commit', noteable_type: 'Commit',
......
...@@ -62,8 +62,12 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -62,8 +62,12 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def show def show
raw_notes = @issue.notes_with_associations.fresh
@notes = Banzai::NoteRenderer.
render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.with_associations.fresh
@noteable = @issue @noteable = @issue
respond_to do |format| respond_to do |format|
...@@ -111,6 +115,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -111,6 +115,7 @@ class Projects::IssuesController < Projects::ApplicationController
render :edit render :edit
end end
end end
format.json do format.json do
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end end
......
...@@ -59,7 +59,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -59,7 +59,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json { render json: @merge_request } format.json { render json: @merge_request }
format.patch { render text: @merge_request.to_patch } format.patch do
headers.store(*Gitlab::Workhorse.send_git_patch(@project.repository,
@merge_request.diff_base_commit.id,
@merge_request.last_commit.id))
headers['Content-Disposition'] = 'inline'
head :ok
end
format.diff do format.diff do
return render_404 unless @merge_request.diff_refs return render_404 unless @merge_request.diff_refs
...@@ -85,6 +91,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -85,6 +91,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
respond_to do |format| respond_to do |format|
format.html format.html
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } } format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
...@@ -190,7 +205,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -190,7 +205,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user) return access_denied! unless @merge_request.can_be_merged_by?(current_user)
unless @merge_request.mergeable? # Disable the CI check if merge_when_build_succeeds is enabled since we have
# to wait until CI completes to know
unless @merge_request.mergeable?(skip_ci_check: merge_when_build_succeeds_active?)
@status = :failed @status = :failed
return return
end end
...@@ -204,8 +221,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -204,8 +221,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil) @merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present? if params[:merge_when_build_succeeds].present?
if @merge_request.pipeline && @merge_request.pipeline.active? unless @merge_request.pipeline
@status = :failed
return
end
if @merge_request.pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request) .execute(@merge_request)
@status = :merge_when_build_succeeds @status = :merge_when_build_succeeds
...@@ -320,8 +342,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -320,8 +342,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars def define_show_vars
# Build a note object for comment form # Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request) @note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.inc_author.fresh
@discussions = @notes.discussions @discussions = @merge_request.mr_and_commit_notes.
inc_author_project_award_emoji.
fresh.
discussions
@notes = Banzai::NoteRenderer.render(
@discussions.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
@noteable = @merge_request @noteable = @merge_request
# Get commits from repository # Get commits from repository
...@@ -368,4 +403,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -368,4 +403,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def ensure_ref_fetched def ensure_ref_fetched
@merge_request.ensure_ref_fetched @merge_request.ensure_ref_fetched
end end
def merge_when_build_succeeds_active?
params[:merge_when_build_succeeds].present? &&
@merge_request.pipeline && @merge_request.pipeline.active?
end
end end
...@@ -24,6 +24,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -24,6 +24,10 @@ class Projects::NotesController < Projects::ApplicationController
def create def create
@note = Notes::CreateService.new(project, current_user, note_params).execute @note = Notes::CreateService.new(project, current_user, note_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format| respond_to do |format|
format.json { render json: note_json(@note) } format.json { render json: note_json(@note) }
format.html { redirect_back_or_default } format.html { redirect_back_or_default }
...@@ -33,6 +37,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -33,6 +37,10 @@ class Projects::NotesController < Projects::ApplicationController
def update def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note) @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format| respond_to do |format|
format.json { render json: note_json(@note) } format.json { render json: note_json(@note) }
format.html { redirect_back_or_default } format.html { redirect_back_or_default }
...@@ -118,6 +126,8 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -118,6 +126,8 @@ class Projects::NotesController < Projects::ApplicationController
name: note.name name: note.name
} }
elsif note.valid? elsif note.valid?
Banzai::NoteRenderer.render([note], @project, current_user)
{ {
valid: true, valid: true,
id: note.id, id: note.id,
......
...@@ -29,10 +29,10 @@ class PipelinesFinder ...@@ -29,10 +29,10 @@ class PipelinesFinder
end end
def branches def branches
project.repository.branches.map(&:name) project.repository.branch_names
end end
def tags def tags
project.repository.tags.map(&:name) project.repository.tag_names
end end
end end
...@@ -197,7 +197,7 @@ module ApplicationHelper ...@@ -197,7 +197,7 @@ module ApplicationHelper
def render_markup(file_name, file_content) def render_markup(file_name, file_content)
if gitlab_markdown?(file_name) if gitlab_markdown?(file_name)
Haml::Helpers.preserve(markdown(file_content)) Hamlit::RailsHelpers.preserve(markdown(file_content))
elsif asciidoc?(file_name) elsif asciidoc?(file_name)
asciidoc(file_content) asciidoc(file_content)
elsif plain?(file_name) elsif plain?(file_name)
......
module BlobHelper module BlobHelper
def highlighter(blob_name, blob_content, nowrap: false) def highlighter(blob_name, blob_content, repository: nil, nowrap: false)
Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap) Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap, repository: repository)
end end
def highlight(blob_name, blob_content, nowrap: false, plain: false) def highlight(blob_name, blob_content, repository: nil, nowrap: false, plain: false)
Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain) Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain, repository: repository)
end end
def no_highlight_files def no_highlight_files
......
module JavascriptHelper module JavascriptHelper
def page_specific_javascripts(js = nil) def page_specific_javascript_tag(js)
@page_specific_javascripts = js unless js.nil? javascript_include_tag asset_path(js), { "data-turbolinks-track" => true }
@page_specific_javascripts
end end
end end
...@@ -196,7 +196,8 @@ class Ability ...@@ -196,7 +196,8 @@ class Ability
@public_project_rules ||= project_guest_rules + [ @public_project_rules ||= project_guest_rules + [
:download_code, :download_code,
:fork_project, :fork_project,
:read_commit_status :read_commit_status,
:read_pipeline
] ]
end end
......
...@@ -163,13 +163,26 @@ module Ci ...@@ -163,13 +163,26 @@ module Ci
end end
def skip_ci? def skip_ci?
git_commit_message =~ /(\[ci skip\])/ if git_commit_message git_commit_message =~ /\[(ci skip|skip ci)\]/i if git_commit_message
end end
def environments def environments
builds.where.not(environment: nil).success.pluck(:environment).uniq builds.where.not(environment: nil).success.pluck(:environment).uniq
end end
# Manually set the notes for a Ci::Pipeline
# There is no ActiveRecord relation between Ci::Pipeline and notes
# as they are related to a commit sha. This method helps importing
# them using the +Gitlab::ImportExport::RelationFactory+ class.
def notes=(notes)
notes.each do |note|
note[:id] = nil
note[:commit_id] = sha
note[:noteable_id] = self['id']
note.save!
end
end
def notes def notes
Note.for_commit_id(sha) Note.for_commit_id(sha)
end end
......
...@@ -13,6 +13,7 @@ module Ci ...@@ -13,6 +13,7 @@ module Ci
attr_encrypted :value, attr_encrypted :value,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv_and_salt,
insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base, key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
end end
......
...@@ -2,10 +2,11 @@ module Awardable ...@@ -2,10 +2,11 @@ module Awardable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
has_many :award_emoji, as: :awardable, dependent: :destroy has_many :award_emoji, -> { includes(:user) }, as: :awardable, dependent: :destroy
if self < Participable if self < Participable
participant :award_emoji_with_associations # By default we always load award_emoji user association
participant :award_emoji
end end
end end
...@@ -34,12 +35,9 @@ module Awardable ...@@ -34,12 +35,9 @@ module Awardable
end end
end end
def award_emoji_with_associations
award_emoji.includes(:user)
end
def grouped_awards(with_thumbs: true) def grouped_awards(with_thumbs: true)
awards = award_emoji_with_associations.group_by(&:name) # By default we always load award_emoji user association
awards = award_emoji.group_by(&:name)
if with_thumbs if with_thumbs
awards[AwardEmoji::UPVOTE_NAME] ||= [] awards[AwardEmoji::UPVOTE_NAME] ||= []
......
...@@ -19,9 +19,14 @@ module Issuable ...@@ -19,9 +19,14 @@ module Issuable
belongs_to :milestone belongs_to :milestone
has_many :notes, as: :noteable, dependent: :destroy do has_many :notes, as: :noteable, dependent: :destroy do
def authors_loaded? def authors_loaded?
# We check first if we're loaded to not load unnecesarily. # We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:author).loaded? } loaded? && to_a.all? { |note| note.association(:author).loaded? }
end end
def award_emojis_loaded?
# We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:award_emoji).loaded? }
end
end end
has_many :label_links, as: :target, dependent: :destroy has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links has_many :labels, through: :label_links
...@@ -49,7 +54,7 @@ module Issuable ...@@ -49,7 +54,7 @@ module Issuable
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) } scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) } scope :join_project, -> { joins(:project) }
scope :inc_notes_with_associations, -> { includes(notes: :author) } scope :inc_notes_with_associations, -> { includes(notes: [ :project, :author, :award_emoji ]) }
scope :references_project, -> { references(:project) } scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) } scope :non_archived, -> { join_project.where(projects: { archived: false }) }
...@@ -112,15 +117,18 @@ module Issuable ...@@ -112,15 +117,18 @@ module Issuable
end end
def sort(method, excluded_labels: []) def sort(method, excluded_labels: [])
case method.to_s sorted = case method.to_s
when 'milestone_due_asc' then order_milestone_due_asc when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc when 'milestone_due_desc' then order_milestone_due_desc
when 'downvotes_desc' then order_downvotes_desc when 'downvotes_desc' then order_downvotes_desc
when 'upvotes_desc' then order_upvotes_desc when 'upvotes_desc' then order_upvotes_desc
when 'priority' then order_labels_priority(excluded_labels: excluded_labels) when 'priority' then order_labels_priority(excluded_labels: excluded_labels)
else else
order_by(method) order_by(method)
end end
# Break ties with the ID column for pagination
sorted.order(id: :desc)
end end
def order_labels_priority(excluded_labels: []) def order_labels_priority(excluded_labels: [])
...@@ -257,7 +265,14 @@ module Issuable ...@@ -257,7 +265,14 @@ module Issuable
# already have their authors loaded (possibly because the scope # already have their authors loaded (possibly because the scope
# `inc_notes_with_associations` was used) and skip the inclusion if that's # `inc_notes_with_associations` was used) and skip the inclusion if that's
# the case. # the case.
notes.authors_loaded? ? notes : notes.includes(:author) includes = []
includes << :author unless notes.authors_loaded?
includes << :award_emoji unless notes.award_emojis_loaded?
if includes.any?
notes.includes(includes)
else
notes
end
end end
def updated_tasks def updated_tasks
......
...@@ -315,7 +315,7 @@ class Event < ActiveRecord::Base ...@@ -315,7 +315,7 @@ class Event < ActiveRecord::Base
def body? def body?
if push? if push?
push_with_commits? push_with_commits? || rm_ref?
elsif note? elsif note?
true true
else else
......
...@@ -11,7 +11,7 @@ class Group < Namespace ...@@ -11,7 +11,7 @@ class Group < Namespace
has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members
has_many :owners, has_many :owners,
-> { where(members: { access_level: Gitlab::Access::OWNER }) }, -> { where(members: { requested_at: nil, access_level: Gitlab::Access::OWNER }) },
through: :group_members, through: :group_members,
source: :user source: :user
......
...@@ -20,7 +20,7 @@ class LegacyDiffNote < Note ...@@ -20,7 +20,7 @@ class LegacyDiffNote < Note
end end
def discussion_id def discussion_id
@discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code, active?) @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
end end
def diff_file_hash def diff_file_hash
......
...@@ -32,6 +32,7 @@ class Member < ActiveRecord::Base ...@@ -32,6 +32,7 @@ class Member < ActiveRecord::Base
scope :request, -> { where.not(requested_at: nil) } scope :request, -> { where.not(requested_at: nil) }
scope :non_request, -> { where(requested_at: nil) } scope :non_request, -> { where(requested_at: nil) }
scope :non_pending, -> { non_request.non_invite } scope :non_pending, -> { non_request.non_invite }
scope :has_access, -> { where('access_level > 0') }
scope :guests, -> { where(access_level: GUEST) } scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) } scope :reporters, -> { where(access_level: REPORTER) }
......
...@@ -264,19 +264,19 @@ class MergeRequest < ActiveRecord::Base ...@@ -264,19 +264,19 @@ class MergeRequest < ActiveRecord::Base
self.title.sub(WIP_REGEX, "") self.title.sub(WIP_REGEX, "")
end end
def mergeable? def mergeable?(skip_ci_check: false)
return false unless mergeable_state? return false unless mergeable_state?(skip_ci_check: skip_ci_check)
check_if_can_be_merged check_if_can_be_merged
can_be_merged? can_be_merged?
end end
def mergeable_state? def mergeable_state?(skip_ci_check: false)
return false unless open? return false unless open?
return false if work_in_progress? return false if work_in_progress?
return false if broken? return false if broken?
return false unless mergeable_ci_state? return false unless skip_ci_check || mergeable_ci_state?
true true
end end
...@@ -319,13 +319,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -319,13 +319,6 @@ class MergeRequest < ActiveRecord::Base
) )
end end
# Returns the commit as a series of email patches.
#
# see "git format-patch"
def to_patch
target_project.repository.format_patch(diff_base_commit.sha, source_sha)
end
def hook_attrs def hook_attrs
attrs = { attrs = {
source: source_project.try(:hook_attrs), source: source_project.try(:hook_attrs),
......
...@@ -108,44 +108,46 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -108,44 +108,46 @@ class MergeRequestDiff < ActiveRecord::Base
# Reload all commits related to current merge request from repo # Reload all commits related to current merge request from repo
# and save it as array of hashes in st_commits db field # and save it as array of hashes in st_commits db field
def reload_commits def reload_commits
new_attributes = {}
commit_objects = unmerged_commits commit_objects = unmerged_commits
if commit_objects.present? if commit_objects.present?
self.st_commits = dump_commits(commit_objects) new_attributes[:st_commits] = dump_commits(commit_objects)
end end
save update_columns_serialized(new_attributes)
end end
# Reload diffs between branches related to current merge request from repo # Reload diffs between branches related to current merge request from repo
# and save it as array of hashes in st_diffs db field # and save it as array of hashes in st_diffs db field
def reload_diffs def reload_diffs
new_attributes = {}
new_diffs = [] new_diffs = []
if commits.size.zero? if commits.size.zero?
self.state = :empty new_attributes[:state] = :empty
else else
diff_collection = unmerged_diffs diff_collection = unmerged_diffs
if diff_collection.overflow? if diff_collection.overflow?
# Set our state to 'overflow' to make the #empty? and #collected? # Set our state to 'overflow' to make the #empty? and #collected?
# methods (generated by StateMachine) return false. # methods (generated by StateMachine) return false.
self.state = :overflow new_attributes[:state] = :overflow
end end
self.real_size = diff_collection.real_size new_attributes[:real_size] = diff_collection.real_size
if diff_collection.any? if diff_collection.any?
new_diffs = dump_diffs(diff_collection) new_diffs = dump_diffs(diff_collection)
self.state = :collected new_attributes[:state] = :collected
end end
end end
self.st_diffs = new_diffs new_attributes[:st_diffs] = new_diffs
new_attributes[:base_commit_sha] = self.repository.merge_base(self.head, self.base)
self.base_commit_sha = self.repository.merge_base(self.head, self.base)
self.save update_columns_serialized(new_attributes)
end end
# Collect array of Git::Diff objects # Collect array of Git::Diff objects
...@@ -190,4 +192,29 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -190,4 +192,29 @@ class MergeRequestDiff < ActiveRecord::Base
) )
end end
end end
private
#
# #save or #update_attributes providing changes on serialized attributes do a lot of
# serialization and deserialization calls resulting in bad performance.
# Using #update_columns solves the problem with just one YAML.dump per serialized attribute that we provide.
# As a tradeoff we need to reload the current instance to properly manage time objects on those serialized
# attributes. So to keep the same behaviour as the attribute assignment we reload the instance.
# The difference is in the usage of
# #write_attribute= (#update_attributes) and #raw_write_attribute= (#update_columns)
#
# Ex:
#
# new_attributes[:st_commits].first.slice(:committed_date)
# => {:committed_date=>2014-02-27 11:01:38 +0200}
# YAML.load(YAML.dump(new_attributes[:st_commits].first.slice(:committed_date)))
# => {:committed_date=>2014-02-27 10:01:38 +0100}
#
def update_columns_serialized(new_attributes)
return unless new_attributes.any?
update_columns(new_attributes.merge(updated_at: current_time_from_proper_timezone))
reload
end
end end
...@@ -6,6 +6,10 @@ class Note < ActiveRecord::Base ...@@ -6,6 +6,10 @@ class Note < ActiveRecord::Base
include Awardable include Awardable
include Importable include Importable
# Attribute containing rendered and redacted Markdown as generated by
# Banzai::ObjectRenderer.
attr_accessor :note_html
default_value_for :system, false default_value_for :system, false
attr_mentionable :note, pipeline: :note attr_mentionable :note, pipeline: :note
...@@ -49,11 +53,13 @@ class Note < ActiveRecord::Base ...@@ -49,11 +53,13 @@ class Note < ActiveRecord::Base
scope :fresh, ->{ order(created_at: :asc, id: :asc) } scope :fresh, ->{ order(created_at: :asc, id: :asc) }
scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) } scope :inc_author, ->{ includes(:author) }
scope :inc_author_project_award_emoji, ->{ includes(:project, :author, :award_emoji) }
scope :legacy_diff_notes, ->{ where(type: 'LegacyDiffNote') } scope :legacy_diff_notes, ->{ where(type: 'LegacyDiffNote') }
scope :non_diff_notes, ->{ where(type: ['Note', nil]) } scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
scope :with_associations, -> do scope :with_associations, -> do
# FYI noteable cannot be loaded for LegacyDiffNote for commits
includes(:author, :noteable, :updated_by, includes(:author, :noteable, :updated_by,
project: [:project_members, { group: [:group_members] }]) project: [:project_members, { group: [:group_members] }])
end end
......
...@@ -81,6 +81,7 @@ class Project < ActiveRecord::Base ...@@ -81,6 +81,7 @@ class Project < ActiveRecord::Base
has_one :jira_service, dependent: :destroy has_one :jira_service, dependent: :destroy
has_one :redmine_service, dependent: :destroy has_one :redmine_service, dependent: :destroy
has_one :custom_issue_tracker_service, dependent: :destroy has_one :custom_issue_tracker_service, dependent: :destroy
has_one :bugzilla_service, dependent: :destroy
has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
has_one :external_wiki_service, dependent: :destroy has_one :external_wiki_service, dependent: :destroy
...@@ -163,6 +164,7 @@ class Project < ActiveRecord::Base ...@@ -163,6 +164,7 @@ class Project < ActiveRecord::Base
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
validate :visibility_level_allowed_by_group validate :visibility_level_allowed_by_group
validate :visibility_level_allowed_as_fork validate :visibility_level_allowed_as_fork
validate :check_wiki_path_conflict
add_authentication_token_field :runners_token add_authentication_token_field :runners_token
before_save :ensure_runners_token before_save :ensure_runners_token
...@@ -539,6 +541,16 @@ class Project < ActiveRecord::Base ...@@ -539,6 +541,16 @@ class Project < ActiveRecord::Base
self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.") self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.")
end end
def check_wiki_path_conflict
return if path.blank?
path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki"
if Project.where(namespace_id: namespace_id, path: path_to_check).exists?
errors.add(:name, 'has already been taken')
end
end
def to_param def to_param
path path
end end
......
...@@ -7,6 +7,7 @@ class ProjectImportData < ActiveRecord::Base ...@@ -7,6 +7,7 @@ class ProjectImportData < ActiveRecord::Base
marshal: true, marshal: true,
encode: true, encode: true,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv_and_salt,
insecure_mode: true,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
serialize :data, JSON serialize :data, JSON
......
class BugzillaService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def title
if self.properties && self.properties['title'].present?
self.properties['title']
else
'Bugzilla'
end
end
def description
if self.properties && self.properties['description'].present?
self.properties['description']
else
'Bugzilla issue tracker'
end
end
def to_param
'bugzilla'
end
end
...@@ -32,7 +32,4 @@ class CustomIssueTrackerService < IssueTrackerService ...@@ -32,7 +32,4 @@ class CustomIssueTrackerService < IssueTrackerService
] ]
end end
def initialize_properties
self.properties = {} if properties.nil?
end
end end
...@@ -137,20 +137,10 @@ class ProjectTeam ...@@ -137,20 +137,10 @@ class ProjectTeam
def max_member_access(user_id) def max_member_access(user_id)
access = [] access = []
project.members.non_request.each do |member| access += project.members.non_request.where(user_id: user_id).has_access.pluck(:access_level)
if member.user_id == user_id
access << member.access_field if member.access_field
break
end
end
if group if group
group.members.non_request.each do |member| access += group.members.non_request.where(user_id: user_id).has_access.pluck(:access_level)
if member.user_id == user_id
access << member.access_field if member.access_field
break
end
end
end end
if project.invited_groups.any? && project.allowed_to_share_with_group? if project.invited_groups.any? && project.allowed_to_share_with_group?
......
...@@ -130,7 +130,7 @@ class Repository ...@@ -130,7 +130,7 @@ class Repository
end end
def find_tag(name) def find_tag(name)
raw_repository.tags.find { |tag| tag.name == name } tags.find { |tag| tag.name == name }
end end
def add_branch(user, branch_name, target) def add_branch(user, branch_name, target)
...@@ -978,6 +978,10 @@ class Repository ...@@ -978,6 +978,10 @@ class Repository
raw_repository.ls_files(actual_ref) raw_repository.ls_files(actual_ref)
end end
def gitattribute(path, name)
raw_repository.attributes(path)[name]
end
def copy_gitattributes(ref) def copy_gitattributes(ref)
actual_ref = ref || root_ref actual_ref = ref || root_ref
begin begin
......
...@@ -170,6 +170,7 @@ class Service < ActiveRecord::Base ...@@ -170,6 +170,7 @@ class Service < ActiveRecord::Base
bamboo bamboo
buildkite buildkite
builds_email builds_email
bugzilla
campfire campfire
custom_issue_tracker custom_issue_tracker
drone_ci drone_ci
......
...@@ -20,6 +20,7 @@ class Snippet < ActiveRecord::Base ...@@ -20,6 +20,7 @@ class Snippet < ActiveRecord::Base
length: { within: 0..255 }, length: { within: 0..255 },
format: { with: Gitlab::Regex.file_name_regex, format: { with: Gitlab::Regex.file_name_regex,
message: Gitlab::Regex.file_name_regex_message } message: Gitlab::Regex.file_name_regex_message }
validates :content, presence: true validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
...@@ -81,6 +82,11 @@ class Snippet < ActiveRecord::Base ...@@ -81,6 +82,11 @@ class Snippet < ActiveRecord::Base
0 0
end end
# alias for compatibility with blobs and highlighting
def path
file_name
end
def name def name
file_name file_name
end end
...@@ -135,7 +141,16 @@ class Snippet < ActiveRecord::Base ...@@ -135,7 +141,16 @@ class Snippet < ActiveRecord::Base
end end
def accessible_to(user) def accessible_to(user)
where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user) return are_public unless user.present?
return all if user.admin?
where(
'visibility_level IN (:visibility_levels)
OR author_id = :author_id
OR project_id IN (:project_ids)',
visibility_levels: [Snippet::PUBLIC, Snippet::INTERNAL],
author_id: user.id,
project_ids: user.authorized_projects.select(:id))
end end
end end
end end
...@@ -25,6 +25,7 @@ class User < ActiveRecord::Base ...@@ -25,6 +25,7 @@ class User < ActiveRecord::Base
attr_encrypted :otp_secret, attr_encrypted :otp_secret,
key: Gitlab::Application.config.secret_key_base, key: Gitlab::Application.config.secret_key_base,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv_and_salt,
insecure_mode: true,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
devise :two_factor_authenticatable, devise :two_factor_authenticatable,
...@@ -57,7 +58,7 @@ class User < ActiveRecord::Base ...@@ -57,7 +58,7 @@ class User < ActiveRecord::Base
# Groups # Groups
has_many :members, dependent: :destroy has_many :members, dependent: :destroy
has_many :group_members, dependent: :destroy, source: 'GroupMember' has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember'
has_many :groups, through: :group_members has_many :groups, through: :group_members
has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group
has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group
...@@ -65,7 +66,7 @@ class User < ActiveRecord::Base ...@@ -65,7 +66,7 @@ class User < ActiveRecord::Base
# Projects # Projects
has_many :groups_projects, through: :groups, source: :projects has_many :groups_projects, through: :groups, source: :projects
has_many :personal_projects, through: :namespace, source: :projects has_many :personal_projects, through: :namespace, source: :projects
has_many :project_members, dependent: :destroy, class_name: 'ProjectMember' has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, class_name: 'ProjectMember'
has_many :projects, through: :project_members has_many :projects, through: :project_members
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :users_star_projects, dependent: :destroy has_many :users_star_projects, dependent: :destroy
...@@ -308,7 +309,7 @@ class User < ActiveRecord::Base ...@@ -308,7 +309,7 @@ class User < ActiveRecord::Base
def generate_password def generate_password
if self.force_random_password if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8) self.password = self.password_confirmation = Devise.friendly_token.first(Devise.password_length.min)
end end
end end
......
...@@ -159,8 +159,9 @@ class TodoService ...@@ -159,8 +159,9 @@ class TodoService
def create_todos(users, attributes) def create_todos(users, attributes)
Array(users).map do |user| Array(users).map do |user|
next if pending_todos(user, attributes).exists? next if pending_todos(user, attributes).exists?
Todo.create(attributes.merge(user_id: user.id)) todo = Todo.create(attributes.merge(user_id: user.id))
user.update_todos_count_cache user.update_todos_count_cache
todo
end end
end end
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
= f.label :default_snippet_visibility, class: 'control-label col-sm-2' = f.label :default_snippet_visibility, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new) = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
.form-group.group-visibility-level-holder .form-group.project-visibility-level-holder
= f.label :default_group_visibility, class: 'control-label col-sm-2' = f.label :default_group_visibility, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new) = render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new)
......
.nav-links.sub-nav .nav-links.sub-nav
%ul{ class: (container_class) } %ul{ class: (container_class) }
= nav_link(controller: :system_info) do
= link_to admin_system_info_path, title: 'System Info' do
%span
System Info
= nav_link(controller: :background_jobs) do = nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs' do = link_to admin_background_jobs_path, title: 'Background Jobs' do
%span %span
......
- @no_container = true
- page_title "System Info"
= render 'admin/background_jobs/head'
%div{ class: (container_class) }
.prepend-top-default
.row
.col-sm-4
.light-well
%h4 CPU
.data
%h1= "#{@cpus} cores"
.col-sm-4
.light-well
%h4 Memory
.data
%h1= "#{number_to_human_size(@mem_used)} / #{number_to_human_size(@mem_total)}"
.col-sm-4
.light-well
%h4 Disk
.data
%h1= "#{number_to_human_size(@disk_used)} / #{number_to_human_size(@disk_total)}"
- page_title "Groups", @user.name, "Users" - page_title "Groups", @user.name, "Users"
= render 'admin/users/head' = render 'admin/users/head'
- if @user.group_members.present? - group_members = @user.group_members.includes(:source)
- if group_members.any?
.panel.panel-default .panel.panel-default
.panel-heading Groups: .panel-heading Groups:
%ul.well-list %ul.well-list
- @user.group_members.each do |group_member| - group_members.each do |group_member|
- group = group_member.group - group = group_member.group
%li.group_member %li.group_member
%span{class: ("list-item-name" unless group_member.owner?)} %span{class: ("list-item-name" unless group_member.owner?)}
......
.bs-callout.help-callout
%h4 How to setup CI for this project
%ol
%li
Add at least one runner to the project.
Go to #{link_to 'Runners page', runners_path(@project), target: :blank} for instructions.
%li
Put the .gitlab-ci.yml in the root of your repository. Examples can be found in
#{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
%li
Return to this page and refresh it, it should show a new build.
.alert.alert-danger
%p
Now you need Runners to process your builds.
%span
Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it
.emoji-menu .emoji-menu
= text_field_tag :emoji_search, "", class: "emoji-search search-input form-control", placeholder: "Seach emojis"
.emoji-menu-content .emoji-menu-content
= text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
- Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis| - Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis|
%h5.emoji-menu-title %h5.emoji-menu-title
= Gitlab::AwardEmoji::CATEGORIES[category] = Gitlab::AwardEmoji::CATEGORIES[category]
......
- project = event.project
.event-title .event-title
%span.author_name= link_to_author event %span.author_name= link_to_author event
%span.event_label.pushed #{event.action_name} #{event.ref_type} %span.event_label.pushed #{event.action_name} #{event.ref_type}
...@@ -5,19 +7,18 @@ ...@@ -5,19 +7,18 @@
%strong= event.ref_name %strong= event.ref_name
- else - else
%strong %strong
= link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), title: h(event.target_title) = link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title)
at at
= link_to_project event.project = link_to_project project
- if event.push_with_commits? - if event.push_with_commits?
- project = event.project
.event-body .event-body
%ul.well-list.event_commits %ul.well-list.event_commits
- few_commits = event.commits[0...2] - few_commits = event.commits[0...2]
- few_commits.each do |commit| - few_commits.each do |commit|
= render "events/commit", commit: commit, project: project, event: event = render "events/commit", commit: commit, project: project, event: event
- create_mr = event.new_ref? && create_mr_button?(event.project.default_branch, event.ref_name, event.project) - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project)
- if event.commits_count > 1 - if event.commits_count > 1
%li.commits-stat %li.commits-stat
- if event.commits_count > 2 - if event.commits_count > 2
...@@ -27,18 +28,26 @@ ...@@ -27,18 +28,26 @@
- from = event.commit_from - from = event.commit_from
- from_label = truncate_sha(from) - from_label = truncate_sha(from)
- else - else
- from = event.project.default_branch - from = project.default_branch
- from_label = from - from_label = from
= link_to namespace_project_compare_path(event.project.namespace, event.project, from: from, to: event.commit_to) do = link_to namespace_project_compare_path(project.namespace, project, from: from, to: event.commit_to) do
Compare #{from_label}...#{truncate_sha(event.commit_to)} Compare #{from_label}...#{truncate_sha(event.commit_to)}
- if create_mr - if create_mr
%span{"data-user-is" => event.author_id, "data-display" => "inline"} %span{"data-user-is" => event.author_id, "data-display" => "inline"}
or or
= link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do = link_to create_mr_path(project.default_branch, event.ref_name, project) do
create a merge request create a merge request
- elsif create_mr - elsif create_mr
%li.commits-stat{"data-user-is" => event.author_id} %li.commits-stat{"data-user-is" => event.author_id}
= link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do = link_to create_mr_path(project.default_branch, event.ref_name, project) do
Create Merge Request Create Merge Request
- elsif event.rm_ref?
- repository = project.repository
- last_commit = repository.commit(event.commit_from)
- if last_commit
.event-body
%ul.well-list.event_commits
= render "events/commit", commit: last_commit, project: project, event: event
...@@ -30,8 +30,8 @@ ...@@ -30,8 +30,8 @@
= javascript_include_tag "application" = javascript_include_tag "application"
- if page_specific_javascripts - if content_for?(:page_specific_javascripts)
= javascript_include_tag page_specific_javascripts, {"data-turbolinks-track" => true} = yield :page_specific_javascripts
= csrf_meta_tags = csrf_meta_tags
......
- if current_user && current_user.is_admin? && Ci::Runner.count.zero?
= render 'ci/shared/no_runners'
.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
- if defined?(sidebar) && sidebar
= render "layouts/ci/#{sidebar}"
- elsif current_user
= render 'layouts/nav/dashboard'
.collapse-nav
= 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'
.username
= current_user.username
.content-wrapper
= render "layouts/flash"
= render 'layouts/ci/info'
%div{ class: container_class }
.content
.clearfix
= yield
%html{lang: "en"}
%head
%meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
%title
GitLab CI
%body
= yield :header
%table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"}
%tr
%td{align: "left", style: "margin: 0; padding: 10px;"}
= yield
%br
%tr
%td{align: "left", style: "margin: 0; padding: 10px;"}
%p{style: "font-size:small;color:#777"}
- if @project
You're receiving this notification because you are the one who triggered a build on the #{@project.name} project.
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
= icon('bars') = icon('bars')
%button.navbar-toggle{type: 'button'} %button.navbar-toggle{type: 'button'}
%span.sr-only Toggle navigation %span.sr-only Toggle navigation
= icon('angle-left') = icon('ellipsis-v')
.navbar-collapse.collapse .navbar-collapse.collapse
%ul.nav.navbar-nav %ul.nav.navbar-nav
......
%div{ class: nav_control_class } .scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/admin_settings' = render 'layouts/nav/admin_settings'
.fade-left
= icon('angle-left')
.fade-right
= icon('angle-right')
%ul.nav-links.scrolling-tabs %ul.nav-links.scrolling-tabs
%li.fade-left
= icon('arrow-left')
= nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do = nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
%span %span
Overview Overview
= nav_link(controller: %w(background_jobs logs health_check)) do = nav_link(controller: %w(system_info background_jobs logs health_check)) do
= link_to admin_background_jobs_path, title: 'Monitoring' do = link_to admin_system_info_path, title: 'Monitoring' do
%span %span
Monitoring Monitoring
= nav_link(controller: :broadcast_messages) do = nav_link(controller: :broadcast_messages) do
...@@ -37,5 +38,3 @@ ...@@ -37,5 +38,3 @@
= link_to admin_spam_logs_path, title: "Spam Logs" do = link_to admin_spam_logs_path, title: "Spam Logs" do
%span %span
Spam Logs Spam Logs
%li.fade-right
= icon('arrow-right')
%div{ class: nav_control_class } .scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/group_settings' = render 'layouts/nav/group_settings'
.fade-left
= icon('angle-left')
.fade-right
= icon('angle-right')
%ul.nav-links.scrolling-tabs %ul.nav-links.scrolling-tabs
%li.fade-left
= icon('arrow-left')
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do = link_to group_path(@group), title: 'Home' do
%span %span
...@@ -32,5 +33,3 @@ ...@@ -32,5 +33,3 @@
= link_to group_group_members_path(@group), title: 'Members' do = link_to group_group_members_path(@group), title: 'Members' do
%span %span
Members Members
%li.fade-right
= icon('arrow-right')
%ul.nav-links.scrolling-tabs .scrolling-tabs-container
%li.fade-left .fade-left
= icon('arrow-left') = icon('angle-left')
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do .fade-right
= link_to profile_path, title: 'Profile Settings' do = icon('angle-right')
%span %ul.nav-links.scrolling-tabs
Profile = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= nav_link(controller: [:accounts, :two_factor_auths]) do = link_to profile_path, title: 'Profile Settings' do
= link_to profile_account_path, title: 'Account' do %span
%span Profile
Account = nav_link(controller: [:accounts, :two_factor_auths]) do
- if current_application_settings.user_oauth_applications? = link_to profile_account_path, title: 'Account' do
= nav_link(controller: 'oauth/applications') do %span
= link_to applications_profile_path, title: 'Applications' do Account
%span - if current_application_settings.user_oauth_applications?
Applications = nav_link(controller: 'oauth/applications') do
= nav_link(controller: :personal_access_tokens) do = link_to applications_profile_path, title: 'Applications' do
= link_to profile_personal_access_tokens_path, title: 'Personal Access Tokens' do %span
%span Applications
Personal Access Tokens = nav_link(controller: :personal_access_tokens) do
= nav_link(controller: :emails) do = link_to profile_personal_access_tokens_path, title: 'Personal Access Tokens' do
= link_to profile_emails_path, title: 'Emails' do %span
%span Personal Access Tokens
Emails = nav_link(controller: :emails) do
- unless current_user.ldap_user? = link_to profile_emails_path, title: 'Emails' do
= nav_link(controller: :passwords) do %span
= link_to edit_profile_password_path, title: 'Password' do Emails
%span - unless current_user.ldap_user?
Password = nav_link(controller: :passwords) do
= nav_link(controller: :notifications) do = link_to edit_profile_password_path, title: 'Password' do
= link_to profile_notifications_path, title: 'Notifications' do %span
%span Password
Notifications = nav_link(controller: :notifications) do
= link_to profile_notifications_path, title: 'Notifications' do
%span
Notifications
= nav_link(controller: :keys) do = nav_link(controller: :keys) do
= link_to profile_keys_path, title: 'SSH Keys' do = link_to profile_keys_path, title: 'SSH Keys' do
%span %span
SSH Keys SSH Keys
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do = link_to profile_preferences_path, title: 'Preferences' do
%span %span
Preferences Preferences
= nav_link(path: 'profiles#audit_log') do = nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path, title: 'Audit Log' do = link_to audit_log_profile_path, title: 'Audit Log' do
%span %span
Audit Log Audit Log
%li.fade-right
= icon('arrow-right')
...@@ -24,10 +24,12 @@ ...@@ -24,10 +24,12 @@
data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do
Leave Project Leave Project
%div{ class: nav_control_class } .scrolling-tabs-container{ class: nav_control_class }
.fade-left
= icon('angle-left')
.fade-right
= icon('angle-right')
%ul.nav-links.scrolling-tabs %ul.nav-links.scrolling-tabs
%li.fade-left
= icon('arrow-left')
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
%span %span
...@@ -111,5 +113,3 @@ ...@@ -111,5 +113,3 @@
%li.hidden %li.hidden
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits Commits
%li.fade-right
= icon('arrow-right')
...@@ -12,13 +12,13 @@ ...@@ -12,13 +12,13 @@
%li.confidential-issue-warning %li.confidential-issue-warning
= icon('warning') = icon('warning')
%span This is a confidential issue. Your comment will not be visible to the public. %span This is a confidential issue. Your comment will not be visible to the public.
%li.pull-right %li.pull-right
.toolbar-group .toolbar-group
= markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" }) = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
= markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" }) = markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" })
= markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" }) = markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
= markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`" }, title: "Insert code" }) = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`", "md-block" => "```" }, title: "Insert code" })
= markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" }) = markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" })
= markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) = markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
= markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) = markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
......
...@@ -8,4 +8,4 @@ ...@@ -8,4 +8,4 @@
%strong Only allow merge requests to be merged if the build succeeds %strong Only allow merge requests to be merged if the build succeeds
.help-block .help-block
Builds need to be configured to enable this feature. Builds need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('workflow', 'merge_requests#only-allow-merge-requests-to-be-merged-if-the-build-succeeds') = link_to icon('question-circle'), help_page_path('workflow', 'merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment