diff options
author | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-05-09 20:21:02 +0300 |
---|---|---|
committer | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-05-09 20:21:02 +0300 |
commit | 71da2e0b56a89fc87f2da3685bdebb333690593a (patch) | |
tree | be6418658f118746ab158be162622fe8cb5f0b0e | |
parent | 011a905a821e2ff0cd2d9885ef93764018eb8346 (diff) | |
parent | 44501820152083d231459223fe09b9d9641b7c1e (diff) | |
download | gitlab-ce-71da2e0b56a89fc87f2da3685bdebb333690593a.tar.gz |
Merge remote-tracking branch 'origin/master' into docker-registry
321 files changed, 3895 insertions, 1356 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 83ed6c38678..9f179efa3ce 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -953,10 +953,9 @@ Performance/DoubleStartEndWith: Performance/EndWith: Enabled: false -# TODO: Enable LstripRstrip Cop. # Use `strip` instead of `lstrip.rstrip`. Performance/LstripRstrip: - Enabled: false + Enabled: true # TODO: Enable RangeInclude Cop. # Use `Range#cover?` instead of `Range#include?`. diff --git a/CHANGELOG b/CHANGELOG index ed151df9e25..fed3caef7e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,22 +1,48 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.8.0 (unreleased) + - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) + - Project#open_branches has been cleaned up and no longer loads entire records into memory. + - Log to application.log when an admin starts and stops impersonating a user + - Updated gitlab_git to 10.1.0 + - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists + - Reduce delay in destroying a project from 1-minute to immediately - Make build status canceled if any of the jobs was canceled and none failed + - Upgrade Sidekiq to 4.1.2 + - Sanitize repo paths in new project error message + - Bump mail_room to 0.7.0 to fix stuck IDLE connections - Remove future dates from contribution calendar graph. + - Support e-mail notifications for comments on project snippets - Use ActionDispatch Remote IP for Akismet checking - Fix error when visiting commit builds page before build was updated - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project + - Update SVG sanitizer to conform to SVG 1.1 - Updated search UI - Display informative message when new milestone is created - - Replace Devise Async with Devise ActiveJob integration. !3902 (Connor Shea) - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - Added button to toggle whitespaces changes on diff view - - Backport GitLab Enterprise support from EE + - Backport GitHub Enterprise import support from EE + - Create tags using Rugged for performance reasons. !3745 - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) + - Added multiple colors for labels in dropdowns when dups happen. + - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) + - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) + - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) + - Expire repository exists? and has_visible_content? caches after a push if necessary + - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi) + +v 8.7.3 + - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented + - Merge request widget displays TeamCity build state and code coverage correctly again. + - Fix the line code when importing PR review comments from GitHub. !4010 + - Wikis are now initialized on legacy projects when checking repositories v 8.7.2 - The "New Branch" button is now loaded asynchronously + - Fix error 500 when trying to create a wiki page + - Updated spacing between notification label and button + - Label titles in filters are now escaped properly v 8.7.1 - Throttle the update of `project.last_activity_at` to 1 minute. !3848 @@ -138,13 +164,25 @@ v 8.7.0 - Import GitHub labels - Add option to filter by "Owned projects" on dashboard page - Import GitHub milestones - - Fix emoji catgories in the emoji picker - Execute system web hooks on push to the project - Allow enable/disable push events for system hooks - Fix GitHub project's link in the import page when provider has a custom URL - Add RAW build trace output and button on build page - Add incremental build trace update into CI API +v 8.6.8 + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via Git branch and tag names + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent XSS via label drop-down + - Prevent information disclosure via milestone API + - Prevent information disclosure via snippet API + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + v 8.6.7 - Fix persistent XSS vulnerability in `commit_person_link` helper - Fix persistent XSS vulnerability in Label and Milestone dropdowns @@ -286,6 +324,17 @@ v 8.6.0 - Trigger a todo for mentions on commits page - Let project owners and admins soft delete issues and merge requests +v 8.5.12 + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via Git branch and tag names + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent information disclosure via snippet API + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + v 8.5.11 - Fix persistent XSS vulnerability in `commit_person_link` helper @@ -436,6 +485,17 @@ v 8.5.0 - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Add Todos +v 8.4.10 + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via Git branch and tag names + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent information disclosure via snippet API + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + v 8.4.9 - Fix persistent XSS vulnerability in `commit_person_link` helper @@ -561,6 +621,15 @@ v 8.4.0 - Add IP check against DNSBLs at account sign-up - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching +v 8.3.9 + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + v 8.3.8 - Fix persistent XSS vulnerability in `commit_person_link` helper @@ -670,6 +739,17 @@ v 8.3.0 - Expose Git's version in the admin area - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye) +v 8.2.5 + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via `window.opener` + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +v 8.2.4 + - Bump Git version requirement to 2.7.4 + v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 084c2d616a9..9fe4cf7b0f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -142,6 +142,16 @@ code snippet right after your description in a new line: `~"feature proposal"`. Please keep feature proposals as small and simple as possible, complex ones might be edited to make them small and simple. +You are encouraged to use the template below for feature proposals. + +``` +## Description including problem, use cases, benefits, and/or goals + +## Proposal + +## Links / references +``` + For changes in the interface, it can be helpful to create a mockup first. If you want to create something yourself, consider opening an issue first to discuss whether it is interesting to include this in GitLab. @@ -349,7 +359,7 @@ on your merge request feel free to mention one of the Merge Marshalls in the Please ensure that your merge request meets the contribution acceptance criteria. When having your code reviewed and when reviewing merge requests please take the -[Thoughtbot code review guide] into account. +[code review guidelines](doc/development/code_review.md) into account. ### Merge request description format @@ -523,4 +533,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design [free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12 [`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/ -[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review @@ -19,7 +19,8 @@ gem "pg", '~> 0.18.2', group: :postgres # Authentication libraries gem 'devise', '~> 3.5.4' -gem 'doorkeeper', '~> 2.2.0' +gem 'doorkeeper', '~> 3.1' +gem 'devise-async', '~> 0.9.0' gem 'omniauth', '~> 1.3.1' gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-azure-oauth2', '~> 0.0.6' @@ -271,7 +272,7 @@ group :development, :test do gem 'database_cleaner', '~> 1.4.0' gem 'factory_girl_rails', '~> 4.6.0' - gem 'rspec-rails', '~> 3.3.0' + gem 'rspec-rails', '~> 3.4.0' gem 'rspec-retry' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' @@ -321,7 +322,7 @@ gem "newrelic_rpm", '~> 3.14' gem 'octokit', '~> 4.3.0' -gem "mail_room", "~> 0.6.1" +gem "mail_room", "~> 0.7" gem 'email_reply_parser', '~> 0.5.8' diff --git a/Gemfile.lock b/Gemfile.lock index 2b1cfdc9bb2..36efb66665f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -135,7 +135,7 @@ GEM execjs coffee-script-source (1.10.0) colorize (0.7.7) - concurrent-ruby (1.0.1) + concurrent-ruby (1.0.2) connection_pool (2.2.0) coveralls (0.8.13) json (~> 1.8) @@ -165,6 +165,8 @@ GEM responders thread_safe (~> 0.1) warden (~> 1.2.3) + devise-async (0.9.0) + devise (~> 3.2) devise-two-factor (2.0.1) activesupport attr_encrypted (~> 1.3.2) @@ -174,7 +176,7 @@ GEM diff-lcs (1.2.5) diffy (3.0.7) docile (1.1.5) - doorkeeper (2.2.2) + doorkeeper (3.1.0) railties (>= 3.2) dropzonejs-rails (0.7.2) rails (> 3.1) @@ -352,7 +354,7 @@ GEM posix-spawn (~> 0.3) gitlab_emoji (0.3.1) gemojione (~> 2.2, >= 2.2.1) - gitlab_git (10.0.1) + gitlab_git (10.1.0) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -466,7 +468,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.6.1) + mail_room (0.7.0) method_source (0.8.2) mime-types (2.99.1) mimemagic (0.3.0) @@ -663,29 +665,29 @@ GEM chunky_png rqrcode-rails3 (0.1.7) rqrcode (>= 0.4.2) - rspec (3.3.0) - rspec-core (~> 3.3.0) - rspec-expectations (~> 3.3.0) - rspec-mocks (~> 3.3.0) - rspec-core (3.3.2) - rspec-support (~> 3.3.0) - rspec-expectations (3.3.1) + rspec (3.4.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-core (3.4.4) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) - rspec-mocks (3.3.2) + rspec-support (~> 3.4.0) + rspec-mocks (3.4.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) - rspec-rails (3.3.3) + rspec-support (~> 3.4.0) + rspec-rails (3.4.2) actionpack (>= 3.0, < 4.3) activesupport (>= 3.0, < 4.3) railties (>= 3.0, < 4.3) - rspec-core (~> 3.3.0) - rspec-expectations (~> 3.3.0) - rspec-mocks (~> 3.3.0) - rspec-support (~> 3.3.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-support (~> 3.4.0) rspec-retry (0.4.5) rspec-core - rspec-support (3.3.0) + rspec-support (3.4.1) rubocop (0.38.0) parser (>= 2.3.0.6, < 3.0) powerpack (~> 0.1) @@ -739,7 +741,7 @@ GEM rack shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (4.1.1) + sidekiq (4.1.2) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) redis (~> 3.2, >= 3.2.1) @@ -922,9 +924,10 @@ DEPENDENCIES database_cleaner (~> 1.4.0) default_value_for (~> 3.0.0) devise (~> 3.5.4) + devise-async (~> 0.9.0) devise-two-factor (~> 2.0.0) diffy (~> 3.0.3) - doorkeeper (~> 2.2.0) + doorkeeper (~> 3.1) dropzonejs-rails (~> 0.7.1) email_reply_parser (~> 0.5.8) email_spec (~> 1.6.0) @@ -964,7 +967,7 @@ DEPENDENCIES letter_opener_web (~> 1.3.0) licensee (~> 8.0.0) loofah (~> 2.0.3) - mail_room (~> 0.6.1) + mail_room (~> 0.7) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) @@ -1014,7 +1017,7 @@ DEPENDENCIES responders (~> 2.0) rouge (~> 1.10.1) rqrcode-rails3 (~> 0.1.7) - rspec-rails (~> 3.3.0) + rspec-rails (~> 3.4.0) rspec-retry rubocop (~> 0.38.0) ruby-fogbugz (~> 0.2.1) @@ -1061,4 +1064,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.11.2 + 1.12.1 diff --git a/README.md b/README.md index afa60116ebb..5e41665a6ba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GitLab -[![build status](https://ci.gitlab.com/projects/1/status.svg?ref=master)](https://ci.gitlab.com/projects/1?ref=master) +[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Build Status](https://semaphoreci.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/400484/shields_badge.svg)](https://semaphoreci.com/gitlabhq/gitlabhq) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.svg?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master) @@ -20,6 +20,10 @@ To see how GitLab looks please see the [features page on our website](https://ab - Completely free and open source (MIT Expat license) - Powered by [Ruby on Rails](https://github.com/rails/rails) +## Hiring + +We're hiring developers, support people, and production engineers all the time, please see our [jobs page](https://about.gitlab.com/jobs/). + ## Editions There are two editions of GitLab: @@ -80,7 +84,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab ## GitLab release cycle -For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/). +For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/release-tools/blob/master/README.md). ## Upgrading diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index fcba9818726..bf95e06b4e5 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,58 +1,58 @@ class @AwardsHandler - constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @unicodes) -> - $(".js-add-award").on "click", (event) => + constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) -> + $('.js-add-award').on 'click', (event) => event.stopPropagation() event.preventDefault() @showEmojiMenu() - $("html").on 'click', (event) -> - if !$(event.target).closest(".emoji-menu").length - if $(".emoji-menu").is(":visible") - $(".emoji-menu").removeClass "is-visible" + $('html').on 'click', (event) -> + if !$(event.target).closest('.emoji-menu').length + if $('.emoji-menu').is(':visible') + $('.emoji-menu').removeClass 'is-visible' - $(".awards") - .off "click" - .on "click", ".js-emoji-btn", @handleClick + $('.awards') + .off 'click' + .on 'click', '.js-emoji-btn', @handleClick @renderFrequentlyUsedBlock() handleClick: (e) -> e.preventDefault() emoji = $(this) - .find(".icon") - .data "emoji" + .find('.icon') + .data 'emoji' - if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown" - awards_handler.addAward "thumbsdown" + if emoji is 'thumbsup' and awardsHandler.didUserClickEmoji $(this), 'thumbsdown' + awardsHandler.addAward 'thumbsdown' - else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup" - awards_handler.addAward "thumbsup" + else if emoji is 'thumbsdown' and awardsHandler.didUserClickEmoji $(this), 'thumbsup' + awardsHandler.addAward 'thumbsup' - awards_handler.addAward emoji + awardsHandler.addAward emoji $(this).trigger 'blur' didUserClickEmoji: (that, emoji) -> - if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title") - $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1 + if $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title') + $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title').indexOf('me') > -1 showEmojiMenu: -> - if $(".emoji-menu").length - if $(".emoji-menu").is ".is-visible" - $(".emoji-menu").removeClass "is-visible" - $("#emoji_search").blur() + if $('.emoji-menu').length + if $('.emoji-menu').is '.is-visible' + $('.emoji-menu').removeClass 'is-visible' + $('#emoji_search').blur() else - $(".emoji-menu").addClass "is-visible" - $("#emoji_search").focus() + $('.emoji-menu').addClass 'is-visible' + $('#emoji_search').focus() else - $('.js-add-award').addClass "is-loading" - $.get @get_emojis_url, (response) => - $('.js-add-award').removeClass "is-loading" - $(".js-award-holder").append response + $('.js-add-award').addClass 'is-loading' + $.get @getEmojisUrl, (response) => + $('.js-add-award').removeClass 'is-loading' + $('.js-award-holder').append response setTimeout => - $(".emoji-menu").addClass "is-visible" - $("#emoji_search").focus() + $('.emoji-menu').addClass 'is-visible' + $('#emoji_search').focus() @setupSearch() , 200 @@ -60,7 +60,7 @@ class @AwardsHandler @postEmoji emoji, => @addAwardToEmojiBar(emoji) - $(".emoji-menu").removeClass "is-visible" + $('.emoji-menu').removeClass 'is-visible' addAwardToEmojiBar: (emoji) -> @addEmojiToFrequentlyUsedList(emoji) @@ -69,9 +69,9 @@ class @AwardsHandler if @isActive(emoji) @decrementCounter(emoji) else - counter = @findEmojiIcon(emoji).siblings(".js-counter") + counter = @findEmojiIcon(emoji).siblings('.js-counter') counter.text(parseInt(counter.text()) + 1) - counter.parent().addClass("active") + counter.parent().addClass('active') @addMeToAuthorList(emoji) else @createEmoji(emoji) @@ -80,47 +80,47 @@ class @AwardsHandler @findEmojiIcon(emoji).length > 0 isActive: (emoji) -> - @findEmojiIcon(emoji).parent().hasClass("active") + @findEmojiIcon(emoji).parent().hasClass('active') decrementCounter: (emoji) -> - counter = @findEmojiIcon(emoji).siblings(".js-counter") + counter = @findEmojiIcon(emoji).siblings('.js-counter') emojiIcon = counter.parent() if parseInt(counter.text()) > 1 counter.text(parseInt(counter.text()) - 1) - emojiIcon.removeClass("active") + emojiIcon.removeClass('active') @removeMeFromAuthorList(emoji) - else if emoji == "thumbsup" || emoji == "thumbsdown" - emojiIcon.tooltip("destroy") + else if emoji == 'thumbsup' || emoji == 'thumbsdown' + emojiIcon.tooltip('destroy') counter.text(0) - emojiIcon.removeClass("active") + emojiIcon.removeClass('active') @removeMeFromAuthorList(emoji) else - emojiIcon.tooltip("destroy") + emojiIcon.tooltip('destroy') emojiIcon.remove() removeMeFromAuthorList: (emoji) -> - award_block = @findEmojiIcon(emoji).parent() - authors = award_block - .attr("data-original-title") - .split(", ") - authors.splice(authors.indexOf("me"),1) - award_block - .closest(".js-emoji-btn") - .attr("data-original-title", authors.join(", ")) - @resetTooltip(award_block) + awardBlock = @findEmojiIcon(emoji).parent() + authors = awardBlock + .attr('data-original-title') + .split(', ') + authors.splice(authors.indexOf('me'),1) + awardBlock + .closest('.js-emoji-btn') + .attr('data-original-title', authors.join(', ')) + @resetTooltip(awardBlock) addMeToAuthorList: (emoji) -> - award_block = @findEmojiIcon(emoji).parent() - origTitle = award_block.attr("data-original-title").trim() + awardBlock = @findEmojiIcon(emoji).parent() + origTitle = awardBlock.attr('data-original-title').trim() authors = [] if origTitle authors = origTitle.split(', ') - authors.push("me") - award_block.attr("data-original-title", authors.join(", ")) - @resetTooltip(award_block) + authors.push('me') + awardBlock.attr('data-original-title', authors.join(', ')) + @resetTooltip(awardBlock) resetTooltip: (award) -> - award.tooltip("destroy") + award.tooltip('destroy') # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout. setTimeout (-> @@ -139,20 +139,28 @@ class @AwardsHandler "</button>" ) - emoji_node = $(nodes.join("\n")) - .insertBefore(".js-award-holder") - .find(".emoji-icon") - .data("emoji", emoji) + $(nodes.join("\n")) + .insertBefore('.js-award-holder') + .find('.emoji-icon') + .data('emoji', emoji) $('.award-control').tooltip() resolveNameToCssClass: (emoji) -> - "emoji-#{@unicodes[emoji]}" + emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']") + + if emojiIcon.length > 0 + unicodeName = emojiIcon.data('unicode-name') + else + # Find by alias + unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name') + + "emoji-#{unicodeName}" postEmoji: (emoji, callback) -> - $.post @post_emoji_url, { note: { + $.post @postEmojiUrl, { note: { note: ":#{emoji}:" - noteable_type: @noteable_type - noteable_id: @noteable_id + noteable_type: @noteableType + noteable_id: @noteableId }},(data) -> if data.ok callback.call() @@ -166,42 +174,42 @@ class @AwardsHandler }, 200) addEmojiToFrequentlyUsedList: (emoji) -> - frequently_used_emojis = @getFrequentlyUsedEmojis() - frequently_used_emojis.push(emoji) - $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 }) + frequentlyUsedEmojis = @getFrequentlyUsedEmojis() + frequentlyUsedEmojis.push(emoji) + $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }) getFrequentlyUsedEmojis: -> - frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",") - _.compact(_.uniq(frequently_used_emojis)) + frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',') + _.compact(_.uniq(frequentlyUsedEmojis)) renderFrequentlyUsedBlock: -> if $.cookie('frequently_used_emojis') - frequently_used_emojis = @getFrequentlyUsedEmojis() + frequentlyUsedEmojis = @getFrequentlyUsedEmojis() - ul = $("<ul>") + ul = $('<ul>') - for emoji in frequently_used_emojis + for emoji in frequentlyUsedEmojis do (emoji) -> - $(".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")) + $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used')) setupSearch: -> - $("input.emoji-search").keyup (ev) => + $('input.emoji-search').keyup (ev) => term = $(ev.target).val() # Clean previous search results - $("ul.emoji-menu-search, h5.emoji-search").remove() + $('ul.emoji-menu-search, h5.emoji-search').remove() if term # Generate a search result block - h5 = $("<h5>").text("Search results").addClass("emoji-search") - found_emojis = @searchEmojis(term).show() - ul = $("<ul>").addClass("emoji-menu-list emoji-menu-search").append(found_emojis) - $(".emoji-menu-content ul, .emoji-menu-content h5").hide() - $(".emoji-menu-content").append(h5).append(ul) + h5 = $('<h5>').text('Search results').addClass('emoji-search') + foundEmojis = @searchEmojis(term).show() + ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis) + $('.emoji-menu-content ul, .emoji-menu-content h5').hide() + $('.emoji-menu-content').append(h5).append(ul) else - $(".emoji-menu-content").children().show() + $('.emoji-menu-content').children().show() searchEmojis: (term)-> $(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone() diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 7d61cd9bf73..995fd768603 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -163,6 +163,21 @@ class @LabelsSelect $.ajax( url: labelUrl ).done (data) -> + data = _.chain data + .groupBy (label) -> + label.title + .map (label) -> + color = _.map label, (dup) -> + dup.color + + return { + id: label[0].id + title: label[0].title + color: color + duplicate: color.length > 1 + } + .value() + if $dropdown.hasClass 'js-extra-options' if showNo data.unshift( @@ -178,6 +193,7 @@ class @LabelsSelect if data.length > 2 data.splice 2, 0, 'divider' + callback data renderRow: (label) -> @@ -192,11 +208,31 @@ class @LabelsSelect if $dropdown.hasClass('js-multiselect') and removesAll selectedClass.push 'dropdown-clear-active' - color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else "" + if label.duplicate + spacing = 100 / label.color.length + + # Reduce the colors to 4 + label.color = label.color.filter (color, i) -> + i < 4 + + color = _.map(label.color, (color, i) -> + percentFirst = Math.floor(spacing * i) + percentSecond = Math.floor(spacing * (i + 1)) + "#{color} #{percentFirst}%,#{color} #{percentSecond}% " + ).join(',') + color = "linear-gradient(#{color})" + else + if label.color? + color = label.color[0] + + if color + colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>" + else + colorEl = '' "<li> <a href='#' class='#{selectedClass.join(' ')}'> - #{color} + #{colorEl} #{_.escape(label.title)} </a> </li>" diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 065626beeb8..17a5a057a94 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -68,20 +68,18 @@ class @MergeRequestWidget $.getJSON @opts.ci_status_url, (data) => @readyForCICheck = true - if @firstCICheck - @firstCICheck = false - @opts.ci_status = data.status - - if @opts.ci_status is '' - @opts.ci_status = data.status + if data.status is '' return - if data.status isnt @opts.ci_status and data.status? + if @firstCiCheck || data.status isnt @opts.ci_status and data.status? + @opts.ci_status = data.status @showCIStatus data.status if data.coverage @showCICoverage data.coverage - if showNotification + # The first check should only update the UI, a notification + # should only be displayed on status changes + if showNotification and not @firstCiCheck status = @ciLabelForStatus(data.status) if status is "preparing" @@ -104,8 +102,7 @@ class @MergeRequestWidget @close() Turbolinks.visit _this.opts.builds_path ) - - @opts.ci_status = data.status + @firstCiCheck = false showCIStatus: (state) -> $('.ci_widget').hide() diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 82e210fed7d..efb3e8e2198 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -167,8 +167,8 @@ class @Notes return if note.award - awards_handler.addAwardToEmojiBar(note.note) - awards_handler.scrollToAwards() + awardsHandler.addAwardToEmojiBar(note.note) + awardsHandler.scrollToAwards() # render note if it not present in loaded list # or skip if rendered @@ -373,11 +373,11 @@ class @Notes new GLForm form if scrollTo? and myLastNote? - # scroll to the bottom + # scroll to the bottom # so the open of the last element doesn't make a jump $('html, body').scrollTop($(document).height()); $('html, body').animate({ - scrollTop: myLastNote.offset().top - 150 + scrollTop: myLastNote.offset().top - 150 }, 500, -> $noteText = form.find(".js-note-text") $noteText.focus() diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index c85ab9148d0..560de9fc0bd 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -25,6 +25,7 @@ @import "framework/lists.scss"; @import "framework/markdown_area.scss"; @import "framework/mobile.scss"; +@import "framework/modal.scss"; @import "framework/nav.scss"; @import "framework/pagination.scss"; @import "framework/progress.scss"; diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 5aa425dab6c..f5ce70b606b 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -28,6 +28,7 @@ &.s46 { width: 46px; height: 46px; margin-right: 15px; } &.s48 { width: 48px; height: 48px; margin-right: 10px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; } + &.s70 { width: 70px; height: 70px; margin-right: 14px; } &.s90 { width: 90px; height: 90px; margin-right: 15px; } &.s110 { width: 110px; height: 110px; margin-right: 15px; } &.s140 { width: 140px; height: 140px; margin-right: 20px; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index e72e4aa47ef..434a26d57c6 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -18,7 +18,7 @@ line-height: 36px; } -.gray-content-block { +.row-content-block { margin-top: 0; margin-bottom: -$gl-padding; background-color: $background-color; @@ -81,6 +81,11 @@ margin-left: 10px; } } + + &.build-content { + background-color: $white-light; + border-top: none; + } } .cover-block { @@ -113,7 +118,7 @@ line-height: 1.1; h1 { - color: #313236; + color: $gl-gray-dark; margin-bottom: 6px; font-size: 23px; } @@ -150,6 +155,41 @@ right: auto; } } + + &.groups-cover-block { + background: $white-light; + border-bottom: 1px solid $border-color; + text-align: left; + padding: 24px 0; + + .group-info { + .cover-title { + margin-top: 9px; + } + + p { + margin-bottom: 0; + } + } + + @media (max-width: $screen-xs-max) { + text-align: center; + + .avatar { + float: none; + } + } + } + + .group-info { + + h1 { + display: inline; + font-weight: normal; + font-size: 24px; + color: $gl-title-color; + } + } } .block-connector { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 062da397b6b..eaf85bb17ca 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -59,7 +59,7 @@ } @mixin btn-gray { - @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236); + @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark); } @mixin btn-white { @@ -251,3 +251,10 @@ .btn-file-option { background: linear-gradient(180deg, $white-light 25%, $gray-light 100%); } + +.btn-build { + margin-left: 10px; + i { + color: $gl-icon-color; + } +} diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 5fa10d29a87..97f9d582007 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -30,6 +30,10 @@ header { border: none; border-bottom: 1px solid $border-color; + &.with-horizontal-nav { + border-bottom: none; + } + .container-fluid { width: 100% !important; filter: none; diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 7eb451c124e..33cbee85987 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -30,7 +30,7 @@ } .rss-btn { - display: none !important; + display: none; } .project-home-links { diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss new file mode 100644 index 00000000000..26ad2870aa0 --- /dev/null +++ b/app/assets/stylesheets/framework/modal.scss @@ -0,0 +1,22 @@ +.modal-body { + position: relative; + overflow-y: auto; + padding: 15px; + + .form-actions { + margin: -$gl-padding+1; + margin-top: 15px; + } + + .text-danger { + font-weight: bold; + } +} + +body.modal-open { + overflow: hidden; +} + +.modal .modal-dialog { + width: 860px; +} diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 5fe687dcec3..7c18e93a261 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -26,8 +26,8 @@ } &.active a { - color: #000; - border-bottom: 2px solid #4688f1; + border-bottom: 2px solid $link-underline-blue; + color: $black; } .badge { @@ -140,6 +140,12 @@ } } + .project-filter-form { + input { + background-color: $background-color; + } + } + @media (max-width: $screen-xs-max) { padding-bottom: 0; @@ -187,13 +193,31 @@ } .layout-nav { + position: fixed; + top: $header-height; + width: 100%; + z-index: 1; background: $background-color; border-bottom: 1px solid $border-color; + transition-duration: .3s; .controls { float: right; - position: relative; - top: 10px; + padding: 7px 5px 0 0; + + i { + color: $layout-link-gray; + } + + .fa-rss, + .fa-cog { + font-size: 16px; + } + + .fa-caret-down { + margin-left: 5px; + color: $gl-icon-color; + } .dropdown { margin-left: 7px; @@ -202,5 +226,34 @@ .nav-links { border-bottom: none; + height: 51px; + white-space: nowrap; + overflow-x: auto; + + li { + + a { + padding-top: 10px; + } + + a, i { + color: $layout-link-gray; + } + + &.active { + a, i { + color: $black; + } + } + + .badge { + color: $gl-icon-color; + } + } } + +} + +.page-with-layout-nav { + margin-top: 50px; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 18189e985c4..e940fd7286e 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -3,6 +3,7 @@ position: absolute; width: 58px; cursor: pointer; + margin-top: 8px; } .page-with-sidebar { @@ -62,7 +63,7 @@ float: left; height: $header-height; width: 100%; - padding: 11px 0 11px 22px; + padding-left: 22px; overflow: hidden; outline: none; transition-duration: .3s; @@ -85,7 +86,7 @@ margin: 0; margin-left: 50px; font-size: 19px; - line-height: 41px; + line-height: 50px; font-weight: normal; } } @@ -254,6 +255,10 @@ } } } + + .layout-nav { + padding-right: $sidebar_collapsed_width; + } } .page-sidebar-expanded { @@ -280,6 +285,10 @@ } } } + + .layout-nav { + padding-right: $sidebar_width; + } } .right-sidebar-collapsed { diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 75b770ae5a2..b42075c98d0 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -32,13 +32,11 @@ table { th { background-color: $background-color; font-weight: normal; - font-size: 15px; - border-bottom: 1px solid $border-color; + border-bottom: none; } td { border-color: $table-border-color; - border-bottom: 1px solid $border-color; } } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 96bab7880c2..6a45c34ccbb 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -81,7 +81,7 @@ // Labels .label { - padding: 2px 4px; + padding: 4px 5px; font-size: 13px; font-style: normal; font-weight: normal; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index b2535ddf4bd..2779cd56788 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -42,14 +42,14 @@ margin: 24px 0 12px; padding: 0 0 10px; border-bottom: 1px solid #e7e9ed; - color: #313236; + color: $gl-gray-dark; } h2 { font-size: 1.2em; font-weight: 600; margin: 24px 0 12px; - color: #313236; + color: $gl-gray-dark; } h3 { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index b8ed7e8a74c..ccb4e5381b7 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,7 +12,7 @@ $gutter_inner_width: 258px; */ $border-color: #e5e5e5; $focus-border-color: #3aabf0; -$table-border-color: #eef0f2; +$table-border-color: #ececec; $background-color: #fafafa; /* @@ -20,7 +20,7 @@ $background-color: #fafafa; */ $gl-font-size: 15px; $gl-title-color: #333; -$gl-text-color: #555; +$gl-text-color: #5c5c5c; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; @@ -30,6 +30,7 @@ $gl-placeholder-color: #8f8f8f; $gl-icon-color: $gl-placeholder-color; $gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; +$gl-gray-dark: #313236; $gl-header-color: $gl-title-color; /* @@ -65,7 +66,7 @@ $gl-padding-top: 10px; $row-hover: #f4f8fe; $progress-color: #c0392b; $avatar_radius: 50%; -$header-height: 58px; +$header-height: 50px; $fixed-layout-width: 1280px; $gl-avatar-size: 40px; $error-exclamation-point: #e62958; @@ -74,6 +75,9 @@ $btn-transparent-color: #8f8f8f; $settings-icon-size: 18px; $provider-btn-group-border: #e5e5e5; $provider-btn-not-active-color: #4688f1; +$link-underline-blue: #4a8bee; +$layout-link-gray: #7e7c7c; +$todo-alert-blue: #428bca; /* * Color schema @@ -108,6 +112,7 @@ $red-light: #e52c5a; $red-normal: #d22852; $red-dark: darken($red-normal, 5%); +$black: #000; $black-transparent: rgba(0, 0, 0, 0.3); $border-white-light: #f1f2f4; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 201f3e5ca46..aa41565f812 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -83,3 +83,12 @@ } } } + +table.builds { + + .build-link { + a { + color: $gl-dark-link-color; + } + } +} diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 358d2f4ab9d..c2cd227571f 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -31,9 +31,23 @@ } .commit-committer-link, .commit-author-link { - color: #444; + color: $gl-gray; font-weight: bold; } + + .time_ago { + margin-left: 8px; + } + + .fa-clipboard { + color: $dropdown-title-btn-color; + } + + .commit-info { + &.branches { + margin-left: 8px; + } + } } .commit-box { @@ -42,7 +56,7 @@ .commit-title { margin: 0; font-size: 23px; - color: #313236; + color: $gl-gray-dark; } .commit-description { @@ -83,6 +97,14 @@ } } +.commit-action-buttons { + i { + color: $gl-icon-color; + font-size: 13px; + margin-right: 3px; + } +} + /* * Commit message textarea for web editor and * custom merge request message diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 3438dbe4958..5e61e61d85c 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -22,7 +22,7 @@ .title { margin: 0; font-size: 23px; - color: #313236; + color: $gl-gray-dark; } .description { diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index ee95bdf488e..4a95b7b852e 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -55,25 +55,6 @@ } } -.modal-body { - position: relative; - overflow-y: auto; - padding: 15px; - - .form-actions { - margin: -$gl-padding+1; - margin-top: 15px; - } -} - -body.modal-open { - overflow: hidden; -} - -.modal .modal-dialog { - width: 860px; -} - .documentation { padding: 7px; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 4ef548ffbe7..c4005ba1e69 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -104,7 +104,7 @@ font-weight: 600; font-size: 17px; margin: 5px 0; - color: #313236; + color: $gl-gray-dark; } p:last-child { @@ -136,7 +136,7 @@ } .label-branch { - color: #313236; + color: $gl-gray-dark; font-family: $monospace_font; font-weight: bold; overflow: hidden; @@ -272,3 +272,19 @@ display: inline-block; width: 250px; } + +.table-holder { + .builds { + + th { + background-color: $white-light; + color: $gl-placeholder-color; + } + + th, + td { + padding: 16px; + } + } +} + diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index d0e72a4422c..b94f524b513 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -28,7 +28,7 @@ li.milestone { // Issue title span a { - color: rgba(0,0,0,0.64); + color: $gl-text-color; } } } @@ -51,7 +51,7 @@ li.milestone { margin-top: 7px; .issuable-number { - color: rgba(0,0,0,0.44); + color: $gl-placeholder-color; margin-right: 5px; } .avatar { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 9619d65db85..624c8249f7e 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -114,10 +114,6 @@ ul.notes { word-break: keep-all; } } - - a { - word-break: break-all; - } } .note-header { @@ -172,6 +168,11 @@ ul.notes { .notes { background-color: $white-light; } + + a code { + top: 0; + margin-right: 0; + } } } } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 01f98479623..abc5a0e9877 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -205,3 +205,21 @@ text-align: center; } } + +.user-profile { + @media (max-width: $screen-xs-max) { + .cover-block { + padding-top: 20px; + } + + .cover-controls { + position: static; + margin-bottom: 20px; + + .btn { + display: inline-block; + width: 48%; + } + } + } +} diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 99108e9bfc4..c20f04653fc 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -178,7 +178,7 @@ .option-title { font-weight: normal; display: inline-block; - color: #313236; + color: $gl-gray-dark; } .option-descr { diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index dbb6daf0d70..2370d35924e 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -1,7 +1,7 @@ .container-fluid { .ci-status { padding: 2px 7px; - margin-right: 5px; + margin-right: 10px; border: 1px solid #eee; white-space: nowrap; @include border-radius(4px); diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 75f78569e3c..e51c3491dae 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -6,9 +6,16 @@ .navbar-nav { li { .badge.todos-pending-count { - background-color: $gl-icon-color; margin-top: -5px; font-weight: normal; + background: $todo-alert-blue; + margin-left: -17px; + font-size: 11px; + color: white; + padding: 3px; + padding-top: 1px; + padding-bottom: 1px; + border-radius: 3px; } } } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 25b5e95583e..a84fc2e0318 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -16,7 +16,7 @@ tr { > td, > th { - line-height: 26px; + line-height: 23px; } &:hover { diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb index 9083bfb41cf..cf795d977ce 100644 --- a/app/controllers/admin/application_controller.rb +++ b/app/controllers/admin/application_controller.rb @@ -6,12 +6,6 @@ class Admin::ApplicationController < ApplicationController layout 'admin' def authenticate_admin! - return render_404 unless current_user.is_admin? - end - - def authorize_impersonator! - if session[:impersonator_id] - User.find_by!(username: session[:impersonator_id]).admin? - end + render_404 unless current_user.is_admin? end end diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 93c4894ea0f..4e85b6b4cf2 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -39,6 +39,12 @@ class Admin::HooksController < Admin::ApplicationController end def hook_params - params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events) + params.require(:hook).permit( + :enable_ssl_verification, + :push_events, + :tag_push_events, + :token, + :url + ) end end diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb deleted file mode 100644 index bf98af78615..00000000000 --- a/app/controllers/admin/impersonation_controller.rb +++ /dev/null @@ -1,38 +0,0 @@ -class Admin::ImpersonationController < Admin::ApplicationController - skip_before_action :authenticate_admin!, only: :destroy - - before_action :user - before_action :authorize_impersonator! - - def create - if @user.blocked? - flash[:alert] = "You cannot impersonate a blocked user" - - redirect_to admin_user_path(@user) - else - session[:impersonator_id] = current_user.username - session[:impersonator_return_to] = admin_user_path(@user) - - warden.set_user(user, scope: 'user') - - flash[:alert] = "You are impersonating #{user.username}." - - redirect_to root_path - end - end - - def destroy - redirect = session[:impersonator_return_to] - - warden.set_user(user, scope: 'user') - - session[:impersonator_return_to] = nil - session[:impersonator_id] = nil - - redirect_to redirect || root_path - end - - def user - @user ||= User.find_by!(username: params[:id] || session[:impersonator_id]) - end -end diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb new file mode 100644 index 00000000000..8be35f00a77 --- /dev/null +++ b/app/controllers/admin/impersonations_controller.rb @@ -0,0 +1,26 @@ +class Admin::ImpersonationsController < Admin::ApplicationController + skip_before_action :authenticate_admin! + before_action :authenticate_impersonator! + + def destroy + original_user = current_user + + warden.set_user(impersonator, scope: :user) + + Gitlab::AppLogger.info("User #{original_user.username} has stopped impersonating #{impersonator.username}") + + session[:impersonator_id] = nil + + redirect_to admin_user_path(original_user) + end + + private + + def impersonator + @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id] + end + + def authenticate_impersonator! + render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked? + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 9abf08d0e19..f2f654c7bcd 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -31,6 +31,24 @@ class Admin::UsersController < Admin::ApplicationController user end + def impersonate + if user.blocked? + flash[:alert] = "You cannot impersonate a blocked user" + + redirect_to admin_user_path(user) + else + session[:impersonator_id] = current_user.id + + warden.set_user(user, scope: :user) + + Gitlab::AppLogger.info("User #{current_user.username} has started impersonating #{user.username}") + + flash[:alert] = "You are now impersonating #{user.username}" + + redirect_to root_path + end + end + def block if user.block redirect_back_or_admin_user(notice: "Successfully blocked") diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1c53b0b21a3..17b3f49aed1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -117,7 +117,7 @@ class ApplicationController < ActionController::Base end def after_sign_out_path_for(resource) - current_application_settings.after_sign_out_path || new_user_session_path + current_application_settings.after_sign_out_path.presence || new_user_session_path end def abilities diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 1420b96840c..a52c614b259 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -15,7 +15,7 @@ class Projects::CommitsController < Projects::ApplicationController if search.present? @repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact else - @repository.commits(@ref, @path, @limit, @offset) + @repository.commits(@ref, path: @path, limit: @limit, offset: @offset) end @note_counts = project.notes.where(commit_id: @commits.map(&:id)). diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index d13ea9f34b6..092ef32e6e3 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -17,7 +17,7 @@ class Projects::GraphsController < Projects::ApplicationController end def commits - @commits = @project.repository.commits(@ref, nil, 2000, 0, true) + @commits = @project.repository.commits(@ref, limit: 2000, skip_merges: true) @commits_graph = Gitlab::Graphs::Commits.new(@commits) @commits_per_week_days = @commits_graph.commits_per_week_days @commits_per_time = @commits_graph.commits_per_time @@ -55,7 +55,7 @@ class Projects::GraphsController < Projects::ApplicationController private def fetch_graph - @commits = @project.repository.commits(@ref, nil, 6000, 0, true) + @commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true) @log = [] @commits.each do |commit| diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 5fd4f855dec..dfa9bd259e8 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -52,8 +52,16 @@ class Projects::HooksController < Projects::ApplicationController end def hook_params - params.require(:hook).permit(:url, :push_events, :issues_events, - :merge_requests_events, :tag_push_events, :note_events, - :build_events, :enable_ssl_verification) + params.require(:hook).permit( + :build_events, + :enable_ssl_verification, + :issues_events, + :merge_requests_events, + :note_events, + :push_events, + :tag_push_events, + :token, + :url + ) end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 9face235baa..016f5dd0005 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -96,6 +96,8 @@ class Projects::IssuesController < Projects::ApplicationController if params[:move_to_project_id].to_i > 0 new_project = Project.find(params[:move_to_project_id]) + return render_404 unless issue.can_move?(current_user, new_project) + move_service = Issues::MoveService.new(project, current_user) @issue = move_service.execute(@issue, new_project) end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index c02bc28acef..0d6c32fabd2 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -40,10 +40,10 @@ class Projects::WikisController < Projects::ApplicationController end def update - @page = @project_wiki.find_page(params[:id]) - return render('empty') unless can?(current_user, :create_wiki, @project) + @page = @project_wiki.find_page(params[:id]) + if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page) redirect_to( namespace_project_wiki_path(@project.namespace, @project, @page), diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 059b88e2253..352bff19383 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -8,6 +8,13 @@ class RegistrationsController < Devise::RegistrationsController def create if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha + # To avoid duplicate form fields on the login page, the registration form + # names fields using `new_user`, but Devise still wants the params in + # `user`. + if params["new_#{resource_name}"].present? && params[resource_name].blank? + params[resource_name] = params.delete(:"new_#{resource_name}") + end + super else flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code." diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index a41172816b8..01cbf91c658 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -51,7 +51,7 @@ class SnippetsFinder snippets = project.snippets.fresh if current_user - if project.team.member?(current_user.id) + if project.team.member?(current_user.id) || current_user.admin? snippets else snippets.public_and_internal diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 474c6f27374..93241b3afb7 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -131,7 +131,7 @@ module BlobHelper # elements and attributes. Note that this whitelist is by no means complete # and may omit some elements. def sanitize_svg(blob) - blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml + blob.data = Gitlab::Sanitizers::SVG.clean(blob.data) blob end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index afe1e11a0da..198d39455d7 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -16,31 +16,49 @@ module IssuesHelper def url_for_project_issues(project = @project, options = {}) return '' if project.nil? - if options[:only_path] - project.issues_tracker.project_path - else - project.issues_tracker.project_url - end + url = + if options[:only_path] + project.issues_tracker.project_path + else + project.issues_tracker.project_url + end + + # Ensure we return a valid URL to prevent possible XSS. + URI.parse(url).to_s + rescue URI::InvalidURIError + '' end def url_for_new_issue(project = @project, options = {}) return '' if project.nil? - if options[:only_path] - project.issues_tracker.new_issue_path - else - project.issues_tracker.new_issue_url - end + url = + if options[:only_path] + project.issues_tracker.new_issue_path + else + project.issues_tracker.new_issue_url + end + + # Ensure we return a valid URL to prevent possible XSS. + URI.parse(url).to_s + rescue URI::InvalidURIError + '' end def url_for_issue(issue_iid, project = @project, options = {}) return '' if project.nil? - if options[:only_path] - project.issues_tracker.issue_path(issue_iid) - else - project.issues_tracker.issue_url(issue_iid) - end + url = + if options[:only_path] + project.issues_tracker.issue_path(issue_iid) + else + project.issues_tracker.issue_url(issue_iid) + end + + # Ensure we return a valid URL to prevent possible XSS. + URI.parse(url).to_s + rescue URI::InvalidURIError + '' end def bulk_update_milestone_options diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 5d86bd490a8..3aa41030453 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -34,10 +34,13 @@ module NavHelper end def nav_header_class - if nav_menu_collapsed? - "header-collapsed" - else - "header-expanded" - end + class_name = + if nav_menu_collapsed? + "header-collapsed" + else + "header-expanded" + end + class_name += " with-horizontal-nav" if defined?(nav) && nav + class_name end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 3d5e61d2c18..f17d02a7a3f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -200,12 +200,8 @@ module ProjectsHelper end def repository_size(project = @project) - "#{project.repository_size} MB" - rescue - # In order to prevent 500 error - # when application cannot allocate memory - # to calculate repo size - just show 'Unknown' - 'unknown' + size_in_bytes = project.repository_size * 1.megabyte + number_to_human_size(size_in_bytes, delimiter: ',', precision: 2) end def default_url_to_repo(project = @project) @@ -341,4 +337,10 @@ module ProjectsHelper ) end end + + def sanitize_repo_path(message) + return '' unless message.present? + + message.strip.gsub(Gitlab.config.gitlab_shell.repos_path.chomp('/'), "[REPOS PATH]") + end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index cdc40b81ee1..96116e916dd 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -28,6 +28,14 @@ module Emails mail_answer_thread(@merge_request, note_thread_options(recipient_id)) end + def note_snippet_email(recipient_id, note_id) + setup_note_mail(note_id, recipient_id) + + @snippet = @note.noteable + @target_url = namespace_project_snippet_url(*note_target_url_options) + mail_answer_thread(@snippet, note_thread_options(recipient_id)) + end + private def note_target_url_options diff --git a/app/models/appearance.rb b/app/models/appearance.rb index 4cf8dd9a8ce..4528760fefa 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -1,3 +1,16 @@ +# == Schema Information +# +# Table name: appearances +# +# id :integer not null, primary key +# title :string +# description :text +# header_logo :string +# logo :string +# created_at :datetime not null +# updated_at :datetime not null +# + class Appearance < ActiveRecord::Base validates :title, presence: true validates :description, presence: true diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 36f88154232..72ec91d2909 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -10,21 +10,20 @@ # sign_in_text :text # created_at :datetime # updated_at :datetime -# home_page_url :string(255) +# home_page_url :string # default_branch_protection :integer default(2) # restricted_visibility_levels :text # version_check_enabled :boolean default(TRUE) # max_attachment_size :integer default(10), not null # default_project_visibility :integer # default_snippet_visibility :integer -# default_group_visibility :integer # restricted_signup_domains :text # user_oauth_applications :boolean default(TRUE) -# after_sign_out_path :string(255) +# after_sign_out_path :string # session_expire_delay :integer default(10080), not null # import_sources :text # help_page_text :text -# admin_notification_email :string(255) +# admin_notification_email :string # shared_runners_enabled :boolean default(TRUE), not null # max_artifacts_size :integer default(100), not null # runners_registration_token :string @@ -32,8 +31,6 @@ # two_factor_grace_period :integer default(48) # metrics_enabled :boolean default(FALSE) # metrics_host :string default("localhost") -# metrics_username :string -# metrics_password :string # metrics_pool_size :integer default(16) # metrics_timeout :integer default(10) # metrics_method_call_threshold :integer default(10) @@ -41,9 +38,16 @@ # recaptcha_site_key :string # recaptcha_private_key :string # metrics_port :integer default(8089) +# metrics_sample_interval :integer default(15) # sentry_enabled :boolean default(FALSE) # sentry_dsn :string +# akismet_enabled :boolean default(FALSE) +# akismet_api_key :string # email_author_in_body :boolean default(FALSE) +# default_group_visibility :integer +# repository_checks_enabled :boolean default(FALSE) +# metrics_packet_size :integer default(1) +# shared_runners_text :text # class ApplicationSetting < ActiveRecord::Base diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index 0ed0dd98a59..44b090260e7 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -4,9 +4,9 @@ # # id :integer not null, primary key # author_id :integer not null -# type :string(255) not null +# type :string not null # entity_id :integer not null -# entity_type :string(255) not null +# entity_type :string not null # details :text # created_at :datetime # updated_at :datetime diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 8a0a8a4c2a9..075ac733bfc 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -8,8 +8,8 @@ # ends_at :datetime # created_at :datetime # updated_at :datetime -# color :string(255) -# font :string(255) +# color :string +# font :string # class BroadcastMessage < ActiveRecord::Base diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index c2ddee527e5..2fea8047147 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # project_id :integer -# status :string(255) +# status :string # finished_at :datetime # trace :text # created_at :datetime @@ -15,19 +15,19 @@ # commit_id :integer # commands :text # job_id :integer -# name :string(255) +# name :string # deploy :boolean default(FALSE) # options :text # allow_failure :boolean default(FALSE), not null -# stage :string(255) +# stage :string # trigger_request_id :integer # stage_idx :integer # tag :boolean -# ref :string(255) +# ref :string # user_id :integer -# type :string(255) -# target_url :string(255) -# description :string(255) +# type :string +# target_url :string +# description :string # artifacts_file :text # gl_project_id :integer # artifacts_metadata :text diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index f2667e5476b..4ac4e0fb8b2 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -4,9 +4,9 @@ # # id :integer not null, primary key # project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) +# ref :string +# sha :string +# before_sha :string # push_data :text # created_at :datetime # updated_at :datetime @@ -14,6 +14,10 @@ # yaml_errors :text # committed_at :datetime # gl_project_id :integer +# status :string +# started_at :datetime +# finished_at :datetime +# duration :integer # module Ci diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 90349a07594..add59a08892 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -3,18 +3,18 @@ # Table name: ci_runners # # id :integer not null, primary key -# token :string(255) +# token :string # created_at :datetime # updated_at :datetime -# description :string(255) +# description :string # contacted_at :datetime # active :boolean default(TRUE), not null # is_shared :boolean default(FALSE) -# name :string(255) -# version :string(255) -# revision :string(255) -# platform :string(255) -# architecture :string(255) +# name :string +# version :string +# revision :string +# platform :string +# architecture :string # module Ci diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 2b9a457c8ab..4f3f4d79fac 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -3,7 +3,7 @@ # Table name: ci_triggers # # id :integer not null, primary key -# token :string(255) +# token :string # project_id :integer # deleted_at :datetime # created_at :datetime diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index e786bd7dd93..4229fe085a1 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -4,11 +4,11 @@ # # id :integer not null, primary key # project_id :integer -# key :string(255) +# key :string # value :text # encrypted_value :text -# encrypted_value_salt :string(255) -# encrypted_value_iv :string(255) +# encrypted_value_salt :string +# encrypted_value_iv :string # gl_project_id :integer # diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index aa56314aa16..1260c448de3 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # project_id :integer -# status :string(255) +# status :string # finished_at :datetime # trace :text # created_at :datetime @@ -15,21 +15,24 @@ # commit_id :integer # commands :text # job_id :integer -# name :string(255) +# name :string # deploy :boolean default(FALSE) # options :text # allow_failure :boolean default(FALSE), not null -# stage :string(255) +# stage :string # trigger_request_id :integer # stage_idx :integer # tag :boolean -# ref :string(255) +# ref :string # user_id :integer -# type :string(255) -# target_url :string(255) -# description :string(255) +# type :string +# target_url :string +# description :string # artifacts_file :text # gl_project_id :integer +# artifacts_metadata :text +# erased_by_id :integer +# erased_at :datetime # class CommitStatus < ActiveRecord::Base diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index d5166e81474..2e4efc4e8d8 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -35,13 +35,14 @@ module Issuable scope :only_opened, -> { with_state(:opened) } scope :only_reopened, -> { with_state(:reopened) } scope :closed, -> { with_state(:closed) } - scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } - scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } + scope :order_milestone_due_desc, -> { outer_join_milestone.reorder('milestones.due_date IS NULL ASC, milestones.due_date DESC, milestones.id DESC') } + scope :order_milestone_due_asc, -> { outer_join_milestone.reorder('milestones.due_date IS NULL ASC, milestones.due_date ASC, milestones.id ASC') } 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 :references_project, -> { references(:project) } scope :non_archived, -> { join_project.where(projects: { archived: false }) } + scope :outer_join_milestone, -> { joins("LEFT OUTER JOIN milestones ON milestones.id = #{table_name}.milestone_id") } delegate :name, :email, diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 9ab663c04ad..43cf625f770 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -7,9 +7,9 @@ # created_at :datetime # updated_at :datetime # key :text -# title :string(255) -# type :string(255) -# fingerprint :string(255) +# title :string +# type :string +# fingerprint :string # public :boolean default(FALSE), not null # diff --git a/app/models/email.rb b/app/models/email.rb index b323d1edd10..eae2472f337 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # user_id :integer not null -# email :string(255) not null +# email :string not null # created_at :datetime # updated_at :datetime # diff --git a/app/models/event.rb b/app/models/event.rb index 897518aadc7..25c7c3e6dc7 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -3,9 +3,9 @@ # Table name: events # # id :integer not null, primary key -# target_type :string(255) +# target_type :string # target_id :integer -# title :string(255) +# title :string # data :text # project_id :integer # created_at :datetime diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb index 97f4f03a9a5..d4afd8cbe84 100644 --- a/app/models/generic_commit_status.rb +++ b/app/models/generic_commit_status.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # project_id :integer -# status :string(255) +# status :string # finished_at :datetime # trace :text # created_at :datetime @@ -15,21 +15,24 @@ # commit_id :integer # commands :text # job_id :integer -# name :string(255) +# name :string # deploy :boolean default(FALSE) # options :text # allow_failure :boolean default(FALSE), not null -# stage :string(255) +# stage :string # trigger_request_id :integer # stage_idx :integer # tag :boolean -# ref :string(255) +# ref :string # user_id :integer -# type :string(255) -# target_url :string(255) -# description :string(255) +# type :string +# target_url :string +# description :string # artifacts_file :text # gl_project_id :integer +# artifacts_metadata :text +# erased_by_id :integer +# erased_at :datetime # class GenericCommitStatus < CommitStatus diff --git a/app/models/group.rb b/app/models/group.rb index 1f8432e3320..cff76877958 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -2,16 +2,17 @@ # # Table name: namespaces # -# id :integer not null, primary key -# name :string(255) not null -# path :string(255) not null -# owner_id :integer -# visibility_level :integer default(20), not null -# created_at :datetime -# updated_at :datetime -# type :string(255) -# description :string(255) default(""), not null -# avatar :string(255) +# id :integer not null, primary key +# name :string not null +# path :string not null +# owner_id :integer +# created_at :datetime +# updated_at :datetime +# type :string +# description :string default(""), not null +# avatar :string +# share_with_group_lock :boolean default(FALSE) +# visibility_level :integer default(20), not null # require 'carrierwave/orm/activerecord' diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index bc6e0f98c3c..2b8f34a0568 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -16,6 +16,8 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null +# wiki_page_events :boolean default(FALSE), not null +# token :string # class ProjectHook < WebHook diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb index 80962264ba2..0e176de5ef8 100644 --- a/app/models/hooks/service_hook.rb +++ b/app/models/hooks/service_hook.rb @@ -16,6 +16,8 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null +# wiki_page_events :boolean default(FALSE), not null +# token :string # class ServiceHook < WebHook diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb index 15dddcc2447..ad508cbbcb8 100644 --- a/app/models/hooks/system_hook.rb +++ b/app/models/hooks/system_hook.rb @@ -16,6 +16,8 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null +# wiki_page_events :boolean default(FALSE), not null +# token :string # class SystemHook < WebHook diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 3a2e4f546f7..8e58c9583ab 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -16,6 +16,8 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null +# wiki_page_events :boolean default(FALSE), not null +# token :string # class WebHook < ActiveRecord::Base @@ -43,23 +45,17 @@ class WebHook < ActiveRecord::Base if parsed_url.userinfo.blank? response = WebHook.post(url, body: data.to_json, - headers: { - "Content-Type" => "application/json", - "X-Gitlab-Event" => hook_name.singularize.titleize - }, + headers: build_headers(hook_name), verify: enable_ssl_verification) else - post_url = url.gsub("#{parsed_url.userinfo}@", "") + post_url = url.gsub("#{parsed_url.userinfo}@", '') auth = { username: CGI.unescape(parsed_url.user), password: CGI.unescape(parsed_url.password), } response = WebHook.post(post_url, body: data.to_json, - headers: { - "Content-Type" => "application/json", - "X-Gitlab-Event" => hook_name.singularize.titleize - }, + headers: build_headers(hook_name), verify: enable_ssl_verification, basic_auth: auth) end @@ -73,4 +69,15 @@ class WebHook < ActiveRecord::Base def async_execute(data, hook_name) Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name) end + + private + + def build_headers(hook_name) + headers = { + 'Content-Type' => 'application/json', + 'X-Gitlab-Event' => hook_name.singularize.titleize + } + headers['X-Gitlab-Token'] = token if token.present? + headers + end end diff --git a/app/models/identity.rb b/app/models/identity.rb index e1915b079d4..ef4d5f99091 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -3,8 +3,8 @@ # Table name: identities # # id :integer not null, primary key -# extern_uid :string(255) -# provider :string(255) +# extern_uid :string +# provider :string # user_id :integer # created_at :datetime # updated_at :datetime diff --git a/app/models/issue.rb b/app/models/issue.rb index ea1bfb776ee..abaa509707c 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -3,20 +3,23 @@ # Table name: issues # # id :integer not null, primary key -# title :string(255) +# title :string # assignee_id :integer # author_id :integer # project_id :integer # created_at :datetime # updated_at :datetime # position :integer default(0) -# branch_name :string(255) +# branch_name :string # description :text # milestone_id :integer -# state :string(255) +# state :string # iid :integer # updated_by_id :integer # moved_to_id :integer +# confidential :boolean default(FALSE) +# deleted_at :datetime +# due_date :date # require 'carrierwave/orm/activerecord' diff --git a/app/models/key.rb b/app/models/key.rb index 0282ad18139..b2b57849f8a 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -7,9 +7,9 @@ # created_at :datetime # updated_at :datetime # key :text -# title :string(255) -# type :string(255) -# fingerprint :string(255) +# title :string +# type :string +# fingerprint :string # public :boolean default(FALSE), not null # diff --git a/app/models/label.rb b/app/models/label.rb index 60bdce32952..9a22398d952 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -2,14 +2,14 @@ # # Table name: labels # -# id :integer not null, primary key -# title :string(255) -# color :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# template :boolean default(FALSE) -# description :string(255) +# id :integer not null, primary key +# title :string +# color :string +# project_id :integer +# created_at :datetime +# updated_at :datetime +# template :boolean default(FALSE) +# description :string # class Label < ActiveRecord::Base diff --git a/app/models/label_link.rb b/app/models/label_link.rb index b94c9c777af..7b8e872b6dd 100644 --- a/app/models/label_link.rb +++ b/app/models/label_link.rb @@ -5,7 +5,7 @@ # id :integer not null, primary key # label_id :integer # target_id :integer -# target_type :string(255) +# target_type :string # created_at :datetime # updated_at :datetime # diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 86b1b7e2f99..927e764af92 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -3,11 +3,11 @@ # Table name: lfs_objects # # id :integer not null, primary key -# oid :string(255) not null +# oid :string not null # size :integer not null # created_at :datetime # updated_at :datetime -# file :string(255) +# file :string # class LfsObject < ActiveRecord::Base diff --git a/app/models/member.rb b/app/models/member.rb index 60efafef211..cca82da89f1 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -5,15 +5,15 @@ # id :integer not null, primary key # access_level :integer not null # source_id :integer not null -# source_type :string(255) not null +# source_type :string not null # user_id :integer # notification_level :integer not null -# type :string(255) +# type :string # created_at :datetime # updated_at :datetime # created_by_id :integer -# invite_email :string(255) -# invite_token :string(255) +# invite_email :string +# invite_token :string # invite_accepted_at :datetime # diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 9fb474a1a93..a48c1943e6f 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -5,15 +5,15 @@ # id :integer not null, primary key # access_level :integer not null # source_id :integer not null -# source_type :string(255) not null +# source_type :string not null # user_id :integer # notification_level :integer not null -# type :string(255) +# type :string # created_at :datetime # updated_at :datetime # created_by_id :integer -# invite_email :string(255) -# invite_token :string(255) +# invite_email :string +# invite_token :string # invite_accepted_at :datetime # diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 07ddb02ae9d..143350a0b55 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -5,15 +5,15 @@ # id :integer not null, primary key # access_level :integer not null # source_id :integer not null -# source_type :string(255) not null +# source_type :string not null # user_id :integer # notification_level :integer not null -# type :string(255) +# type :string # created_at :datetime # updated_at :datetime # created_by_id :integer -# invite_email :string(255) -# invite_token :string(255) +# invite_email :string +# invite_token :string # invite_accepted_at :datetime # diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index d00919c3b0c..4175e1e5fba 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -3,28 +3,29 @@ # Table name: merge_requests # # id :integer not null, primary key -# target_branch :string(255) not null -# source_branch :string(255) not null +# target_branch :string not null +# source_branch :string not null # source_project_id :integer not null # author_id :integer # assignee_id :integer -# title :string(255) +# title :string # created_at :datetime # updated_at :datetime # milestone_id :integer -# state :string(255) -# merge_status :string(255) +# state :string +# merge_status :string # target_project_id :integer not null # iid :integer # description :text # position :integer default(0) # locked_at :datetime # updated_by_id :integer -# merge_error :string(255) +# merge_error :string # merge_params :text # merge_when_build_succeeds :boolean default(FALSE), not null # merge_user_id :integer # merge_commit_sha :string +# deleted_at :datetime # class MergeRequest < ActiveRecord::Base diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 0580cafdd1b..8951e92a0b8 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -3,12 +3,14 @@ # Table name: merge_request_diffs # # id :integer not null, primary key -# state :string(255) +# state :string # st_commits :text # st_diffs :text # merge_request_id :integer not null # created_at :datetime # updated_at :datetime +# base_commit_sha :string +# real_size :string # class MergeRequestDiff < ActiveRecord::Base diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 986184dd301..5ee8a965ad8 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -3,13 +3,13 @@ # Table name: milestones # # id :integer not null, primary key -# title :string(255) not null +# title :string not null # project_id :integer not null # description :text # due_date :date # created_at :datetime # updated_at :datetime -# state :string(255) +# state :string # iid :integer # diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 55842df1e2d..741e912171d 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -2,15 +2,17 @@ # # Table name: namespaces # -# id :integer not null, primary key -# name :string(255) not null -# path :string(255) not null -# owner_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) -# description :string(255) default(""), not null -# avatar :string(255) +# id :integer not null, primary key +# name :string not null +# path :string not null +# owner_id :integer +# created_at :datetime +# updated_at :datetime +# type :string +# description :string default(""), not null +# avatar :string +# share_with_group_lock :boolean default(FALSE) +# visibility_level :integer default(20), not null # class Namespace < ActiveRecord::Base diff --git a/app/models/note.rb b/app/models/note.rb index 71b4293d57a..deee2b9e885 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -4,14 +4,14 @@ # # id :integer not null, primary key # note :text -# noteable_type :string(255) +# noteable_type :string # author_id :integer # created_at :datetime # updated_at :datetime # project_id :integer -# attachment :string(255) -# line_code :string(255) -# commit_id :string(255) +# attachment :string +# line_code :string +# commit_id :string # noteable_id :integer # system :boolean default(FALSE), not null # st_diff :text diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 5001738f411..846773752a6 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -1,3 +1,16 @@ +# == Schema Information +# +# Table name: notification_settings +# +# id :integer not null, primary key +# user_id :integer not null +# source_id :integer not null +# source_type :string not null +# level :integer default(0), not null +# created_at :datetime not null +# updated_at :datetime not null +# + class NotificationSetting < ActiveRecord::Base enum level: { disabled: 0, participating: 1, watch: 2, global: 3, mention: 4 } diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb index 452f3913eef..1d5f4c50254 100644 --- a/app/models/personal_snippet.rb +++ b/app/models/personal_snippet.rb @@ -3,14 +3,14 @@ # Table name: snippets # # id :integer not null, primary key -# title :string(255) +# title :string # content :text # author_id :integer not null # project_id :integer # created_at :datetime # updated_at :datetime -# file_name :string(255) -# type :string(255) +# file_name :string +# type :string # visibility_level :integer default(0), not null # diff --git a/app/models/project.rb b/app/models/project.rb index 76265a59ea7..bce25455373 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2,41 +2,46 @@ # # Table name: projects # -# id :integer not null, primary key -# name :string(255) -# path :string(255) -# description :text -# created_at :datetime -# updated_at :datetime -# creator_id :integer -# issues_enabled :boolean default(TRUE), not null -# wall_enabled :boolean default(TRUE), not null -# merge_requests_enabled :boolean default(TRUE), not null -# wiki_enabled :boolean default(TRUE), not null -# namespace_id :integer -# issues_tracker :string(255) default("gitlab"), not null -# issues_tracker_id :string(255) -# snippets_enabled :boolean default(TRUE), not null -# last_activity_at :datetime -# import_url :string(255) -# visibility_level :integer default(0), not null -# archived :boolean default(FALSE), not null -# avatar :string(255) -# import_status :string(255) -# repository_size :float default(0.0) -# star_count :integer default(0), not null -# import_type :string(255) -# import_source :string(255) -# commit_count :integer default(0) -# import_error :text -# ci_id :integer -# builds_enabled :boolean default(TRUE), not null -# shared_runners_enabled :boolean default(TRUE), not null -# runners_token :string -# build_coverage_regex :string -# build_allow_git_fetch :boolean default(TRUE), not null -# build_timeout :integer default(3600), not null -# pending_delete :boolean +# id :integer not null, primary key +# name :string +# path :string +# description :text +# created_at :datetime +# updated_at :datetime +# creator_id :integer +# issues_enabled :boolean default(TRUE), not null +# wall_enabled :boolean default(TRUE), not null +# merge_requests_enabled :boolean default(TRUE), not null +# wiki_enabled :boolean default(TRUE), not null +# namespace_id :integer +# issues_tracker :string default("gitlab"), not null +# issues_tracker_id :string +# snippets_enabled :boolean default(TRUE), not null +# last_activity_at :datetime +# import_url :string +# visibility_level :integer default(0), not null +# archived :boolean default(FALSE), not null +# avatar :string +# import_status :string +# repository_size :float default(0.0) +# star_count :integer default(0), not null +# import_type :string +# import_source :string +# commit_count :integer default(0) +# import_error :text +# ci_id :integer +# builds_enabled :boolean default(TRUE), not null +# shared_runners_enabled :boolean default(TRUE), not null +# runners_token :string +# build_coverage_regex :string +# build_allow_git_fetch :boolean default(TRUE), not null +# build_timeout :integer default(3600), not null +# pending_delete :boolean default(FALSE) +# public_builds :boolean default(TRUE), not null +# main_language :string +# pushes_since_gc :integer default(0) +# last_repository_check_failed :boolean +# last_repository_check_at :datetime # require 'carrierwave/orm/activerecord' @@ -740,19 +745,17 @@ class Project < ActiveRecord::Base end def open_branches - all_branches = repository.branches + # We're using a Set here as checking values in a large Set is faster than + # checking values in a large Array. + protected_set = Set.new(protected_branch_names) - if protected_branches.present? - all_branches.reject! do |branch| - protected_branches_names.include?(branch.name) - end + repository.branches.reject do |branch| + protected_set.include?(branch.name) end - - all_branches end - def protected_branches_names - @protected_branches_names ||= protected_branches.map(&:name) + def protected_branch_names + @protected_branch_names ||= protected_branches.pluck(:name) end def root_ref?(branch) @@ -769,7 +772,7 @@ class Project < ActiveRecord::Base # Check if current branch name is marked as protected in the system def protected_branch?(branch_name) - protected_branches_names.include?(branch_name) + protected_branches.where(name: branch_name).any? end def developers_can_push_to_protected_branch?(branch_name) @@ -1041,4 +1044,11 @@ class Project < ActiveRecord::Base def wiki @wiki ||= ProjectWiki.new(self, self.owner) end + + def schedule_delete!(user_id, params) + # Queue this task for after the commit, so once we mark pending_delete it will run + run_after_commit { ProjectDestroyWorker.perform_async(id, user_id, params) } + + update_attribute(:pending_delete, true) + end end diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index e52a6bd7c84..66f5a609bf5 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: project_group_links +# +# id :integer not null, primary key +# project_id :integer not null +# group_id :integer not null +# created_at :datetime +# updated_at :datetime +# group_access :integer default(30), not null +# + class ProjectGroupLink < ActiveRecord::Base GUEST = 10 REPORTER = 20 diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 2c0ae312f1b..7830f764ed3 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -2,9 +2,12 @@ # # Table name: project_import_data # -# id :integer not null, primary key -# project_id :integer -# data :text +# id :integer not null, primary key +# project_id :integer +# data :text +# encrypted_credentials :text +# encrypted_credentials_iv :string +# encrypted_credentials_salt :string # require 'carrierwave/orm/activerecord' diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb index 792ad804575..368485a060a 100644 --- a/app/models/project_services/asana_service.rb +++ b/app/models/project_services/asana_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require 'asana' diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 29d841faed8..ffb7455b014 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class AssemblaService < Service diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 060062aaf7a..c36ee95e378 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class BambooService < CiService diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 3efbfd2eec3..f9f4897a065 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require "addressable/uri" @@ -26,7 +29,7 @@ class BuildkiteService < CiService prop_accessor :project_url, :token, :enable_ssl_verification - validates :project_url, presence: true, if: :activated? + validates :project_url, presence: true, url: true, if: :activated? validates :token, presence: true, if: :activated? after_save :compose_service_hook, if: :activated? @@ -91,7 +94,7 @@ class BuildkiteService < CiService { type: 'text', name: 'project_url', placeholder: "#{ENDPOINT}/example/project" }, - + { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb index 6ab6d7417b7..20cdfcaffb2 100644 --- a/app/models/project_services/builds_email_service.rb +++ b/app/models/project_services/builds_email_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class BuildsEmailService < Service diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 6e8f0842524..28c969fe57f 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class CampfireService < Service diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index d9f0849d147..9bc8f982da6 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # # Base class for CI services diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index 88a3e9218cb..4d1319eb6f8 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class CustomIssueTrackerService < IssueTrackerService diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index b4724bb647e..d8e00e018cc 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class DroneCiService < CiService diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index b831577cd97..2dbd29062df 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class EmailsOnPushService < Service diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb index b402b68665a..5469049bb5e 100644 --- a/app/models/project_services/external_wiki_service.rb +++ b/app/models/project_services/external_wiki_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class ExternalWikiService < Service diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 8605ce66e48..3dc1e0fbe8b 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require "flowdock-git-hook" diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 61babe9cfe5..b4c311cf664 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require "gemnasium/gitlab_service" diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 33f0d7ea01a..a92f7226083 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # # TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index eaa5654b9c6..1adaeeb3b2b 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class GitlabIssueTrackerService < IssueTrackerService diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 064ef8e8674..f9ddf588722 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class HipchatService < Service diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 04c714bfaad..b9a592d7096 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require 'uri' diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 25045224ce5..98a3a7c6b86 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,11 +17,14 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class IssueTrackerService < Service - validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated? + validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? default_value_for :category, 'issue_tracker' diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 1ed42c4f3e7..ba68658f0bd 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class JiraService < IssueTrackerService @@ -28,6 +31,8 @@ class JiraService < IssueTrackerService prop_accessor :username, :password, :api_url, :jira_issue_transition_id, :title, :description, :project_url, :issues_url, :new_issue_url + validates :api_url, presence: true, url: true, if: :activated? + before_validation :set_api_url, :set_jira_issue_transition_id before_update :reset_password diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index c9a890c7e3f..acaa0c39365 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class PivotaltrackerService < Service diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index e76d9eca2ab..a640c8cb440 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class PushoverService < Service diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index de974354c77..e2137e92c62 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class RedmineService < IssueTrackerService diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index fd65027f084..83ffa53a407 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,12 +17,15 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class SlackService < Service prop_accessor :webhook, :username, :channel boolean_accessor :notify_only_broken_builds - validates :webhook, presence: true, if: :activated? + validates :webhook, presence: true, url: true, if: :activated? def initialize_properties if properties.nil? diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 8dceee5e2c5..4015da31509 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class TeamcityService < CiService diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index 1f7d85a5f3d..b4b2807eba4 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -3,14 +3,14 @@ # Table name: snippets # # id :integer not null, primary key -# title :string(255) +# title :string # content :text # author_id :integer not null # project_id :integer # created_at :datetime # updated_at :datetime -# file_name :string(255) -# type :string(255) +# file_name :string +# type :string # visibility_level :integer default(0), not null # @@ -22,4 +22,6 @@ class ProjectSnippet < Snippet # Scopes scope :fresh, -> { order("created_at DESC") } + + participant :author, :notes end diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 8ebd790a89e..3d2052c892c 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # project_id :integer not null -# name :string(255) not null +# name :string not null # created_at :datetime # updated_at :datetime # developers_can_push :boolean default(FALSE), not null diff --git a/app/models/release.rb b/app/models/release.rb index 89f70278af5..dc700d1ea5a 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -3,7 +3,7 @@ # Table name: releases # # id :integer not null, primary key -# tag :string(255) +# tag :string # description :text # project_id :integer # created_at :datetime diff --git a/app/models/repository.rb b/app/models/repository.rb index b4319297e43..7aebfe279fb 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -87,13 +87,15 @@ class Repository nil end - def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) + def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil) options = { repo: raw_repository, ref: ref, path: path, limit: limit, offset: offset, + after: after, + before: before, # --follow doesn't play well with --skip. See: # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 follow: false, @@ -146,10 +148,20 @@ class Repository find_branch(branch_name) end - def add_tag(tag_name, ref, message = nil) - before_push_tag + def add_tag(user, tag_name, target, message = nil) + oldrev = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::TAG_REF_PREFIX + tag_name + target = commit(target).try(:id) + + return false unless target + + options = { message: message, tagger: user_to_committer(user) } if message + + GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do + rugged.tags.create(tag_name, target, options) + end - gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) + find_tag(tag_name) end def rm_branch(user, branch_name) @@ -575,7 +587,7 @@ class Repository end def contributors - commits = self.commits(nil, nil, 2000, 0, true) + commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true) commits.group_by(&:author_email).map do |email, commits| contributor = Gitlab::Contributor.new diff --git a/app/models/security_event.rb b/app/models/security_event.rb index 68c00adad59..0bee03974f1 100644 --- a/app/models/security_event.rb +++ b/app/models/security_event.rb @@ -4,9 +4,9 @@ # # id :integer not null, primary key # author_id :integer not null -# type :string(255) not null +# type :string not null # entity_id :integer not null -# entity_type :string(255) not null +# entity_type :string not null # details :text # created_at :datetime # updated_at :datetime diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 77115597d71..99279a2e083 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -5,11 +5,11 @@ # id :integer not null, primary key # project_id :integer # noteable_id :integer -# noteable_type :string(255) +# noteable_type :string # recipient_id :integer -# commit_id :string(255) -# line_code :string(255) -# reply_key :string(255) not null +# commit_id :string +# reply_key :string not null +# line_code :string # class SentNotification < ActiveRecord::Base diff --git a/app/models/service.rb b/app/models/service.rb index 2645b8321d7..bf16a545307 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # # To add new service you should build a class inherited from Service diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 0fd08061925..2f905a90942 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -3,14 +3,14 @@ # Table name: snippets # # id :integer not null, primary key -# title :string(255) +# title :string # content :text # author_id :integer not null # project_id :integer # created_at :datetime # updated_at :datetime -# file_name :string(255) -# type :string(255) +# file_name :string +# type :string # visibility_level :integer default(0), not null # diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb index 12df68ef83b..f49eb7d88e2 100644 --- a/app/models/spam_log.rb +++ b/app/models/spam_log.rb @@ -1,3 +1,20 @@ +# == Schema Information +# +# Table name: spam_logs +# +# id :integer not null, primary key +# user_id :integer +# source_ip :string +# user_agent :string +# via_api :boolean +# project_id :integer +# noteable_type :string +# title :string +# description :text +# created_at :datetime not null +# updated_at :datetime not null +# + class SpamLog < ActiveRecord::Base belongs_to :user diff --git a/app/models/subscription.rb b/app/models/subscription.rb index dd800ce110f..242faa7d32e 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -5,7 +5,7 @@ # id :integer not null, primary key # user_id :integer # subscribable_id :integer -# subscribable_type :string(255) +# subscribable_type :string # subscribed :boolean # created_at :datetime # updated_at :datetime diff --git a/app/models/user.rb b/app/models/user.rb index b6f405c6981..959b1f93758 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,55 +3,55 @@ # Table name: users # # id :integer not null, primary key -# email :string(255) default(""), not null -# encrypted_password :string(255) default(""), not null -# reset_password_token :string(255) +# email :string default(""), not null +# encrypted_password :string default(""), not null +# reset_password_token :string # reset_password_sent_at :datetime # remember_created_at :datetime # sign_in_count :integer default(0) # current_sign_in_at :datetime # last_sign_in_at :datetime -# current_sign_in_ip :string(255) -# last_sign_in_ip :string(255) +# current_sign_in_ip :string +# last_sign_in_ip :string # created_at :datetime # updated_at :datetime -# name :string(255) +# name :string # admin :boolean default(FALSE), not null # projects_limit :integer default(10) -# skype :string(255) default(""), not null -# linkedin :string(255) default(""), not null -# twitter :string(255) default(""), not null -# authentication_token :string(255) +# skype :string default(""), not null +# linkedin :string default(""), not null +# twitter :string default(""), not null +# authentication_token :string # theme_id :integer default(1), not null -# bio :string(255) +# bio :string # failed_attempts :integer default(0) # locked_at :datetime -# username :string(255) +# username :string # can_create_group :boolean default(TRUE), not null # can_create_team :boolean default(TRUE), not null -# state :string(255) +# state :string # color_scheme_id :integer default(1), not null # notification_level :integer default(1), not null # password_expires_at :datetime # created_by_id :integer # last_credential_check_at :datetime -# avatar :string(255) -# confirmation_token :string(255) +# avatar :string +# confirmation_token :string # confirmed_at :datetime # confirmation_sent_at :datetime -# unconfirmed_email :string(255) +# unconfirmed_email :string # hide_no_ssh_key :boolean default(FALSE) -# website_url :string(255) default(""), not null -# notification_email :string(255) +# website_url :string default(""), not null +# notification_email :string # hide_no_password :boolean default(FALSE) # password_automatically_set :boolean default(FALSE) -# location :string(255) -# encrypted_otp_secret :string(255) -# encrypted_otp_secret_iv :string(255) -# encrypted_otp_secret_salt :string(255) +# location :string +# encrypted_otp_secret :string +# encrypted_otp_secret_iv :string +# encrypted_otp_secret_salt :string # otp_required_for_login :boolean default(FALSE), not null # otp_backup_codes :text -# public_email :string(255) default(""), not null +# public_email :string default(""), not null # dashboard :integer default(0) # project_view :integer default(0) # consumed_timestep :integer @@ -59,7 +59,8 @@ # hide_project_limit :boolean default(FALSE) # unlock_token :string # otp_grace_period_started_at :datetime -# external :boolean default(FALSE) +# ldap_email :boolean default(FALSE), not null +# external :boolean default(FALSE) # require 'carrierwave/orm/activerecord' @@ -91,7 +92,7 @@ class User < ActiveRecord::Base devise :two_factor_backupable, otp_number_of_backup_codes: 10 serialize :otp_backup_codes, JSON - devise :lockable, :recoverable, :rememberable, :trackable, + devise :lockable, :async, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable attr_accessor :force_random_password diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 707c2f7ff85..9f4481a8153 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -43,9 +43,4 @@ class CreateBranchService < BaseService out[:branch] = branch out end - - def build_push_data(project, user, branch) - Gitlab::PushDataBuilder. - build(project, user, Gitlab::Git::BLANK_SHA, branch.target, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) - end end diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index 55985380d31..91ed0e354d0 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -1,50 +1,30 @@ require_relative 'base_service' class CreateTagService < BaseService - def execute(tag_name, ref, message, release_description = nil) + def execute(tag_name, target, message, release_description = nil) valid_tag = Gitlab::GitRefValidator.validate(tag_name) - if valid_tag == false - return error('Tag name invalid') - end + return error('Tag name invalid') unless valid_tag repository = project.repository - existing_tag = repository.find_tag(tag_name) - if existing_tag - return error('Tag already exists') - end - message.strip! if message - repository.add_tag(tag_name, ref, message) - new_tag = repository.find_tag(tag_name) + new_tag = nil + begin + new_tag = repository.add_tag(current_user, tag_name, target, message) + rescue Rugged::TagError + return error("Tag #{tag_name} already exists") + rescue GitHooksService::PreReceiveError + return error('Tag creation was rejected by Git hook') + end if new_tag - push_data = create_push_data(project, current_user, new_tag) - EventCreateService.new.push(project, current_user, push_data) - project.execute_hooks(push_data.dup, :tag_push_hooks) - project.execute_services(push_data.dup, :tag_push_hooks) - CreateCommitBuildsService.new.execute(project, current_user, push_data) - if release_description CreateReleaseService.new(@project, @current_user). execute(tag_name, release_description) end - - success(new_tag) + success.merge(tag: new_tag) else - error('Invalid reference name') + error("Target #{target} is invalid") end end - - def success(branch) - out = super() - out[:tag] = branch - out - end - - def create_push_data(project, user, tag) - commits = [project.commit(tag.target)].compact - Gitlab::PushDataBuilder. - build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", commits, tag.message) - end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index b7af80055bf..66136b62617 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -17,6 +17,7 @@ class GitPushService < BaseService # 6. Checks if the project's main language has changed # def execute + @project.repository.after_create if @project.empty_repo? @project.repository.after_push_commit(branch_name, params[:newrev]) if push_remove_branch? diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 64271d8bc5c..7410442609d 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -2,6 +2,7 @@ class GitTagPushService < BaseService attr_accessor :push_data def execute + project.repository.after_create if project.empty_repo? project.repository.before_push_tag @push_data = build_push_data diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index 82e7090f1ea..e61628086f0 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -41,14 +41,25 @@ module Issues private def create_new_issue - new_params = { id: nil, iid: nil, label_ids: [], milestone: nil, + new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids, + milestone_id: cloneable_milestone_id, project: @new_project, author: @old_issue.author, description: rewrite_content(@old_issue.description) } - new_params = @old_issue.serializable_hash.merge(new_params) + new_params = @old_issue.serializable_hash.symbolize_keys.merge(new_params) CreateService.new(@new_project, @current_user, new_params).execute end + def cloneable_label_ids + @new_project.labels + .where(title: @old_issue.labels.pluck(:title)).pluck(:id) + end + + def cloneable_milestone_id + @new_project.milestones + .find_by(title: @old_issue.milestone.try(:title)).try(:id) + end + def rewrite_notes @old_issue.notes.find_each do |note| new_note = note.dup diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index fa34753c4fd..cd4230aa5e4 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -7,6 +7,9 @@ module MergeRequests merge_request.can_be_created = false merge_request.compare_commits = [] merge_request.source_project = project unless merge_request.source_project + + merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project) + merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_branch ||= merge_request.target_project.default_branch @@ -38,21 +41,45 @@ module MergeRequests merge_request.can_be_created = false end + set_title_and_description(merge_request) + end + + private + + # When your branch name starts with an iid followed by a dash this pattern will be + # interpreted as the user wants to close that issue on this project. + # + # For example: + # - Issue 112 exists, title: Emoji don't show up in commit title + # - Source branch is: 112-fix-mep-mep + # + # Will lead to: + # - Appending `Closes #112` to the description + # - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is + # more than one commit in the MR + # + def set_title_and_description(merge_request) + if match = merge_request.source_branch.match(/\A(\d+)-/) + iid = match[1] + end + commits = merge_request.compare_commits if commits && commits.count == 1 commit = commits.first merge_request.title = commit.title merge_request.description ||= commit.description.try(:strip) + elsif iid && (issue = merge_request.target_project.get_issue(iid)) && !issue.try(:confidential?) + case issue + when Issue + merge_request.title = "Resolve \"#{issue.title}\"" + when ExternalIssue + merge_request.title = "Resolve #{issue.title}" + end else merge_request.title = merge_request.source_branch.titleize.humanize end - # When your branch name starts with an iid followed by a dash this pattern will - # be interpreted as the use wants to close that issue on this project - # Pattern example: 112-fix-mep-mep - # Will lead to appending `Closes #112` to the description - if match = merge_request.source_branch.match(/\A(\d+)-/) - iid = match[1] + if iid closes_issue = "Closes ##{iid}" if merge_request.description.present? diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 2bb312bb252..01586994813 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -5,6 +5,8 @@ module Notes note.author = current_user note.system = false + return unless valid_project?(note) + if note.save # Finish the harder work in the background NewNoteWorker.perform_in(2.seconds, note.id, params) @@ -13,5 +15,14 @@ module Notes note end + + private + + def valid_project?(note) + return false unless project + return true if note.for_commit? + + note.noteable.try(:project) == project + end end end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index df5054f08d7..19aab999e00 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -7,9 +7,7 @@ module Projects DELETED_FLAG = '+deleted' def pending_delete! - project.update_attribute(:pending_delete, true) - - ProjectDestroyWorker.perform_in(1.minute, project.id, current_user.id, params) + project.schedule_delete!(current_user.id, params) end def execute diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb index 988c663b9d0..24a817c06c9 100644 --- a/app/services/wiki_pages/create_service.rb +++ b/app/services/wiki_pages/create_service.rb @@ -1,7 +1,8 @@ module WikiPages class CreateService < WikiPages::BaseService def execute - page = WikiPage.new(@project.wiki) + project_wiki = ProjectWiki.new(@project, current_user) + page = WikiPage.new(project_wiki) if page.create(@params) execute_hooks(page, 'create') diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index 3571eefd570..967151bc33b 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -35,15 +35,15 @@ %td #{build.stage} / #{build.name} - .pull-right - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.try(:trigger_request) - %span.label.label-info triggered - - if build.try(:allow_failure) - %span.label.label-danger allowed to fail + %td + - if build.tags.any? + - build.tags.each do |tag| + %span.label.label-primary + = tag + - if build.try(:trigger_request) + %span.label.label-info triggered + - if build.try(:allow_failure) + %span.label.label-danger allowed to fail %td.duration - if build.duration @@ -61,12 +61,12 @@ %td .pull-right - if can?(current_user, :read_build, project) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do + = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do %i.fa.fa-download - if can?(current_user, :update_build, build.project) - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do + = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do %i.fa.fa-remove.cred - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do - %i.fa.fa-repeat + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + %i.fa.fa-refresh diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 5931efdefe6..804d7851bdb 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -19,7 +19,7 @@ - if @all_builds.running_or_pending.any? = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post -.gray-content-block.second-block +.row-content-block.second-block #{(@scope || 'running').capitalize} builds %ul.content-list @@ -38,6 +38,7 @@ %th Ref %th Runner %th Name + %th Tags %th Duration %th Finished at %th diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 67d23c80233..7b388cf7862 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -13,9 +13,15 @@ = form_errors(@hook) .form-group - = f.label :url, "URL:", class: 'control-label' + = f.label :url, 'URL', class: 'control-label' .col-sm-10 - = f.text_field :url, class: "form-control" + = f.text_field :url, class: 'form-control' + .form-group + = f.label :token, 'Secret Token', class: 'control-label' + .col-sm-10 + = f.text_field :token, class: 'form-control' + %p.help-block + Use this token to validate received payloads .form-group = f.label :url, "Trigger", class: 'control-label' .col-sm-10.prepend-top-10 diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 4b475a4d8fa..698feb571ac 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -7,7 +7,7 @@ %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } = link_to klass::file_name, "##{klass::file_name_noext}", 'data-toggle' => 'tab' -.gray-content-block +.row-content-block To prevent performance issues admin logs output the last 2000 lines .tab-content - loggers.each do |klass| diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 0ee8dc962b9..d6743081c8e 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -32,7 +32,7 @@ Without projects %small.badge= number_with_delimiter(User.without_projects.count) - .gray-content-block.second-block + .row-content-block.second-block .pull-right .dropdown.inline %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 49ab8aad1d5..fc42e5dcc66 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -8,14 +8,14 @@ = link_to todos_filter_path(state: 'pending') do %span To do - %span{class: 'badge'} + %span.badge = todos_pending_count - todo_done_active = ('active' if params[:state] == 'done') %li{class: "todos-done #{todo_done_active}"} = link_to todos_filter_path(state: 'done') do %span Done - %span{class: 'badge'} + %span.badge = todos_done_count .nav-controls @@ -25,7 +25,7 @@ = icon('spinner spin') .todos-filters - .gray-content-block.second-block + .row-content-block.second-block = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do .filter-item.inline = select_tag('project_id', todo_projects_options, diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 22b2c1a186b..c9d1e454a5e 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -4,7 +4,7 @@ %h3 Two-factor Authentication .login-body = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| - = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor authentication code', required: true, autofocus: true - %p.help-block.hint If you've lost your phone, you may enter one of your recovery codes. + = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor Authentication code', required: true, autofocus: true + %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. .prepend-top-20 = f.submit "Verify code", class: "btn btn-save" diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index e5607dacd0d..510215bb8cd 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -6,7 +6,7 @@ .login-heading %h3 Create an account .login-body - = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| + = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f| .devise-errors = devise_error_messages! %div @@ -16,7 +16,7 @@ %div = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true .form-group.append-bottom-20#password-strength - = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true + = f.password_field :password, class: "form-control bottom", placeholder: "Password", required: true %div - if current_application_settings.recaptcha_enabled = recaptcha_tags diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 5d622582088..e4629bae0e6 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -5,7 +5,7 @@ = cache [event, current_application_settings, "v2.2"] do - if event.author - = link_to user_path(event.author.username) do + = link_to user_path(event.author) do = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:'' - else = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:'' diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index 5753158c24d..a1a282178e7 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -1,5 +1,5 @@ - if show_last_push_widget?(event) - .gray-content-block.clear-block.last-push-widget + .row-content-block.clear-block.last-push-widget .event-last-push .event-last-push-text %span You pushed to diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 8ffca96bb4e..57f6e7e0612 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -6,7 +6,7 @@ - else = render 'explore/head' -.gray-content-block.clearfix +.row-content-block.clearfix .pull-left = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| = hidden_field_tag :sort, @sort diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 0f100c39ffb..9b838b9f3b7 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -6,7 +6,7 @@ - else = render 'explore/head' -.gray-content-block +.row-content-block - if current_user .pull-right = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml index f73e1d9e865..aaad265b3ee 100644 --- a/app/views/groups/activity.html.haml +++ b/app/views/groups/activity.html.haml @@ -3,7 +3,6 @@ = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") - page_title "Activity" -- header_title group_title(@group, "Activity", activity_group_path(@group)) %section.activities = render 'activities' diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index a698cbbe9db..92cd4c553d0 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,5 +1,3 @@ -- header_title group_title(@group, "Settings", edit_group_path(@group)) - .panel.panel-default.prepend-top-default .panel-heading Group settings diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 6b7fd5746d6..0eb6bbd4420 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,5 +1,4 @@ - page_title "Members" -- header_title group_title(@group, "Members", group_group_members_path(@group)) .group-members-page.prepend-top-default - if current_user && current_user.can?(:admin_group_member, @group) diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index aea35c50862..4434f1cbd35 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,5 +1,4 @@ - page_title "Issues" -- header_title group_title(@group, "Issues", issues_group_path(@group)) = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues") @@ -16,7 +15,7 @@ = render 'shared/issuable/filter', type: :issues -.gray-content-block.second-block +.row-content-block.second-block Only issues from %strong #{@group.name} group are listed here. diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index e1c9dd931ee..e6953d94531 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,5 +1,4 @@ - page_title "Merge Requests" -- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group)) .top-area = render 'shared/issuable/nav', type: :merge_requests @@ -8,7 +7,7 @@ = render 'shared/issuable/filter', type: :merge_requests -.gray-content-block.second-block +.row-content-block.second-block Only merge requests from %strong #{@group.name} group are listed here. diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index ab307708b75..121a7de3ad7 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -1,5 +1,4 @@ - page_title "Milestones" -- header_title group_title(@group, "Milestones", group_milestones_path(@group)) .top-area = render 'shared/milestones_filter' @@ -10,7 +9,7 @@ = icon('plus') New Milestone -.gray-content-block +.row-content-block Only milestones from %strong #{@group.name} group are listed here. diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index dd75766121e..c2f2d9912f7 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -1,5 +1,4 @@ - page_title "Projects" -- header_title group_title(@group, "Projects", projects_group_path(@group)) .panel.panel-default.prepend-top-default .panel-heading diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 3d16ecb097a..77c297255b8 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -4,28 +4,20 @@ - if current_user = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") -.cover-block - .cover-controls - - if @group && can?(current_user, :admin_group, @group) - = link_to icon('pencil'), edit_group_path(@group), class: 'btn' - - if current_user - = link_to icon('rss'), group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' - - .avatar-holder +.cover-block.groups-cover-block + .container-fluid.container-limited = link_to group_icon(@group), target: '_blank' do - = image_tag group_icon(@group), class: "avatar group-avatar s90" - .cover-title - %h1 - = @group.name - %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } - = visibility_level_icon(@group.visibility_level, fw: false) - - .cover-desc.username - @#{@group.path} - - - if @group.description.present? - .cover-desc.description - = markdown(@group.description, pipeline: :description) + = image_tag group_icon(@group), class: "avatar group-avatar s70" + .group-info + .cover-title + %h1 + @#{@group.path} + %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } + = visibility_level_icon(@group.visibility_level, fw: false) + + - if @group.description.present? + .cover-desc.description + = markdown(@group.description, pipeline: :description) %div{ class: container_class } .top-area diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index f12df5c8ffe..d676bc28c89 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -48,14 +48,14 @@ .lead Gray content block with side padding using - %code .gray-content-block + %code .row-content-block .example - .gray-content-block + .row-content-block %h4 Normal block inside content = lorem - .gray-content-block.second-block + .row-content-block.second-block %h4 Second block = lorem diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index ad8a2e1e6c7..3c3bc41bf0e 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -26,7 +26,7 @@ .layout-nav .container-fluid = render "layouts/nav/#{nav}" - .content-wrapper + .content-wrapper{ class: ('page-with-layout-nav' if defined?(nav) && nav) } = render "layouts/flash" = yield :flash_message %div{ class: (container_class unless @no_container) } diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 2e483b7148d..f06acc98ca1 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,6 +1,6 @@ - page_title @group.name - page_description @group.description unless page_description - header_title group_title(@group) unless header_title -- sidebar "group" unless sidebar +- nav "group" = render template: "layouts/application" diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml index a1a1fc2f858..66b115e36de 100644 --- a/app/views/layouts/group_settings.html.haml +++ b/app/views/layouts/group_settings.html.haml @@ -1,5 +1,4 @@ - page_title "Settings" -- header_title group_title(@group, "Settings", edit_group_path(@group)) -- sidebar "group_settings" +- nav "group" = render template: "layouts/group" diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 3beb8ff7c0d..86930d4eaaf 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -15,7 +15,7 @@ - if current_user - if session[:impersonator_id] %li.impersonation - = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do + = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('user-secret fw') - if current_user.is_admin? %li @@ -23,6 +23,7 @@ = icon('wrench fw') %li = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('bell fw') %span.badge.todos-pending-count = todos_pending_count - if current_user.can_create_project? diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index ca49c313ff7..fad4224e945 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -15,12 +15,12 @@ = icon('dashboard fw') %span Activity - = nav_link(controller: :groups) do + = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = link_to dashboard_groups_path, title: 'Groups' do = icon('group fw') %span Groups - = nav_link(controller: :milestones) do + = nav_link(controller: 'dashboard/milestones') do = link_to dashboard_milestones_path, title: 'Milestones' do = icon('clock-o fw') %span diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml index f08c5edf99c..3b40006a0cc 100644 --- a/app/views/layouts/nav/_explore.html.haml +++ b/app/views/layouts/nav/_explore.html.haml @@ -4,7 +4,7 @@ = icon('bookmark fw') %span Projects - = nav_link(controller: :groups) do + = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = link_to explore_groups_path, title: 'Groups' do = icon('group fw') %span diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 55940741dc0..3438005863a 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,12 +1,6 @@ -%ul.nav.nav-sidebar - = nav_link do - = link_to root_path, title: 'Go to dashboard', class: 'back-link' do - = icon('caret-square-o-left fw') - %span - Go to dashboard - - %li.separate-item += render 'layouts/nav/group_settings' +%ul.nav-links = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: 'Home' do = icon('group fw') @@ -28,22 +22,16 @@ %span Issues - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - %span.count= number_with_delimiter(issues.count) + %span.badge.count= number_with_delimiter(issues.count) = nav_link(path: 'groups#merge_requests') do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do = icon('tasks fw') %span Merge Requests - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute - %span.count= number_with_delimiter(merge_requests.count) + %span.badge.count= number_with_delimiter(merge_requests.count) = nav_link(controller: [:group_members]) do = link_to group_group_members_path(@group), title: 'Members' do = icon('users fw') %span Members - - if can?(current_user, :admin_group, @group) - = nav_link(html_options: { class: "separate-item" }) do - = link_to edit_group_path(@group), title: 'Settings' do - = icon ('cogs fw') - %span - Settings diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index 56a92fe9103..e391ec7f2b7 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,20 +1,20 @@ -%ul.nav.nav-sidebar - = nav_link do - = link_to group_path(@group), title: 'Go to group', class: 'back-link' do - = icon('caret-square-o-left fw') - %span - Go to group - - %li.separate-item - - %ul.sidebar-subnav - = nav_link(path: 'groups#edit') do - = link_to edit_group_path(@group), title: 'Group Settings' do - = icon ('pencil-square-o fw') - %span - Group Settings - = nav_link(path: 'groups#projects') do - = link_to projects_group_path(@group), title: 'Projects' do - = icon('folder fw') - %span - Projects +- if current_user + - if access = @group.users.find_by(id: current_user.id) + .controls + %span.dropdown.group-settings-dropdown + %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} + = icon('cog') + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right + - if can?(current_user, :admin_group, @group) + = nav_link(path: 'groups#projects') do + = link_to projects_group_path(@group), title: 'Projects' do + Projects + %li.divider + %li + = link_to edit_group_path(@group) do + Edit Group + %li + = link_to leave_group_group_members_path(@group), + data: { confirm: leave_group_message(@group.name) }, method: :delete, title: 'Leave group' do + Leave Group diff --git a/app/views/notify/note_snippet_email.html.haml b/app/views/notify/note_snippet_email.html.haml new file mode 100644 index 00000000000..2fa2f784661 --- /dev/null +++ b/app/views/notify/note_snippet_email.html.haml @@ -0,0 +1 @@ += render 'note_message' diff --git a/app/views/notify/note_snippet_email.text.erb b/app/views/notify/note_snippet_email.text.erb new file mode 100644 index 00000000000..4d5a406f4b0 --- /dev/null +++ b/app/views/notify/note_snippet_email.text.erb @@ -0,0 +1,8 @@ +New comment for Snippet <%= @snippet.id %> + +<%= url_for(namespace_project_snippet_url(@snippet.project.namespace, @snippet.project, @snippet, anchor: "note_#{@note.id}")) %> + + +Author: <%= @note.author_name %> + +<%= @note.note %> diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 57527361eb6..6f7fefdb46d 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -45,4 +45,4 @@ %span.label.label-info Public Email - if email.email === current_user.notification_email %span.label.label-info Notification Email - = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right' + = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10' diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index f59d27f7ed0..eef50d887c7 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -8,11 +8,11 @@ %p - if @user.avatar? You can change your avatar here - - if Gitlab.config.gravatar.enabled + - if gravatar_enabled? or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} - else You can upload an avatar here - - if Gitlab.config.gravatar.enabled + - if gravatar_enabled? or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} .col-lg-9 .clearfix.avatar-image.append-bottom-default diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index f0a3e416db7..7c2b8d01508 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -1,7 +1,7 @@ - if event = last_push_event - if show_last_push_widget?(event) - .gray-content-block.top-block.clear-block.hidden-xs + .row-content-block.top-block.clear-block.hidden-xs .event-last-push .event-last-push-text %span You pushed to diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index a9908eaecca..369a847e7d4 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -7,7 +7,7 @@ = cache(readme_cache_key) do = render_readme(readme) - else - .gray-content-block.second-block.center + .row-content-block.second-block.center %h3.page-title This project does not have a README yet - if can?(current_user, :push_code, @project) diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index 84034c8bf16..49f95ff37db 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -1,7 +1,7 @@ - page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds' = render 'projects/builds/header_title' -.top-block.gray-content-block.clearfix +.top-block.row-content-block.clearfix .pull-right = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-default download' do diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 88266e21230..ac7790421a4 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -1,7 +1,7 @@ - page_title "Branches" = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block .pull-right - if can? current_user, :push_code, @project = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 0406fc21d77..2e8015d119b 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -9,6 +9,7 @@ %span.badge.js-totalbuilds-count = number_with_delimiter(@all_builds.count(:id)) + %li{class: ('active' if @scope == 'running')} = link_to project_builds_path(@project, scope: :running) do Running @@ -34,7 +35,7 @@ = icon('wrench') %span CI Lint -.gray-content-block +.row-content-block #{(@scope || 'running').capitalize} builds from this project %ul.content-list @@ -52,6 +53,7 @@ %th Ref %th Stage %th Name + %th Tags %th Duration %th Finished at - if @project.build_coverage_enabled? diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 99d72aa7935..c0f7a7686f0 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -2,7 +2,7 @@ = render "header_title" .build-page - .gray-content-block.top-block + .row-content-block.top-block Build ##{@build.id} for commit %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit) from @@ -34,7 +34,7 @@ %i.fa.fa-warning This build was retried. - .gray-content-block.middle-block + .row-content-block.middle-block .build-head .clearfix = ci_status_with_icon(@build.status) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index e123eb1cc7a..8e95f040273 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -40,6 +40,7 @@ %td = build.name + %td .label-container - if build.tags.any? - build.tags.each do |tag| @@ -68,12 +69,12 @@ %td .pull-right - if can?(current_user, :read_build, build) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do + = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do %i.fa.fa-download - if can?(current_user, :update_build, build) - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do + = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do %i.fa.fa-remove.cred - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do - %i.fa.fa-repeat + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + %i.fa.fa-refresh diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index d3acd33116c..e849aefb188 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -1,4 +1,4 @@ -.gray-content-block.middle-block +.row-content-block.build-content.middle-block .pull-right - if can?(current_user, :update_build, @project) - if ci_commit.builds.latest.failed.any?(&:retryable?) @@ -40,6 +40,7 @@ %th Build ID %th Stage %th Name + %th Tags %th Duration %th Finished at - if @project.build_coverage_enabled? @@ -49,7 +50,7 @@ = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true - if ci_commit.retried.any? - .gray-content-block.second-block + .row-content-block.second-block Retried builds .table-holder @@ -61,6 +62,7 @@ %th Ref %th Stage %th Name + %th Tags %th Duration %th Finished at - if @project.build_coverage_enabled? diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 3d7c18a5f58..01163e526b2 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,4 +1,4 @@ -.pull-right +.pull-right.commit-action-buttons %div - if @notes_count > 0 %span.btn.disabled.btn-grouped @@ -22,10 +22,12 @@ %div %p - %span.light Commit - = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" - = clipboard_button(clipboard_text: @commit.id) .commit-info-row + - if @commit.status + = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do + = ci_icon_for_status(@commit.status) + build: + = ci_label_for_status(@commit.status) %span.light Authored by %strong = commit_author_link(@commit, avatar: true, size: 24) @@ -39,19 +41,15 @@ #{time_ago_with_tooltip(@commit.committed_date)} .commit-info-row + %span.light Commit + = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" + = clipboard_button(clipboard_text: @commit.id) %span.cgray= pluralize(@commit.parents.count, "parent") - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" -- if @commit.status - .pull-right - = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do - = ci_icon_for_status(@commit.status) - build: - = ci_label_for_status(@commit.status) - -.commit-info-row.branches - %i.fa.fa-spinner.fa-spin + %span.commit-info.branches + %i.fa.fa-spinner.fa-spin .commit-box.content-block %h3.commit-title @@ -61,4 +59,4 @@ = preserve(markdown(escape_once(@commit.description), pipeline: :single_line)) :javascript - $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}"); + $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}"); diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml index 82aac1fbd15..2b0c9a4b4de 100644 --- a/app/views/projects/commit/branches.html.haml +++ b/app/views/projects/commit/branches.html.haml @@ -3,7 +3,6 @@ - branch = commit_default_branch(@project, @branches) = link_to(namespace_project_tree_path(@project.namespace, @project, branch)) do %span.label.label-gray - %i.fa.fa-code-fork = branch - if @branches.any? || @tags.any? = link_to("#", class: "js-details-expand") do diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 7a5b0d993db..d1bd76ab529 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -2,7 +2,8 @@ = nav_link(controller: [:commit, :commits]) do = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do Commits - %span.badge= number_with_delimiter(@repository.commit_count) + %span.badge + = number_with_delimiter(@repository.commit_count) = nav_link(controller: %w(network)) do = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index bcdb09208aa..088eaa28013 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -6,7 +6,7 @@ = render "head" -.gray-content-block.second-block +.row-content-block.second-block .tree-ref-holder = render 'shared/ref_switcher', destination: 'commits' diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 02be5a2d07f..5e188dd0f3c 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -2,7 +2,7 @@ = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block Compare branches, tags or commit ranges. %br Fill input field with commit id like diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index da731f28bb6..62525168239 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -3,7 +3,7 @@ = render "projects/commits/head" -.gray-content-block +.row-content-block = render "form" - if @commits.present? diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 52d093871b4..1a2e59752fe 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -7,7 +7,7 @@ = render "home_panel" -.gray-content-block.second-block.center +.row-content-block.second-block.center %h3.page-title The repository for this project is empty - if can?(current_user, :push_code, @project) diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml index 6fa77cc10c6..9f05be9982b 100644 --- a/app/views/projects/graphs/ci.html.haml +++ b/app/views/projects/graphs/ci.html.haml @@ -1,7 +1,7 @@ - page_title "Continuous Integration", "Graphs" = render "header_title" = render 'head' -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .oneline A collection of graphs for Continuous Integration diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index fc465ab273b..da9f648cc9c 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'head' -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs_commits' %ul.breadcrumb.repo-breadcrumb diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml index a7fab5b6d72..ebecab1dbfc 100644 --- a/app/views/projects/graphs/languages.html.haml +++ b/app/views/projects/graphs/languages.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'head' -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .oneline Programming languages used in this repository diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 882e7d6b6ee..ad4a932d391 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'head' -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs' %ul.breadcrumb.repo-breadcrumb diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 6f1ee209430..36c1d69f060 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -16,6 +16,11 @@ = f.label :url, "URL", class: "label-light" = f.text_field :url, class: "form-control", placeholder: "http://example.com/trigger-ci.json" .form-group + = f.label :token, "Secret Token", class: 'label-light' + = f.text_field :token, class: "form-control", placeholder: '' + %p.help-block + Use this token to validate received payloads + .form-group = f.label :url, "Trigger", class: "label-light" %div = f.check_box :push_events, class: "pull-left" diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 6027fb23360..a8a8caf7280 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -10,7 +10,7 @@ .panel-body %pre :preserve - #{@project.import_error.try(:strip)} + #{sanitize_repo_path(@project.import_error)} = form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f| = render "shared/import_form", f: f diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 56543ccd062..6ec84660157 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -24,15 +24,15 @@ - else = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" - = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-nr" do - = icon('trash-o') - Delete - = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do = icon('pencil-square-o') Edit -.detail-page-description.milestone-detail.second-block + = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do + = icon('trash-o') + Delete + +.detail-page-description.milestone-detail %h2.title = markdown escape_once(@milestone.title), pipeline: :single_line %div diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml index 28a617538b5..c609c505def 100644 --- a/app/views/projects/network/_head.html.haml +++ b/app/views/projects/network/_head.html.haml @@ -1,4 +1,4 @@ -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .tree-ref-holder = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 6f0b32aa165..0d59cec322c 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -2,7 +2,7 @@ = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block .oneline .title Release notes for tag diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index d854ac21725..74feb9e3282 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -12,7 +12,7 @@ = render 'projects/last_push' = render "home_panel" -.project-stats.gray-content-block.second-block +.project-stats.row-content-block.second-block %ul.nav %li = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 4af963e14da..103ff447464 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,7 +1,7 @@ - page_title "Snippets" = render "header_title" -.gray-content-block.top-block +.row-content-block.top-block .pull-right = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do = icon('plus') diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 760347de0a9..dc6ece30dd2 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -2,7 +2,7 @@ = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block - if can? current_user, :push_code, @project .pull-right = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 1dc9b799a95..9c916fd02de 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -2,7 +2,7 @@ = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block .pull-right - if can?(current_user, :push_code, @project) = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index dd27ea2b11b..ba3f2cadc48 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'nav' -.gray-content-block +.row-content-block %span.oneline Git access for %strong= @project_wiki.path_with_namespace diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 711337f308e..252c37532e1 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -1,7 +1,7 @@ - if @search_objects.empty? = render partial: "search/results/empty" - else - .gray-content-block + .row-content-block = search_entries_info(@search_objects, @scope, @search_term) - unless @show_snippets - if @project diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml index 34241cd8aad..b0fc60573f7 100644 --- a/app/views/shared/_confirm_modal.html.haml +++ b/app/views/shared/_confirm_modal.html.haml @@ -7,7 +7,7 @@ Confirmation required .modal-body - %p.cred.lead.js-confirm-text + %p.text-danger.js-confirm-text %p This action can lead to data loss. diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index fc1101646fb..9474462cbd1 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -1,5 +1,5 @@ .issues-filters - .issues-details-filters.gray-content-block.second-block + .issues-details-filters.row-content-block.second-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) .check-all-holder @@ -48,7 +48,7 @@ = button_tag "Update issues", class: "btn update_selected_issues btn-save" - if !@labels.nil? - .gray-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) } + .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) } - if @labels.any? = render "shared/labels_row", labels: @labels diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 18b091df39b..5c52cc6d1da 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -116,7 +116,7 @@ = link_to 'Change branches', mr_change_branches_path(@merge_request) - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) -.gray-content-block{class: (is_footer ? "footer-block" : "middle-block")} +.row-content-block{class: (is_footer ? "footer-block" : "middle-block")} - if issuable.new_record? = f.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create' - else diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index cab8743a077..7ff947a51db 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -24,7 +24,7 @@ - else = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" -.detail-page-description.gray-content-block.second-block +.detail-page-description.milestone-detail %h2.title = markdown escape_once(milestone.title), pipeline: :single_line diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index 3c445f67236..e65b1814872 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -20,6 +20,6 @@ - else = render "snippets/actions" -.detail-page-description.gray-content-block.second-block +.detail-page-description.row-content-block.second-block %h2.title = markdown escape_once(@snippet.title), pipeline: :single_line diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml index cfd11e45b6a..94d4dd4fa7d 100644 --- a/app/views/sherlock/file_samples/show.html.haml +++ b/app/views/sherlock/file_samples/show.html.haml @@ -3,7 +3,7 @@ - header_title t('sherlock.title'), sherlock_transactions_path -.gray-content-block +.row-content-block .pull-right = link_to(sherlock_transaction_path(@transaction), class: 'btn') do %i.fa.fa-arrow-left diff --git a/app/views/sherlock/queries/show.html.haml b/app/views/sherlock/queries/show.html.haml index 83f61ce4b07..fc2863dca8e 100644 --- a/app/views/sherlock/queries/show.html.haml +++ b/app/views/sherlock/queries/show.html.haml @@ -9,7 +9,7 @@ %a(href="#tab-backtrace" data-toggle="tab") = t('sherlock.backtrace') -.gray-content-block +.row-content-block .pull-right = link_to(sherlock_transaction_path(@transaction), class: 'btn') do %i.fa.fa-arrow-left diff --git a/app/views/sherlock/transactions/index.html.haml b/app/views/sherlock/transactions/index.html.haml index 010e1a2a902..da969c02765 100644 --- a/app/views/sherlock/transactions/index.html.haml +++ b/app/views/sherlock/transactions/index.html.haml @@ -1,7 +1,7 @@ - page_title t('sherlock.title') - header_title t('sherlock.title'), sherlock_transactions_path -.gray-content-block +.row-content-block .pull-right = link_to(destroy_all_sherlock_transactions_path, class: 'btn btn-danger', diff --git a/app/views/sherlock/transactions/show.html.haml b/app/views/sherlock/transactions/show.html.haml index 9d4b0b2724c..8aa6b437d95 100644 --- a/app/views/sherlock/transactions/show.html.haml +++ b/app/views/sherlock/transactions/show.html.haml @@ -16,7 +16,7 @@ %span.badge #{@transaction.file_samples.length} -.gray-content-block +.row-content-block .pull-right = link_to(sherlock_transactions_path, class: 'btn') do %i.fa.fa-arrow-left diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 0dff27f9654..3c0b89c6741 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -85,7 +85,7 @@ %div{ class: container_class } .tab-content #activity.tab-pane - .gray-content-block.calender-block.white.second-block.hidden-xs + .row-content-block.calender-block.white.second-block.hidden-xs %div{ class: container_class } .user-calendar{data: {href: user_calendar_path}} %h4.center.light @@ -98,7 +98,7 @@ #groups.tab-pane - # This tab is always loaded via AJAX - #contributed.contributed-projects.tab-pane + #contributed.tab-pane - # This tab is always loaded via AJAX #projects.tab-pane diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 59e12798691..4beb8746444 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -15,16 +15,16 @@ - if current_user :javascript - var get_emojis_url = "#{emojis_path}"; - var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"; - var noteable_type = "#{votable.class.name.underscore}"; - var noteable_id = "#{votable.id}"; + var getEmojisUrl = "#{emojis_path}"; + var postEmojiUrl = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"; + var noteableType = "#{votable.class.name.underscore}"; + var noteableId = "#{votable.id}"; var unicodes = #{AwardEmoji.unicode.to_json}; - window.awards_handler = new AwardsHandler( - get_emojis_url, - post_emoji_url, - noteable_type, - noteable_id, + window.awardsHandler = new AwardsHandler( + getEmojisUrl, + postEmojiUrl, + noteableType, + noteableId, unicodes ); diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb index a76729e3c74..f2d12ba5a7d 100644 --- a/app/workers/repository_check/single_repository_worker.rb +++ b/app/workers/repository_check/single_repository_worker.rb @@ -1,9 +1,9 @@ module RepositoryCheck class SingleRepositoryWorker include Sidekiq::Worker - + sidekiq_options retry: false - + def perform(project_id) project = Project.find(project_id) project.update_columns( @@ -11,20 +11,32 @@ module RepositoryCheck last_repository_check_at: Time.now, ) end - + private - + def check(project) - repositories = [project.repository] - repositories << project.wiki.repository if project.wiki_enabled? - # Use 'map do', not 'all? do', to prevent short-circuiting - repositories.map { |repository| git_fsck(repository.path_to_repo) }.all? + if !git_fsck(project.repository) + false + elsif project.wiki_enabled? + # Historically some projects never had their wiki repos initialized; + # this happens on project creation now. Let's initialize an empty repo + # if it is not already there. + begin + project.create_wiki + rescue Rugged::RepositoryError + end + + git_fsck(project.wiki.repository) + else + true + end end - - def git_fsck(path) + + def git_fsck(repository) + path = repository.path_to_repo cmd = %W(nice git --git-dir=#{path} fsck) output, status = Gitlab::Popen.popen(cmd) - + if status.zero? true else diff --git a/bin/background_jobs b/bin/background_jobs index 1f67d732949..25a578a1c49 100755 --- a/bin/background_jobs +++ b/bin/background_jobs @@ -37,7 +37,7 @@ start_no_deamonize() start_sidekiq() { - bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@" + exec bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@" } load_ok() @@ -19,12 +19,12 @@ get_unicorn_pid() start() { - $unicorn_cmd -D + exec $unicorn_cmd -D } start_foreground() { - $unicorn_cmd + exec $unicorn_cmd } stop() diff --git a/config/application.rb b/config/application.rb index abe22691ad1..d4b86bb38bb 100644 --- a/config/application.rb +++ b/config/application.rb @@ -32,7 +32,30 @@ module Gitlab config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables, :import_url) + # + # Parameters filtered: + # - Password (:password, :password_confirmation) + # - Private tokens (:private_token) + # - Two-factor tokens (:otp_attempt) + # - Repo/Project Import URLs (:import_url) + # - Build variables (:variables) + # - GitLab Pages SSL cert/key info (:certificate, :encrypted_key) + # - Webhook URLs (:hook) + # - Sentry DSN (:sentry_dsn) + # - Deploy keys (:key) + config.filter_parameters += %i( + certificate + encrypted_key + hook + import_url + key + otp_attempt + password + password_confirmation + private_token + sentry_dsn + variables + ) # Enable escaping HTML in JSON. config.active_support.escape_html_entities_in_json = true diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index e55ca6f9c6b..cbb7c656fe7 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -152,7 +152,6 @@ production: &base ## Gravatar ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html gravatar: - enabled: true # Use user avatar image from Gravatar.com (default: true) # gravatar urls: possible placeholders: %{hash} %{size} %{email} # plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon # ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon diff --git a/config/initializers/devise_async.rb b/config/initializers/devise_async.rb new file mode 100644 index 00000000000..05a1852cdbd --- /dev/null +++ b/config/initializers/devise_async.rb @@ -0,0 +1 @@ +Devise::Async.backend = :sidekiq diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 283936d0efc..b2d08d87bac 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -61,12 +61,30 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(const) end - Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path| - const = File.basename(path, '.rb').camelize.constantize - - config.instrument_instance_methods(const) + # Path to search => prefix to strip from constant + paths_to_instrument = { + ['app', 'finders'] => ['app', 'finders'], + ['app', 'mailers', 'emails'] => ['app', 'mailers'], + ['app', 'services', '**'] => ['app', 'services'], + ['lib', 'gitlab', 'diff'] => ['lib'], + ['lib', 'gitlab', 'email', 'message'] => ['lib'] + } + + paths_to_instrument.each do |(path, prefix)| + prefix = Rails.root.join(*prefix) + + Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path| + path = Pathname.new(file_path).relative_path_from(prefix) + const = path.to_s.sub('.rb', '').camelize.constantize + + config.instrument_methods(const) + config.instrument_instance_methods(const) + end end + config.instrument_methods(Premailer::Adapter::Nokogiri) + config.instrument_instance_methods(Premailer::Adapter::Nokogiri) + [ :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository, :Tag, :TagCollection, :Tree @@ -97,17 +115,6 @@ if Gitlab::Metrics.enabled? config.instrument_methods(Gitlab::ReferenceExtractor) config.instrument_instance_methods(Gitlab::ReferenceExtractor) - # Instrument all service classes - services = Rails.root.join('app', 'services') - - Dir[services.join('**', '*.rb')].each do |file_path| - path = Pathname.new(file_path).relative_path_from(services) - const = path.to_s.sub('.rb', '').camelize.constantize - - config.instrument_methods(const) - config.instrument_instance_methods(const) - end - # Instrument the classes used for checking if somebody has push access. config.instrument_instance_methods(Gitlab::GitAccess) config.instrument_instance_methods(Gitlab::GitAccessWiki) diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index e87899b2d5c..74fef7cadfe 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -15,6 +15,9 @@ if Rails.env.production? Raven.configure do |config| config.dsn = current_application_settings.sentry_dsn config.release = Gitlab::REVISION + + # Sanitize fields based on those sanitized from Rails. + config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) end end end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 88cb859871c..599dabb9e50 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -22,7 +22,7 @@ else key: '_gitlab_session', secure: Gitlab.config.gitlab.https, httponly: true, - expire_after: Settings.gitlab['session_expire_delay'] * 60, + expires_in: Settings.gitlab['session_expire_delay'] * 60, path: (Rails.application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root ) end diff --git a/config/routes.rb b/config/routes.rb index 5b48819dd9d..bfc6818a8d8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -216,8 +216,6 @@ Rails.application.routes.draw do resources :keys, only: [:show, :destroy] resources :identities, except: [:show] - delete 'stop_impersonation' => 'impersonation#destroy', on: :collection - member do get :projects get :keys @@ -227,12 +225,14 @@ Rails.application.routes.draw do put :unblock put :unlock put :confirm - post 'impersonate' => 'impersonation#create' + post :impersonate patch :disable_two_factor delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' end end + resource :impersonation, only: :destroy + resources :abuse_reports, only: [:index, :destroy] resources :spam_logs, only: [:index, :destroy] diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb index 0825776ffaa..87fb8e3300d 100644 --- a/db/fixtures/development/10_merge_requests.rb +++ b/db/fixtures/development/10_merge_requests.rb @@ -1,6 +1,9 @@ Gitlab::Seeder.quiet do + # Limit the number of merge requests per project to avoid long seeds + MAX_NUM_MERGE_REQUESTS = 10 + Project.all.reject(&:empty_repo?).each do |project| - branches = project.repository.branch_names + branches = project.repository.branch_names.sample(MAX_NUM_MERGE_REQUESTS * 2) branches.each do |branch_name| break if branches.size < 2 diff --git a/db/migrate/20160413115152_add_token_to_web_hooks.rb b/db/migrate/20160413115152_add_token_to_web_hooks.rb new file mode 100644 index 00000000000..f04225068cd --- /dev/null +++ b/db/migrate/20160413115152_add_token_to_web_hooks.rb @@ -0,0 +1,5 @@ +class AddTokenToWebHooks < ActiveRecord::Migration + def change + add_column :web_hooks, :token, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index bf46028d23f..7ea16e21358 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -70,16 +70,16 @@ ActiveRecord::Schema.define(version: 20160421130527) do t.string "recaptcha_site_key" t.string "recaptcha_private_key" t.integer "metrics_port", default: 8089 + t.boolean "akismet_enabled", default: false + t.string "akismet_api_key" t.integer "metrics_sample_interval", default: 15 t.boolean "sentry_enabled", default: false t.string "sentry_dsn" - t.boolean "akismet_enabled", default: false - t.string "akismet_api_key" t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" t.boolean "repository_checks_enabled", default: false - t.integer "metrics_packet_size", default: 1 t.text "shared_runners_text" + t.integer "metrics_packet_size", default: 1 end create_table "audit_events", force: :cascade do |t| @@ -426,10 +426,10 @@ ActiveRecord::Schema.define(version: 20160421130527) do t.string "state" t.integer "iid" t.integer "updated_by_id" - t.integer "moved_to_id" t.boolean "confidential", default: false t.datetime "deleted_at" t.date "due_date" + t.integer "moved_to_id" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -716,8 +716,8 @@ ActiveRecord::Schema.define(version: 20160421130527) do t.integer "project_id" t.text "data" t.text "encrypted_credentials" - t.text "encrypted_credentials_iv" - t.text "encrypted_credentials_salt" + t.string "encrypted_credentials_iv" + t.string "encrypted_credentials_salt" end create_table "projects", force: :cascade do |t| @@ -817,9 +817,9 @@ ActiveRecord::Schema.define(version: 20160421130527) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "active", default: false, null: false t.text "properties" t.boolean "template", default: false t.boolean "push_events", default: true @@ -1026,6 +1026,7 @@ ActiveRecord::Schema.define(version: 20160421130527) do t.boolean "enable_ssl_verification", default: true t.boolean "build_events", default: false, null: false t.boolean "wiki_page_events", default: false, null: false + t.string "token" end add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree diff --git a/doc/api/commits.md b/doc/api/commits.md index 6341440c58b..57c2e1d9b87 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -12,6 +12,8 @@ GET /projects/:id/repository/commits | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | | `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch | +| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | +| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | ```bash curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits" diff --git a/doc/api/notes.md b/doc/api/notes.md index 7aa1c2155bf..a6b5b1787fd 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -15,7 +15,7 @@ GET /projects/:id/issues/:issue_id/notes Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The ID of an issue +- `issue_id` (required) - The IID of an issue (not ID) ```json [ @@ -73,7 +73,7 @@ GET /projects/:id/issues/:issue_id/notes/:note_id Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The ID of a project issue +- `issue_id` (required) - The IID of a project issue (not ID) - `note_id` (required) - The ID of an issue note ### Create new issue note @@ -87,7 +87,7 @@ POST /projects/:id/issues/:issue_id/notes Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The ID of an issue +- `issue_id` (required) - The IID of an issue (not ID) - `body` (required) - The content of a note - `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z @@ -102,7 +102,7 @@ PUT /projects/:id/issues/:issue_id/notes/:note_id Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The ID of an issue +- `issue_id` (required) - The IID of an issue (not ID) - `note_id` (required) - The ID of a note - `body` (required) - The content of a note @@ -120,7 +120,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | -| `issue_id` | integer | yes | The ID of an issue | +| `issue_id` | integer | yes | The IID of an issue | | `note_id` | integer | yes | The ID of a note | ```bash diff --git a/doc/api/users.md b/doc/api/users.md index 7d2b4897cff..7e848586dbd 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -20,6 +20,7 @@ GET /users "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", + "web_url": "http://localhost:3000/u/john_smith" }, { "id": 2, @@ -27,6 +28,7 @@ GET /users "name": "Jack Smith", "state": "blocked", "avatar_url": "http://gravatar.com/../e32131cd8.jpeg", + "web_url": "http://localhost:3000/u/jack_smith" } ] ``` @@ -45,21 +47,31 @@ GET /users "email": "john@example.com", "name": "John Smith", "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", + "web_url": "http://localhost:3000/u/john_smith", "created_at": "2012-05-23T08:00:58Z", + "is_admin": false, "bio": null, + "location": null, "skype": "", "linkedin": "", "twitter": "", "website_url": "", - "extern_uid": "john.smith", - "provider": "provider_name", + "last_sign_in_at": "2012-06-01T11:41:01Z", + "confirmed_at": "2012-05-23T09:05:22Z", "theme_id": 1, "color_scheme_id": 2, - "is_admin": false, - "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", + "projects_limit": 100, + "current_sign_in_at": "2012-06-02T06:36:55Z", + "identities": [ + {"provider": "github", "extern_uid": "2435223452345"}, + {"provider": "bitbucket", "extern_uid": "john.smith"}, + {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"} + ], "can_create_group": true, - "current_sign_in_at": "2014-03-19T13:12:15Z", - "two_factor_enabled": true + "can_create_project": true, + "two_factor_enabled": true, + "external": false }, { "id": 2, @@ -67,24 +79,27 @@ GET /users "email": "jack@example.com", "name": "Jack Smith", "state": "blocked", + "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg", + "web_url": "http://localhost:3000/u/jack_smith", "created_at": "2012-05-23T08:01:01Z", + "is_admin": false, "bio": null, "location": null, "skype": "", "linkedin": "", "twitter": "", "website_url": "", - "extern_uid": "jack.smith", - "provider": "provider_name", + "last_sign_in_at": null, + "confirmed_at": "2012-05-30T16:53:06.148Z", "theme_id": 1, "color_scheme_id": 3, - "is_admin": false, - "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", - "can_create_group": true, - "can_create_project": true, "projects_limit": 100, "current_sign_in_at": "2014-03-19T17:54:13Z", - "two_factor_enabled": false + "identities": [], + "can_create_group": true, + "can_create_project": true, + "two_factor_enabled": true, + "external": false } ] ``` @@ -124,6 +139,7 @@ Parameters: "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", + "web_url": "http://localhost:3000/u/john_smith", "created_at": "2012-05-23T08:00:58Z", "is_admin": false, "bio": null, @@ -152,23 +168,31 @@ Parameters: "email": "john@example.com", "name": "John Smith", "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", + "web_url": "http://localhost:3000/u/john_smith", "created_at": "2012-05-23T08:00:58Z", - "confirmed_at": "2012-05-23T08:00:58Z", - "last_sign_in_at": "2015-03-23T08:00:58Z", + "is_admin": false, "bio": null, "location": null, "skype": "", "linkedin": "", "twitter": "", "website_url": "", - "extern_uid": "john.smith", - "provider": "provider_name", + "last_sign_in_at": "2012-06-01T11:41:01Z", + "confirmed_at": "2012-05-23T09:05:22Z", "theme_id": 1, "color_scheme_id": 2, - "is_admin": false, + "projects_limit": 100, + "current_sign_in_at": "2012-06-02T06:36:55Z", + "identities": [ + {"provider": "github", "extern_uid": "2435223452345"}, + {"provider": "bitbucket", "extern_uid": "john.smith"}, + {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"} + ], "can_create_group": true, "can_create_project": true, - "projects_limit": 100 + "two_factor_enabled": true, + "external": false } ``` @@ -261,21 +285,33 @@ GET /user "username": "john_smith", "email": "john@example.com", "name": "John Smith", - "private_token": "dd34asd13as", "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", + "web_url": "http://localhost:3000/u/john_smith", "created_at": "2012-05-23T08:00:58Z", + "is_admin": false, "bio": null, "location": null, "skype": "", "linkedin": "", "twitter": "", "website_url": "", + "last_sign_in_at": "2012-06-01T11:41:01Z", + "confirmed_at": "2012-05-23T09:05:22Z", "theme_id": 1, "color_scheme_id": 2, - "is_admin": false, + "projects_limit": 100, + "current_sign_in_at": "2012-06-02T06:36:55Z", + "identities": [ + {"provider": "github", "extern_uid": "2435223452345"}, + {"provider": "bitbucket", "extern_uid": "john_smith"}, + {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"} + ], "can_create_group": true, "can_create_project": true, - "projects_limit": 100 + "two_factor_enabled": true, + "external": false, + "private_token": "dd34asd13as" } ``` diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 5fb086b1dd9..ca52a483a59 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -41,9 +41,9 @@ GitLab Runner then executes build scripts as `gitlab-runner` user. --description "My Runner" ``` -2. Install Docker on server. +2. Install Docker Engine on server. - For more information how to install Docker on different systems checkout the [Supported installations](https://docs.docker.com/installation/). + For more information how to install Docker Engine on different systems checkout the [Supported installations](https://docs.docker.com/engine/installation/). 3. Add `gitlab-runner` user to `docker` group: @@ -151,4 +151,4 @@ In order to do that follow the steps: An example project using this approach can be found here: https://gitlab.com/gitlab-examples/docker. [docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/ -[docker-cap]: https://docs.docker.com/reference/run/#runtime-privilege-and-linux-capabilities +[docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 84212fb3c61..56ac2195c49 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -64,7 +64,7 @@ You can see some widely used services examples in the relevant documentation of ### How is service linked to the build To better understand how the container linking works, read -[Linking containers together](https://docs.docker.com/userguide/dockerlinks/). +[Linking containers together][linking-containers]. To summarize, if you add `mysql` as service to your application, the image will then be used to create a container that is linked to the build container. @@ -273,7 +273,7 @@ creation. [Docker Fundamentals]: https://docs.docker.com/engine/understanding-docker/ [hub]: https://hub.docker.com/ [linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/ -[tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/ -[postgres-hub]: https://registry.hub.docker.com/u/library/postgres/ -[mysql-hub]: https://registry.hub.docker.com/u/library/mysql/ +[tutum/wordpress]: https://hub.docker.com/r/tutum/wordpress/ +[postgres-hub]: https://hub.docker.com/r/_/postgres/ +[mysql-hub]: https://hub.docker.com/r/_/mysql/ [runner-priv-reg]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/configuration/advanced-configuration.md#using-a-private-docker-registry diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index cc059dc4376..61294be599d 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -4,12 +4,12 @@ - [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md) - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md) - [Test a Clojure application](test-clojure-application.md) -- [Using `dpl` as deployment tool](deployment/README.md) +- [Using `dpl` as deployment tool](../deployment/README.md) - Help your favorite programming language and GitLab by sending a merge request with a guide for that language. ## Outside the documentation -- [Blost post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) +- [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md index db077927126..26953014502 100644 --- a/doc/ci/examples/php.md +++ b/doc/ci/examples/php.md @@ -60,7 +60,7 @@ docker-php-ext-install pdo_mysql You might wonder what `docker-php-ext-install` is. In short, it is a script provided by the official php docker image that you can use to easilly install extensions. For more information read the the documentation at -<https://hub.docker.com/_/php/>. +<https://hub.docker.com/r/_/php/>. Now that we created the script that contains all prerequisites for our build environment, let's add it in `.gitlab-ci.yml`: @@ -92,7 +92,7 @@ Finally, commit your files and push them to GitLab to see your build succeeding The final `.gitlab-ci.yml` should look similar to this: ```yaml -# Select image from https://hub.docker.com/_/php/ +# Select image from https://hub.docker.com/r/_/php/ image: php:5.6 before_script: @@ -278,7 +278,7 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available Want to hack on it? Simply fork it, commit and push your changes. Within a few moments the changes will be picked by a public runner and the build will begin. -[php-hub]: https://hub.docker.com/_/php/ +[php-hub]: https://hub.docker.com/r/_/php/ [phpenv]: https://github.com/phpenv/phpenv [phpenv-installation]: https://github.com/phpenv/phpenv#installation [php-example-repo]: https://gitlab.com/gitlab-examples/php diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md index a236da53fe9..e4d3970deac 100644 --- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md +++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md @@ -8,7 +8,7 @@ This is what the `.gitlab-ci.yml` file looks like for this project: ```yaml test: script: - # this configures django application to use attached postgres database that is run on `postgres` host + # this configures Django application to use attached postgres database that is run on `postgres` host - export DATABASE_URL=postgres://postgres:@postgres:5432/python-test-app - apt-get update -qy - apt-get install -y python-dev python-pip @@ -37,7 +37,7 @@ production: ``` This project has three jobs: -1. `test` - used to test rails application, +1. `test` - used to test Django application, 2. `staging` - used to automatically deploy staging environment every push to `master` branch 3. `production` - used to automatically deploy production environmnet for every created tag @@ -61,12 +61,12 @@ gitlab-ci-multi-runner register \ --non-interactive \ --url "https://gitlab.com/ci/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ - --description "python-3.2" \ + --description "python-3.5" \ --executor "docker" \ - --docker-image python:3.2 \ + --docker-image python:3.5 \ --docker-postgres latest ``` -With the command above, you create a runner that uses [python:3.2](https://registry.hub.docker.com/u/library/python/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database. +With the command above, you create a runner that uses [python:3.5](https://hub.docker.com/r/_/python/) image and uses [postgres](https://hub.docker.com/r/_/postgres/) database. To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password. diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md index f5645d586ae..08c10d391ea 100644 --- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md +++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md @@ -1,5 +1,5 @@ ## Test and Deploy a ruby application -This example will guide you how to run tests in your Ruby application and deploy it automatically as Heroku application. +This example will guide you how to run tests in your Ruby on Rails application and deploy it automatically as Heroku application. You can checkout the example [source](https://gitlab.com/ayufan/ruby-getting-started) and check [CI status](https://gitlab.com/ayufan/ruby-getting-started/builds?scope=all). @@ -32,7 +32,7 @@ production: ``` This project has three jobs: -1. `test` - used to test rails application, +1. `test` - used to test Rails application, 2. `staging` - used to automatically deploy staging environment every push to `master` branch 3. `production` - used to automatically deploy production environmnet for every created tag @@ -62,6 +62,6 @@ gitlab-ci-multi-runner register \ --docker-postgres latest ``` -With the command above, you create a runner that uses [ruby:2.2](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database. +With the command above, you create a runner that uses [ruby:2.2](https://hub.docker.com/r/_/ruby/) image and uses [postgres](https://hub.docker.com/r/_/postgres/) database. To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password. diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md index c66d77122b2..aaf3aa77837 100644 --- a/doc/ci/services/mysql.md +++ b/doc/ci/services/mysql.md @@ -16,7 +16,7 @@ services: - mysql:latest variables: - # Configure mysql environment variables (https://hub.docker.com/_/mysql/) + # Configure mysql environment variables (https://hub.docker.com/r/_/mysql/) MYSQL_DATABASE: el_duderino MYSQL_ROOT_PASSWORD: mysql_strong_password ``` @@ -114,5 +114,5 @@ available [shared runners](../runners/README.md). Want to hack on it? Simply fork it, commit and push your changes. Within a few moments the changes will be picked by a public runner and the build will begin. -[hub-mysql]: https://hub.docker.com/_/mysql/ +[hub-mysql]: https://hub.docker.com/r/_/mysql/ [mysql-example-repo]: https://gitlab.com/gitlab-examples/mysql diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md index 17d21dbda1c..f787cc0a124 100644 --- a/doc/ci/services/postgres.md +++ b/doc/ci/services/postgres.md @@ -110,5 +110,5 @@ available [shared runners](../runners/README.md). Want to hack on it? Simply fork it, commit and push your changes. Within a few moments the changes will be picked by a public runner and the build will begin. -[hub-pg]: https://hub.docker.com/_/postgres/ +[hub-pg]: https://hub.docker.com/r/_/postgres/ [postgres-example-repo]: https://gitlab.com/gitlab-examples/postgres diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md index b281e8f9f60..80705024d2f 100644 --- a/doc/ci/services/redis.md +++ b/doc/ci/services/redis.md @@ -65,5 +65,5 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available Want to hack on it? Simply fork it, commit and push your changes. Within a few moments the changes will be picked by a public runner and the build will begin. -[hub-redis]: https://hub.docker.com/_/redis/ +[hub-redis]: https://hub.docker.com/r/_/redis/ [redis-example-repo]: https://gitlab.com/gitlab-examples/redis diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index 7f825e6a065..7c0fb225dac 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -57,7 +57,7 @@ before_script: # WARNING: Use this only with the Docker executor, if you use it with shell # you will overwrite your user's SSH config. - mkdir -p ~/.ssh - - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' + - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' ``` As a final step, add the _public_ key from the one you created earlier to the diff --git a/doc/development/README.md b/doc/development/README.md index 3f3ef068f96..aa7d54c01d0 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -8,6 +8,7 @@ - [How to dump production data to staging](db_dump.md) - [Instrumentation](instrumentation.md) - [Migration Style Guide](migration_style_guide.md) for creating safe migrations +- [Performance guidelines](performance.md) - [Rake tasks](rake_tasks.md) for development - [Shell commands](shell_commands.md) in the GitLab codebase - [Sidekiq debugging](sidekiq_debugging.md) diff --git a/doc/development/performance.md b/doc/development/performance.md new file mode 100644 index 00000000000..fb37b3a889c --- /dev/null +++ b/doc/development/performance.md @@ -0,0 +1,258 @@ +# Performance Guidelines + +This document describes various guidelines to follow to ensure good and +consistent performance of GitLab. + +## Workflow + +The process of solving performance problems is roughly as follows: + +1. Make sure there's an issue open somewhere (e.g., on the GitLab CE issue + tracker), create one if there isn't. See [#15607][#15607] for an example. +2. Measure the performance of the code in a production environment such as + GitLab.com (see the [Tooling](#tooling) section below). Performance should be + measured over a period of _at least_ 24 hours. +3. Add your findings based on the measurement period (screenshots of graphs, + timings, etc) to the issue mentioned in step 1. +4. Solve the problem. +5. Create a merge request, assign the "performance" label and ping the right + people (e.g. [@yorickpeterse][yorickpeterse] and [@joshfng][joshfng]). +6. Once a change has been deployed make sure to _again_ measure for at least 24 + hours to see if your changes have any impact on the production environment. +7. Repeat until you're done. + +When providing timings make sure to provide: + +* The 95th percentile +* The 99th percentile +* The mean + +When providing screenshots of graphs, make sure that both the X and Y axes and +the legend are clearly visible. If you happen to have access to GitLab.com's own +monitoring tools you should also provide a link to any relevant +graphs/dashboards. + +## Tooling + +GitLab provides two built-in tools to aid the process of improving performance: + +* [Sherlock](doc/development/profiling.md#sherlock) +* [GitLab Performance Monitoring](doc/monitoring/performance/monitoring.md) + +GitLab employees can use GitLab.com's performance monitoring systems located at +<http://performance.gitlab.net>, this requires you to log in using your +`@gitlab.com` Email address. Non-GitLab employees are advised to set up their +own InfluxDB + Grafana stack. + +## Benchmarks + +Benchmarks are almost always useless. Benchmarks usually only test small bits of +code in isolation and often only measure the best case scenario. On top of that, +benchmarks for libraries (e.g., a Gem) tend to be biased in favour of the +library. After all there's little benefit to an author publishing a benchmark +that shows they perform worse than their competitors. + +Benchmarks are only really useful when you need a rough (emphasis on "rough") +understanding of the impact of your changes. For example, if a certain method is +slow a benchmark can be used to see if the changes you're making have any impact +on the method's performance. However, even when a benchmark shows your changes +improve performance there's no guarantee the performance also improves in a +production environment. + +When writing benchmarks you should almost always use +[benchmark-ips](https://github.com/evanphx/benchmark-ips). Ruby's `Benchmark` +module that comes with the standard library is rarely useful as it runs either a +single iteration (when using `Benchmark.bm`) or two iterations (when using +`Benchmark.bmbm`). Running this few iterations means external factors (e.g. a +video streaming in the background) can very easily skew the benchmark +statistics. + +Another problem with the `Benchmark` module is that it displays timings, not +iterations. This means that if a piece of code completes in a very short period +of time it can be very difficult to compare the timings before and after a +certain change. This in turn leads to patterns such as the following: + +```ruby +Benchmark.bmbm(10) do |bench| + bench.report 'do something' do + 100.times do + ... work here ... + end + end +end +``` + +This however leads to the question: how many iterations should we run to get +meaningful statistics? + +The benchmark-ips Gem basically takes care of all this and much more, and as a +result of this should be used instead of the `Benchmark` module. + +In short: + +1. Don't trust benchmarks you find on the internet. +2. Never make claims based on just benchmarks, always measure in production to + confirm your findings. +3. X being N times faster than Y is meaningless if you don't know what impact it + will actually have on your production environment. +4. A production environment is the _only_ benchmark that always tells the truth + (unless your performance monitoring systems are not set up correctly). +5. If you must write a benchmark use the benchmark-ips Gem instead of Ruby's + `Benchmark` module. + +## Importance of Changes + +When working on performance improvements, it's important to always ask yourself +the question "How important is it to improve the performance of this piece of +code?". Not every piece of code is equally important and it would be a waste to +spend a week trying to improve something that only impacts a tiny fraction of +our users. For example, spending a week trying to squeeze 10 milliseconds out of +a method is a waste of time when you could have spent a week squeezing out 10 +seconds elsewhere. + +There is no clear set of steps that you can follow to determine if a certain +piece of code is worth optimizing. The only two things you can do are: + +1. Think about what the code does, how it's used, how many times it's called and + how much time is spent in it relative to the total execution time (e.g., the + total time spent in a web request). +2. Ask others (preferably in the form of an issue). + +Some examples of changes that aren't really important/worth the effort: + +* Replacing double quotes with single quotes. +* Replacing usage of Array with Set when the list of values is very small. +* Replacing library A with library B when both only take up 0.1% of the total + execution time. +* Calling `freeze` on every string (see [String Freezing](#string-freezing)). + +## Slow Operations & Sidekiq + +Slow operations (e.g. merging branches) or operations that are prone to errors +(using external APIs) should be performed in a Sidekiq worker instead of +directly in a web request as much as possible. This has numerous benefits such +as: + +1. An error won't prevent the request from completing. +2. The process being slow won't affect the loading time of a page. +3. In case of a failure it's easy to re-try the process (Sidekiq takes care of + this automatically). +4. By isolating the code from a web request it will hopefully be easier to test + and maintain. + +It's especially important to use Sidekiq as much as possible when dealing with +Git operations as these operations can take quite some time to complete +depending on the performance of the underlying storage system. + +## Git Operations + +Care should be taken to not run unnecessary Git operations. For example, +retrieving the list of branch names using `Repository#branch_names` can be done +without an explicit check if a repository exists or not. In other words, instead +of this: + +```ruby +if repository.exists? + repository.branch_names.each do |name| + ... + end +end +``` + +You can just write: + +```ruby +repository.branch_names.each do |name| + ... +end +``` + +## Caching + +Operations that will often return the same result should be cached using Redis, +in particular Git operations. When caching data in Redis, make sure the cache is +flushed whenever needed. For example, a cache for the list of tags should be +flushed whenever a new tag is pushed or a tag is removed. + +When adding cache expiration code for repositories, this code should be placed +in one of the before/after hooks residing in the Repository class. For example, +if a cache should be flushed after importing a repository this code should be +added to `Repository#after_import`. This ensures the cache logic stays within +the Repository class instead of leaking into other classes. + +When caching data, make sure to also memoize the result in an instance variable. +While retrieving data from Redis is much faster than raw Git operations, it still +has overhead. By caching the result in an instance variable, repeated calls to +the same method won't end up retrieving data from Redis upon every call. When +memoizing cached data in an instance variable, make sure to also reset the +instance variable when flushing the cache. An example: + + +```ruby +def first_branch + @first_branch ||= cache.fetch(:first_branch) { branches.first } +end + +def expire_first_branch_cache + cache.expire(:first_branch) + @first_branch = nil +end +``` + +## Anti-Patterns + +This is a collection of [anti-patterns][anti-pattern] that should be avoided +unless these changes have a measurable, significant and positive impact on +production environments. + +### String Freezing + +In recent Ruby versions calling `freeze` on a String leads to it being allocated +only once and re-used. For example, on Ruby 2.3 this will only allocate the +"foo" String once: + +```ruby +10.times do + 'foo'.freeze +end +``` + +Blindly adding a `.freeze` call to every String is an anti-pattern that should +be avoided unless one can prove (using production data) the call actually has a +positive impact on performance. + +This feature of Ruby wasn't really meant to make things faster directly, instead +it was meant to reduce the number of allocations. Depending on the size of the +String and how frequently it would be allocated (before the `.freeze` call was +added), this _may_ make things faster, but there's no guarantee it will. + +Another common flavour of this is to not only freeze a String, but also assign +it to a constant, for example: + +```ruby +SOME_CONSTANT = 'foo'.freeze + +9000.times do + SOME_CONSTANT +end +``` + +The only reason you should be doing this is to prevent somebody from mutating +the global String. However, since you can just re-assign constants in Ruby +there's nothing stopping somebody from doing this elsewhere in the code: + +```ruby +SOME_CONSTANT = 'bar' +``` + +### Moving Allocations to Constants + +Storing an object as a constant so you only allocate it once _may_ improve +performance, but there's no guarantee this will. Looking up constants has an +impact on runtime performance, and as such, using a constant instead of +referencing an object directly may even slow code down. + +[#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607 +[yorickpeterse]: https://gitlab.com/u/yorickpeterse +[joshfng]: https://gitlab.com/u/joshfng +[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern diff --git a/doc/development/testing.md b/doc/development/testing.md index 672e3fb4649..33eed29ba5c 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -64,6 +64,7 @@ the command line via `bundle exec teaspoon`, or via a web browser at methods. - Use `context` to test branching logic. - Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)). +- Don't supply the `:each` argument to hooks since it's the default. - Prefer `not_to` to `to_not`. - Try to match the ordering of tests to the ordering within the class. - Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines diff --git a/doc/install/installation.md b/doc/install/installation.md index e721e70a596..e3af3022262 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -157,22 +157,64 @@ Create a `git` user for GitLab: ## 5. Database -We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md). *Note*: because we need to make use of extensions you need at least pgsql 9.1. +We recommend using a PostgreSQL database. For MySQL check the +[MySQL setup guide](database_mysql.md). - # Install the database packages - sudo apt-get install -y postgresql postgresql-client libpq-dev +> **Note**: because we need to make use of extensions you need at least pgsql 9.1. - # Create a user for GitLab +1. Install the database packages: + + ```bash + sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib + ``` + +1. Create a database user for GitLab: + + ```bash sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;" + ``` + +1. Create the GitLab production database and grant all privileges on database: - # Create the GitLab production database & grant all privileges on database + ```bash sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;" + ``` + +1. Create the `pg_trgm` extension (required for GitLab 8.6+): + + ```bash + sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + ``` + +1. Try connecting to the new database with the new user: - # Try connecting to the new database with the new user + ```bash sudo -u git -H psql -d gitlabhq_production + ``` + +1. Check if the `pg_trgm` extension is enabled: + + ```bash + SELECT true AS enabled + FROM pg_available_extensions + WHERE name = 'pg_trgm' + AND installed_version IS NOT NULL; + ``` + + If the extension is enabled this will produce the following output: - # Quit the database session + ``` + enabled + --------- + t + (1 row) + ``` + +1. Quit the database session: + + ```bash gitlabhq_production> \q + ``` ## 6. Redis diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 58f409746cd..df8e8bdc476 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -64,7 +64,10 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim ### Memory You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab! -With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage. +The operating system and any other running applications will also be using memory +so keep in mind that you need at least 2GB available before running GitLab. With +less memory GitLab will give strange errors during the reconfigure run and 500 +errors during usage. - 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice. - 1GB RAM + 1GB swap supports up to 100 users but it will be very slow @@ -77,6 +80,10 @@ With less memory GitLab will give strange errors during the reconfigure run and - 128GB RAM supports up to 32,000 users - More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/) +We recommend having at least 1GB of swap on your server, even if you currently have +enough available RAM. Having swap will help reduce the chance of errors occuring +if your available memory changes. + Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those. ## Gitlab Runner diff --git a/doc/integration/github.md b/doc/integration/github.md index e1f9242fd0e..e7497e475c9 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -9,7 +9,9 @@ GitHub will generate an application ID and secret key for you to use. 1. Navigate to your individual user settings or an organization's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or an organization - that is entirely up to you. -1. Select "Applications" in the left menu. +1. Select "OAuth applications" in the left menu. + +1. If you already have applications listed, switch to the "Developer applications" tab. 1. Select "Register new application". diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md index 90e99302210..771584268d9 100644 --- a/doc/monitoring/performance/gitlab_configuration.md +++ b/doc/monitoring/performance/gitlab_configuration.md @@ -37,4 +37,4 @@ Read more on: - [Introduction to GitLab Performance Monitoring](introduction.md) - [InfluxDB Configuration](influxdb_configuration.md) - [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md index 63aa03985ef..c30cd2950d8 100644 --- a/doc/monitoring/performance/influxdb_configuration.md +++ b/doc/monitoring/performance/influxdb_configuration.md @@ -181,7 +181,7 @@ Read more on: - [Introduction to GitLab Performance Monitoring](introduction.md) - [GitLab Configuration](gitlab_configuration.md) - [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md +- [Grafana Install/Configuration](grafana_configuration.md) [influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management [influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md index d31b3788f36..41861860b6d 100644 --- a/doc/monitoring/performance/influxdb_schema.md +++ b/doc/monitoring/performance/influxdb_schema.md @@ -85,4 +85,4 @@ Read more on: - [Introduction to GitLab Performance Monitoring](introduction.md) - [GitLab Configuration](gitlab_configuration.md) - [InfluxDB Configuration](influxdb_configuration.md) -- [Grafana Install/Configuration](grafana_configuration.md +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index 60729316cde..b4283a526f3 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -57,10 +57,10 @@ sudo -u git -H make cd /home/git/gitlab # PostgreSQL -sudo -u git -H bundle install --without development test mysql --deployment +sudo -u git -H bundle install --without development test mysql --with postgres --deployment # MySQL -sudo -u git -H bundle install --without development test postgres --deployment +sudo -u git -H bundle install --without development test postgres --with mysql --deployment # Optional: clean up old gems sudo -u git -H bundle clean diff --git a/docker/README.md b/docker/README.md index 7514d610aec..ee1f32adc26 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,7 +1,7 @@ # GitLab Docker images -* The official GitLab Community Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/). -* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ee/). +* The official GitLab Community Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ce/). +* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ee/). * The complete usage guide can be found in [Using GitLab Docker images](http://doc.gitlab.com/omnibus/docker/) * The Dockerfile used for building public images is in [Omnibus Repository](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker) -* Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#Build-Docker-image) +* Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#build-docker-image) diff --git a/features/groups.feature b/features/groups.feature index 419a5d3963d..49e939807b5 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -7,10 +7,6 @@ Feature: Groups When I visit group "NonExistentGroup" page Then page status code should be 404 - Scenario: I should have back to group button - When I visit group "Owned" page - Then I should see back to dashboard button - @javascript Scenario: I should see group "Owned" dashboard list When I visit group "Owned" page diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb index a167d259837..f5fddab357d 100644 --- a/features/steps/group/milestones.rb +++ b/features/steps/group/milestones.rb @@ -5,7 +5,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps include SharedUser step 'I click on group milestones' do - click_link 'Milestones' + page.within('.layout-nav') do + click_link 'Milestones' + end end step 'I should see group milestones index page has no milestones' do @@ -84,7 +86,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps end step 'I click on the "Labels" tab' do - page.within('.nav-links') do + page.within('.content .nav-links') do page.find(:xpath, "//a[@href='#tab-labels']").click end end diff --git a/features/steps/groups.rb b/features/steps/groups.rb index e5b7db4c5e3..483370f41c6 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -4,10 +4,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps include SharedGroup include SharedUser - step 'I should see back to dashboard button' do - expect(page).to have_content 'Go to dashboard' - end - step 'I should see group "Owned"' do expect(page).to have_content '@owned' end diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb index eff4234a44a..912ba580efd 100644 --- a/features/steps/project/commits/tags.rb +++ b/features/steps/project/commits/tags.rb @@ -57,11 +57,11 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps end step 'I should see new an error that tag ref is invalid' do - expect(page).to have_content 'Invalid reference name' + expect(page).to have_content 'Target foo is invalid' end step 'I should see new an error that tag already exists' do - expect(page).to have_content 'Tag already exists' + expect(page).to have_content 'Tag v1.0.0 already exists' end step "I visit tag 'v1.1.0' page" do diff --git a/features/steps/user.rb b/features/steps/user.rb index 3230234cb6d..b1d088f07f9 100644 --- a/features/steps/user.rb +++ b/features/steps/user.rb @@ -12,7 +12,7 @@ class Spinach::Features::User < Spinach::FeatureSteps user = User.find_by(name: 'John Doe') project = contributed_project - # Issue controbution + # Issue contribution issue_params = { title: 'Bug in old browser' } Issues::CreateService.new(project, user, issue_params).execute @@ -28,7 +28,7 @@ class Spinach::Features::User < Spinach::FeatureSteps end step 'I should see contributed projects' do - page.within '.contributed-projects' do + page.within '#contributed' do expect(page).to have_content(@contributed_project.name) end end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 4544a41b1e3..93a3a5ce089 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -12,14 +12,20 @@ module API # Parameters: # id (required) - The ID of a project # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used + # since (optional) - Only commits after or in this date will be returned + # until (optional) - Only commits before or in this date will be returned # Example Request: # GET /projects/:id/repository/commits get ":id/repository/commits" do + datetime_attributes! :since, :until + page = (params[:page] || 0).to_i per_page = (params[:per_page] || 20).to_i ref = params[:ref_name] || user_project.try(:default_branch) || 'master' + after = params[:since] + before = params[:until] - commits = user_project.repository.commits(ref, nil, per_page, page * per_page) + commits = user_project.repository.commits(ref, limit: per_page, offset: page * per_page, after: after, before: before) present commits, with: Entities::RepoCommit end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 5bbf721321d..40c967453fb 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -183,6 +183,22 @@ module API Gitlab::Access.options_with_owner.values.include? level.to_i end + # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601 + # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked. + # + # Parameters: + # keys (required) - An array consisting of elements that must be parseable as dates from the params hash + def datetime_attributes!(*keys) + keys.each do |key| + begin + params[key] = Time.xmlschema(params[key]) if params[key].present? + rescue ArgumentError + message = "\"" + key.to_s + "\" must be a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ" + render_api_error!(message, 400) + end + end + end + def issuable_order_by if params["order_by"] == 'updated_at' 'updated_at' diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 84b4d4cdd6d..132043cf3f7 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -105,7 +105,15 @@ module API authorize! :read_milestone, user_project @milestone = user_project.milestones.find(params[:milestone_id]) - present paginate(@milestone.issues), with: Entities::Issue, current_user: current_user + + finder_params = { + project_id: user_project.id, + milestone_title: @milestone.title, + state: 'all' + } + + issues = IssuesFinder.new(current_user, finder_params).execute + present paginate(issues), with: Entities::Issue, current_user: current_user end end diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 22ce3c6a066..ce1bf0d26d2 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -11,6 +11,11 @@ module API end not_found! end + + def snippets_for_current_user + finder_params = { filter: :by_project, project: user_project } + SnippetsFinder.new.execute(current_user, finder_params) + end end # Get a project snippets @@ -20,7 +25,7 @@ module API # Example Request: # GET /projects/:id/snippets get ":id/snippets" do - present paginate(user_project.snippets), with: Entities::ProjectSnippet + present paginate(snippets_for_current_user), with: Entities::ProjectSnippet end # Get a project snippet @@ -31,7 +36,7 @@ module API # Example Request: # GET /projects/:id/snippets/:snippet_id get ":id/snippets/:snippet_id" do - @snippet = user_project.snippets.find(params[:snippet_id]) + @snippet = snippets_for_current_user.find(params[:snippet_id]) present @snippet, with: Entities::ProjectSnippet end @@ -73,7 +78,7 @@ module API # Example Request: # PUT /projects/:id/snippets/:snippet_id put ":id/snippets/:snippet_id" do - @snippet = user_project.snippets.find(params[:snippet_id]) + @snippet = snippets_for_current_user.find(params[:snippet_id]) authorize! :update_project_snippet, @snippet attrs = attributes_for_keys [:title, :file_name, :visibility_level] @@ -97,7 +102,7 @@ module API # DELETE /projects/:id/snippets/:snippet_id delete ":id/snippets/:snippet_id" do begin - @snippet = user_project.snippets.find(params[:snippet_id]) + @snippet = snippets_for_current_user.find(params[:snippet_id]) authorize! :update_project_snippet, @snippet @snippet.destroy rescue @@ -113,7 +118,7 @@ module API # Example Request: # GET /projects/:id/snippets/:snippet_id/raw get ":id/snippets/:snippet_id/raw" do - @snippet = user_project.snippets.find(params[:snippet_id]) + @snippet = snippets_for_current_user.find(params[:snippet_id]) env['api.format'] = :txt content_type 'text/plain' diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 5e2fb863a8f..132f9cd1966 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -79,24 +79,6 @@ module Gitlab 'rm-project', "#{name}.git"]) end - # Add repository tag from passed ref - # - # path - project path with namespace - # tag_name - new tag name - # ref - HEAD for new tag - # message - optional message for tag (annotated tag) - # - # Ex. - # add_tag("gitlab/gitlab-ci", "v4.0", "master") - # add_tag("gitlab/gitlab-ci", "v4.0", "master", "message") - # - def add_tag(path, tag_name, ref, message = nil) - cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git - #{tag_name} #{ref}) - cmd << message unless message.nil? || message.empty? - Gitlab::Utils.system_silent(cmd) - end - # Gc repository # # path - project path with namespace diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb index 9bb507b5edd..9b83292ef33 100644 --- a/lib/gitlab/bitbucket_import/client.rb +++ b/lib/gitlab/bitbucket_import/client.rb @@ -12,7 +12,7 @@ module Gitlab token_secret = import_data_credentials[:bb_session][:bitbucket_access_token_secret] new(token, token_secret) else - raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" + raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{project.id}" end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 3ed1eec517c..6cb41239871 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -128,7 +128,7 @@ module Gitlab action = if project.protected_branch?(branch_name(ref)) protected_branch_action(oldrev, newrev, branch_name(ref)) - elsif protected_tag?(tag_name(ref)) + elsif (tag_ref = tag_name(ref)) && protected_tag?(tag_ref) # Prevent any changes to existing git tag unless user has permissions :admin_project else @@ -176,7 +176,7 @@ module Gitlab end def protected_tag?(tag_name) - project.repository.tag_names.include?(tag_name) + project.repository.tag_exists?(tag_name) end def user_allowed? diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb index 7d58e53991a..7d679eaec6a 100644 --- a/lib/gitlab/github_import/comment_formatter.rb +++ b/lib/gitlab/github_import/comment_formatter.rb @@ -28,13 +28,26 @@ module Gitlab end def line_code - if on_diff? - Gitlab::Diff::LineCode.generate(raw_data.path, raw_data.position, 0) - end + return unless on_diff? + + parsed_lines = Gitlab::Diff::Parser.new.parse(diff_hunk.lines) + generate_line_code(parsed_lines.to_a.last) + end + + def generate_line_code(line) + Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) end def on_diff? - raw_data.path && raw_data.position + diff_hunk.present? + end + + def diff_hunk + raw_data.diff_hunk + end + + def file_path + raw_data.path end def note diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb index 67622f321a6..c8f12577112 100644 --- a/lib/gitlab/push_data_builder.rb +++ b/lib/gitlab/push_data_builder.rb @@ -66,7 +66,7 @@ module Gitlab # This method provide a sample data generated with # existing project and commits to test webhooks def build_sample(project, user) - commits = project.repository.commits(project.default_branch, nil, 3) + commits = project.repository.commits(project.default_branch, limit: 3) ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}" build(project, user, commits.last.id, commits.first.id, ref, commits) end diff --git a/lib/gitlab/sanitizers/svg.rb b/lib/gitlab/sanitizers/svg.rb new file mode 100644 index 00000000000..b98589dff89 --- /dev/null +++ b/lib/gitlab/sanitizers/svg.rb @@ -0,0 +1,37 @@ +require_relative "svg/whitelist" + +module Gitlab + module Sanitizers + module SVG + def self.clean(data) + Loofah.xml_document(data).scrub!(Scrubber.new).to_s + end + + class Scrubber < Loofah::Scrubber + # http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#embedding-custom-non-visible-data-with-the-data-*-attributes + DATA_ATTR_PATTERN = /\Adata-(?!xml)[a-z_][\w.\u00E0-\u00F6\u00F8-\u017F\u01DD-\u02AF-]*\z/u + + def scrub(node) + unless ALLOWED_ELEMENTS.include?(node.name) + node.unlink + else + node.attributes.each do |attr_name, attr| + valid_attributes = ALLOWED_ATTRIBUTES[node.name] + + unless valid_attributes && valid_attributes.include?(attr_name) + if ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS.include?(node.name) && + attr_name.start_with?('data-') + # Arbitrary data attributes are allowed. Verify that the attribute + # is a valid data attribute. + attr.unlink unless attr_name =~ DATA_ATTR_PATTERN + else + attr.unlink + end + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/sanitizers/svg/whitelist.rb b/lib/gitlab/sanitizers/svg/whitelist.rb new file mode 100644 index 00000000000..917e795b29e --- /dev/null +++ b/lib/gitlab/sanitizers/svg/whitelist.rb @@ -0,0 +1,107 @@ +# Generated from: +# SVG element list: https://www.w3.org/TR/SVG/eltindex.html +# SVG Attribute list: https://www.w3.org/TR/SVG/attindex.html +module Gitlab + module Sanitizers + module SVG + ALLOWED_ELEMENTS = %w[ + a altGlyph altGlyphDef altGlyphItem animate + animateColor animateMotion animateTransform circle clipPath color-profile + cursor defs desc ellipse feBlend feColorMatrix feComponentTransfer + feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap + feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur + feImage feMerge feMergeNode feMorphology feOffset fePointLight + feSpecularLighting feSpotLight feTile feTurbulence filter font font-face + font-face-format font-face-name font-face-src font-face-uri foreignObject + g glyph glyphRef hkern image line linearGradient marker mask metadata + missing-glyph mpath path pattern polygon polyline radialGradient rect + script set stop style svg switch symbol text textPath title tref tspan use + view vkern].freeze + + ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS = %w[svg].freeze + + ALLOWED_ATTRIBUTES = { + 'a' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage target text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'altGlyph' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format glyph-orientation-horizontal glyph-orientation-vertical glyphRef id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y], + 'altGlyphDef' => %w[id xml:base xml:lang xml:space], + 'altGlyphItem' => %w[id xml:base xml:lang xml:space], + 'animate' => %w[accumulate additive alignment-baseline attributeName attributeType baseline-shift begin by calcMode clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dur enable-background end externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight from glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning keySplines keyTimes letter-spacing lighting-color marker-end marker-mid marker-start mask max min onbegin onend onload onrepeat opacity overflow pointer-events repeatCount repeatDur requiredExtensions requiredFeatures restart shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width systemLanguage text-anchor text-decoration text-rendering to unicode-bidi values visibility word-spacing writing-mode xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'animateColor' => %w[accumulate additive alignment-baseline attributeName attributeType baseline-shift begin by calcMode clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dur enable-background end externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight from glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning keySplines keyTimes letter-spacing lighting-color marker-end marker-mid marker-start mask max min onbegin onend onload onrepeat opacity overflow pointer-events repeatCount repeatDur requiredExtensions requiredFeatures restart shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width systemLanguage text-anchor text-decoration text-rendering to unicode-bidi values visibility word-spacing writing-mode xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'animateMotion' => %w[accumulate additive begin by calcMode dur end externalResourcesRequired fill from id keyPoints keySplines keyTimes max min onbegin onend onload onrepeat origin path repeatCount repeatDur requiredExtensions requiredFeatures restart rotate systemLanguage to values xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'animateTransform' => %w[accumulate additive attributeName attributeType begin by calcMode dur end externalResourcesRequired fill from id keySplines keyTimes max min onbegin onend onload onrepeat repeatCount repeatDur requiredExtensions requiredFeatures restart systemLanguage to type values xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'circle' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor cx cy direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events r requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'clipPath' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule clipPathUnits color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'color-profile' => %w[id local name rendering-intent xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'cursor' => %w[externalResourcesRequired id requiredExtensions requiredFeatures systemLanguage x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y], + 'defs' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'desc' => %w[class id style xml:base xml:lang xml:space], + 'ellipse' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor cx cy direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rx ry shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'feBlend' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in in2 kerning letter-spacing lighting-color marker-end marker-mid marker-start mask mode opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feColorMatrix' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering type unicode-bidi values visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feComponentTransfer' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feComposite' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in in2 k1 k2 k3 k4 kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity operator overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feConvolveMatrix' => %w[alignment-baseline baseline-shift bias class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display divisor dominant-baseline edgeMode enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kernelMatrix kernelUnitLength kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity order overflow pointer-events preserveAlpha result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style targetX targetY text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feDiffuseLighting' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor diffuseConstant direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kernelUnitLength kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feDisplacementMap' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in in2 kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result scale shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xChannelSelector xml:base xml:lang xml:space y yChannelSelector], + 'feDistantLight' => %w[azimuth elevation id xml:base xml:lang xml:space], + 'feFlood' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feFuncA' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space], + 'feFuncB' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space], + 'feFuncG' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space], + 'feFuncR' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space], + 'feGaussianBlur' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stdDeviation stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feImage' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events preserveAspectRatio result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y], + 'feMerge' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feMergeNode' => %w[id xml:base xml:lang xml:space], + 'feMorphology' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity operator overflow pointer-events radius result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feOffset' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'fePointLight' => %w[id x xml:base xml:lang xml:space y z], + 'feSpecularLighting' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kernelUnitLength kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering specularConstant specularExponent stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feSpotLight' => %w[id limitingConeAngle pointsAtX pointsAtY pointsAtZ specularExponent x xml:base xml:lang xml:space y z], + 'feTile' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'feTurbulence' => %w[alignment-baseline baseFrequency baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask numOctaves opacity overflow pointer-events result seed shape-rendering stitchTiles stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering type unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'filter' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events primitiveUnits shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y], + 'font' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x horiz-origin-y id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi vert-adv-y vert-origin-x vert-origin-y visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'font-face' => %w[accent-height alphabetic ascent bbox cap-height descent font-family font-size font-stretch font-style font-variant font-weight hanging id ideographic mathematical overline-position overline-thickness panose-1 slope stemh stemv strikethrough-position strikethrough-thickness underline-position underline-thickness unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical widths x-height xml:base xml:lang xml:space], + 'font-face-format' => %w[id string xml:base xml:lang xml:space], + 'font-face-name' => %w[id name xml:base xml:lang xml:space], + 'font-face-src' => %w[id xml:base xml:lang xml:space], + 'font-face-uri' => %w[id xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'foreignObject' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'g' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'glyph' => %w[alignment-baseline arabic-form baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor d direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x id image-rendering kerning lang letter-spacing lighting-color marker-end marker-mid marker-start mask opacity orientation overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode unicode-bidi vert-adv-y vert-origin-x vert-origin-y visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'glyphRef' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format glyph-orientation-horizontal glyph-orientation-vertical glyphRef id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y], + 'hkern' => %w[g1 g2 id k u1 u2 xml:base xml:lang xml:space], + 'image' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events preserveAspectRatio requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y], + 'line' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode x1 x2 xml:base xml:lang xml:space y1 y2], + 'linearGradient' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical gradientTransform gradientUnits id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering spreadMethod stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode x1 x2 xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space y1 y2], + 'marker' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask opacity orient overflow pointer-events preserveAspectRatio refX refY shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi viewBox visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'mask' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask maskContentUnits maskUnits opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'metadata' => %w[id xml:base xml:lang xml:space], + 'missing-glyph' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor d direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi vert-adv-y vert-origin-x vert-origin-y visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'mpath' => %w[externalResourcesRequired id xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'path' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor d direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pathLength pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'pattern' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow patternContentUnits patternTransform patternUnits pointer-events preserveAspectRatio requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi viewBox visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y], + 'polygon' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events points requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'polyline' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events points requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'radialGradient' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor cx cy direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight fx fy glyph-orientation-horizontal glyph-orientation-vertical gradientTransform gradientUnits id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events r shape-rendering spreadMethod stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space], + 'rect' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rx ry shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y], + 'script' => %w[externalResourcesRequired id type xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'set' => %w[attributeName attributeType begin dur end externalResourcesRequired fill id max min onbegin onend onload onrepeat repeatCount repeatDur requiredExtensions requiredFeatures restart systemLanguage to xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space], + 'stop' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask offset opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'style' => %w[id media title type xml:base xml:lang xml:space], + 'svg' => %w[alignment-baseline baseProfile baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onabort onactivate onclick onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onresize onscroll onunload onzoom opacity overflow pointer-events preserveAspectRatio requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi version viewBox visibility width word-spacing writing-mode x xml:base xml:lang xml:space xmlns y zoomAndPan], + 'switch' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'symbol' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events preserveAspectRatio shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi viewBox visibility word-spacing writing-mode xml:base xml:lang xml:space], + 'text' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength transform unicode-bidi visibility word-spacing writing-mode x xml:base xml:lang xml:space y], + 'textPath' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask method onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering spacing startOffset stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength unicode-bidi visibility word-spacing writing-mode xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space], + 'title' => %w[class id style xml:base xml:lang xml:space], + 'tref' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength unicode-bidi visibility word-spacing writing-mode x xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space y], + 'tspan' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength unicode-bidi visibility word-spacing writing-mode x xml:base xml:lang xml:space y], + 'use' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y], + 'view' => %w[externalResourcesRequired id preserveAspectRatio viewBox viewTarget xml:base xml:lang xml:space zoomAndPan], + 'vkern' => %w[g1 g2 id k u1 u2 xml:base xml:lang xml:space] + }.freeze + end + end +end diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake new file mode 100644 index 00000000000..16bad4bd2bd --- /dev/null +++ b/lib/tasks/auto_annotate_models.rake @@ -0,0 +1,44 @@ +if Rails.env.development? + task :set_annotation_options do + # You can override any of these by setting an environment variable of the + # same name. + Annotate.set_defaults( + 'routes' => 'false', + 'position_in_routes' => 'before', + 'position_in_class' => 'before', + 'position_in_test' => 'before', + 'position_in_fixture' => 'before', + 'position_in_factory' => 'before', + 'position_in_serializer' => 'before', + 'show_foreign_keys' => 'true', + 'show_indexes' => 'false', + 'simple_indexes' => 'false', + 'model_dir' => 'app/models', + 'root_dir' => '', + 'include_version' => 'false', + 'require' => '', + 'exclude_tests' => 'true', + 'exclude_fixtures' => 'true', + 'exclude_factories' => 'true', + 'exclude_serializers' => 'true', + 'exclude_scaffolds' => 'true', + 'exclude_controllers' => 'true', + 'exclude_helpers' => 'true', + 'ignore_model_sub_dir' => 'false', + 'ignore_columns' => nil, + 'ignore_unknown_models' => 'false', + 'hide_limit_column_types' => 'integer,boolean', + 'skip_on_db_migrate' => 'false', + 'format_bare' => 'true', + 'format_rdoc' => 'false', + 'format_markdown' => 'false', + 'sort' => 'false', + 'force' => 'false', + 'trace' => 'false', + 'wrapper_open' => nil, + 'wrapper_close' => nil, + ) + end + + Annotate.load_tasks +end diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake index 4921c6e0bcf..1c706dc11b3 100644 --- a/lib/tasks/gitlab/db.rake +++ b/lib/tasks/gitlab/db.rake @@ -29,7 +29,10 @@ namespace :gitlab do tables.delete 'schema_migrations' # Truncate schema_migrations to ensure migrations re-run connection.execute('TRUNCATE schema_migrations') - tables.each { |t| connection.execute("DROP TABLE #{t}") } + # Drop tables with cascade to avoid dependent table errors + # PG: http://www.postgresql.org/docs/current/static/ddl-depend.html + # MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html + tables.each { |t| connection.execute("DROP TABLE #{t} CASCADE") } end end end diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb deleted file mode 100644 index d7a7ba1c5b6..00000000000 --- a/spec/controllers/admin/impersonation_controller_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe Admin::ImpersonationController do - let(:admin) { create(:admin) } - - before do - sign_in(admin) - end - - describe 'CREATE #impersonation when blocked' do - let(:blocked_user) { create(:user, state: :blocked) } - - it 'does not allow impersonation' do - post :create, id: blocked_user.username - - expect(flash[:alert]).to eq 'You cannot impersonate a blocked user' - end - end -end diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb new file mode 100644 index 00000000000..eb82476b179 --- /dev/null +++ b/spec/controllers/admin/impersonations_controller_spec.rb @@ -0,0 +1,95 @@ +require 'spec_helper' + +describe Admin::ImpersonationsController do + let(:impersonator) { create(:admin) } + let(:user) { create(:user) } + + describe "DELETE destroy" do + context "when not signed in" do + it "redirects to the sign in page" do + delete :destroy + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when not impersonating" do + it "responds with status 404" do + delete :destroy + + expect(response.status).to eq(404) + end + + it "doesn't sign us in" do + delete :destroy + + expect(warden.user).to eq(user) + end + end + + context "when impersonating" do + before do + session[:impersonator_id] = impersonator.id + end + + context "when the impersonator is not admin (anymore)" do + before do + impersonator.admin = false + impersonator.save + end + + it "responds with status 404" do + delete :destroy + + expect(response.status).to eq(404) + end + + it "doesn't sign us in as the impersonator" do + delete :destroy + + expect(warden.user).to eq(user) + end + end + + context "when the impersonator is admin" do + context "when the impersonator is blocked" do + before do + impersonator.block! + end + + it "responds with status 404" do + delete :destroy + + expect(response.status).to eq(404) + end + + it "doesn't sign us in as the impersonator" do + delete :destroy + + expect(warden.user).to eq(user) + end + end + + context "when the impersonator is not blocked" do + it "redirects to the impersonated user's page" do + delete :destroy + + expect(response).to redirect_to(admin_user_path(user)) + end + + it "signs us in as the impersonator" do + delete :destroy + + expect(warden.user).to eq(impersonator) + end + end + end + end + end + end +end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 9ef8ba1b097..ce2a62ae1fd 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -2,9 +2,10 @@ require 'spec_helper' describe Admin::UsersController do let(:user) { create(:user) } + let(:admin) { create(:admin) } before do - sign_in(create(:admin)) + sign_in(admin) end describe 'DELETE #user with projects' do @@ -112,4 +113,50 @@ describe Admin::UsersController do patch :disable_two_factor, id: user.to_param end end + + describe "POST impersonate" do + context "when the user is blocked" do + before do + user.block! + end + + it "shows a notice" do + post :impersonate, id: user.username + + expect(flash[:alert]).to eq("You cannot impersonate a blocked user") + end + + it "doesn't sign us in as the user" do + post :impersonate, id: user.username + + expect(warden.user).to eq(admin) + end + end + + context "when the user is not blocked" do + it "stores the impersonator in the session" do + post :impersonate, id: user.username + + expect(session[:impersonator_id]).to eq(admin.id) + end + + it "signs us in as the user" do + post :impersonate, id: user.username + + expect(warden.user).to eq(user) + end + + it "redirects to root" do + post :impersonate, id: user.username + + expect(response).to redirect_to(root_path) + end + + it "shows a notice" do + post :impersonate, id: user.username + + expect(flash[:alert]).to eq("You are now impersonating #{user.username}") + end + end + end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index d6e4cd71ce6..2b2ad3b9412 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -40,6 +40,45 @@ describe Projects::IssuesController do end end + describe 'PUT #update' do + context 'when moving issue to another private project' do + let(:another_project) { create(:project, :private) } + + before do + sign_in(user) + project.team << [user, :developer] + end + + context 'when user has access to move issue' do + before { another_project.team << [user, :reporter] } + + it 'moves issue to another project' do + move_issue + + expect(response).to have_http_status :found + expect(another_project.issues).to_not be_empty + end + end + + context 'when user does not have access to move issue' do + it 'responds with 404' do + move_issue + + expect(response).to have_http_status :not_found + end + end + + def move_issue + put :update, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: issue.iid, + issue: { title: 'New title' }, + move_to_project_id: another_project.id + end + end + end + describe 'Confidential Issues' do let(:project) { create(:project_empty_repo, :public) } let(:assignee) { create(:assignee) } diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb index 94dd935a039..3195fb3ddcc 100644 --- a/spec/factories/project_hooks.rb +++ b/spec/factories/project_hooks.rb @@ -1,5 +1,9 @@ FactoryGirl.define do factory :project_hook do url { FFaker::Internet.uri('http') } + + trait :token do + token { SecureRandom.hex(10) } + end end end diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb new file mode 100644 index 00000000000..24e83d44010 --- /dev/null +++ b/spec/features/dashboard/label_filter_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'Dashboard > label filter', feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } + let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) } + let(:label) { create(:label, title: 'bug', color: '#ff0000') } + let(:label2) { create(:label, title: 'bug') } + + before do + project.labels << label + project2.labels << label2 + + login_as(user) + visit issues_dashboard_path + end + + context 'duplicate labels' do + it 'should remove duplicate labels' do + page.within('.labels-filter') do + click_button 'Label' + end + + page.within('.dropdown-menu-labels') do + expect(page).to have_selector('.dropdown-content a', text: 'bug', count: 1) + end + end + end +end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index b57131f68d5..d5755c293c5 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -264,12 +264,14 @@ describe 'Issues', feature: true do visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_soon) expect(first_issue).to include('foo') + expect(last_issue).to include('baz') end it 'sorts by least recently due milestone' do visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_later) expect(first_issue).to include('bar') + expect(last_issue).to include('baz') end end diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 4433ef2d6f1..8c38dd5b122 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -37,7 +37,7 @@ feature 'Login', feature: true do end def enter_code(code) - fill_in 'Two-factor authentication code', with: code + fill_in 'Two-factor Authentication code', with: code click_button 'Verify code' end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 00b60bd0e75..e296078bad8 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -30,4 +30,14 @@ feature 'Create New Merge Request', feature: true, js: true do expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch' end + + context 'when target project cannot be viewed by the current user' do + it 'does not leak the private project name & namespace' do + private_project = create(:project, :private) + + visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id }) + + expect(page).not_to have_content private_project.to_reference + end + end end diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb new file mode 100644 index 00000000000..7e6eef65873 --- /dev/null +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +feature 'Projects > Wiki > User creates wiki page', feature: true do + let(:user) { create(:user) } + + background do + project.team << [user, :master] + login_as(user) + + visit namespace_project_path(project.namespace, project) + click_link 'Wiki' + end + + context 'in the user namespace' do + let(:project) { create(:project, namespace: user.namespace) } + + context 'when wiki is empty' do + scenario 'directly from the wiki home page' do + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Create page' + + expect(page).to have_content('Home') + expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content('My awesome wiki!') + end + end + + context 'when wiki is not empty' do + before do + WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute + end + + scenario 'via the "new wiki page" page', js: true do + click_link 'New Page' + + fill_in :new_wiki_path, with: 'foo' + click_button 'Create Page' + + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Create page' + + expect(page).to have_content('Foo') + expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content('My awesome wiki!') + end + end + end + + context 'in a group namespace' do + let(:project) { create(:project, namespace: create(:group, :public)) } + + context 'when wiki is empty' do + scenario 'directly from the wiki home page' do + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Create page' + + expect(page).to have_content('Home') + expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content('My awesome wiki!') + end + end + + context 'when wiki is not empty' do + before do + WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute + end + + scenario 'via the "new wiki page" page', js: true do + click_link 'New Page' + + fill_in :new_wiki_path, with: 'foo' + click_button 'Create Page' + + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Create page' + + expect(page).to have_content('Foo') + expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content('My awesome wiki!') + end + end + end +end diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb new file mode 100644 index 00000000000..ef82d2375dd --- /dev/null +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +feature 'Projects > Wiki > User updates wiki page', feature: true do + let(:user) { create(:user) } + + background do + project.team << [user, :master] + login_as(user) + + visit namespace_project_path(project.namespace, project) + WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute + click_link 'Wiki' + end + + context 'in the user namespace' do + let(:project) { create(:project, namespace: user.namespace) } + + scenario 'the home page' do + click_link 'Edit' + + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Save changes' + + expect(page).to have_content('Home') + expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content('My awesome wiki!') + end + end + + context 'in a group namespace' do + let(:project) { create(:project, namespace: create(:group, :public)) } + + scenario 'the home page' do + click_link 'Edit' + + fill_in :wiki_content, with: 'My awesome wiki!' + click_button 'Save changes' + + expect(page).to have_content('Home') + expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content('My awesome wiki!') + end + end +end diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index 51b754ff85c..58aabd913eb 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -7,10 +7,10 @@ feature 'Signup', feature: true do visit root_path - fill_in 'user_name', with: user.name - fill_in 'user_username', with: user.username - fill_in 'user_email', with: user.email - fill_in 'user_password_sign_up', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: user.email + fill_in 'new_user_password', with: user.password click_button "Sign up" expect(current_path).to eq users_almost_there_path @@ -25,10 +25,10 @@ feature 'Signup', feature: true do visit root_path - fill_in 'user_name', with: user.name - fill_in 'user_username', with: user.username - fill_in 'user_email', with: existing_user.email - fill_in 'user_password_sign_up', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: existing_user.email + fill_in 'new_user_password', with: user.password click_button "Sign up" expect(current_path).to eq user_registration_path @@ -42,10 +42,10 @@ feature 'Signup', feature: true do visit root_path - fill_in 'user_name', with: user.name - fill_in 'user_username', with: user.username - fill_in 'user_email', with: existing_user.email - fill_in 'user_password_sign_up', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: existing_user.email + fill_in 'new_user_password', with: user.password click_button "Sign up" expect(current_path).to eq user_registration_path diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index c1248162031..cf116040394 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -5,10 +5,10 @@ feature 'Users', feature: true do scenario 'GET /users/sign_in creates a new user account' do visit new_user_session_path - fill_in 'user_name', with: 'Name Surname' - fill_in 'user_username', with: 'Great' - fill_in 'user_email', with: 'name@mail.com' - fill_in 'user_password_sign_up', with: 'password1234' + fill_in 'new_user_name', with: 'Name Surname' + fill_in 'new_user_username', with: 'Great' + fill_in 'new_user_email', with: 'name@mail.com' + fill_in 'new_user_password', with: 'password1234' expect { click_button 'Sign up' }.to change { User.count }.by(1) end @@ -31,10 +31,10 @@ feature 'Users', feature: true do scenario 'Should show one error if email is already taken' do visit new_user_session_path - fill_in 'user_name', with: 'Another user name' - fill_in 'user_username', with: 'anotheruser' - fill_in 'user_email', with: user.email - fill_in 'user_password_sign_up', with: '12341234' + fill_in 'new_user_name', with: 'Another user name' + fill_in 'new_user_username', with: 'anotheruser' + fill_in 'new_user_email', with: user.email + fill_in 'new_user_password', with: '12341234' expect { click_button 'Sign up' }.to change { User.count }.by(0) expect(page).to have_text('Email has already been taken') expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}' diff --git a/spec/fixtures/sanitized.svg b/spec/fixtures/sanitized.svg new file mode 100644 index 00000000000..8f84b8f5e20 --- /dev/null +++ b/spec/fixtures/sanitized.svg @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 622 682"> + + <defs> + <style>.cls-1{fill:#30353e;}.cls-2{fill:#8c929d;}.cls-3{fill:#fc6d26;}.cls-4{fill:#e24329;}.cls-5{fill:#fca326;}</style> + </defs> + <title>stacked_wm</title> + <path id="bg" class="cls-1" d="M622,681H0V-1H622V681h0Z"/> + <g id="g12"> + <path id="path14" class="cls-2" d="M316.89,497.72h-19l0.06,141.74H375V621.93h-58l-0.06-124.22h0Z"/> + </g> + <g id="g24"> + <path id="path26" class="cls-2" d="M448.32,614.57a32.46,32.46,0,0,1-23.59,10c-14.5,0-20.35-7.14-20.35-16.45,0-14.07,9.74-20.77,30.52-20.77a86.46,86.46,0,0,1,13.42,1.08v26.19h0Zm-19.7-85.91a63.45,63.45,0,0,0-40.5,14.53l6.73,11.66c7.79-4.54,17.32-9.09,31-9.09,15.58,0,22.51,8,22.51,21.42v6.93a81.48,81.48,0,0,0-13.2-1.08c-33.33,0-50.22,11.69-50.22,36.14,0,21.86,13.42,32.89,33.76,32.89,13.71,0,26.84-6.28,31.38-16.45l3.46,13.85h13.42V567c0-22.94-10-38.3-38.31-38.3h0Z"/> + </g> + <g id="g28"> + <path id="path30" class="cls-2" d="M528.4,625.18c-7.14,0-13.42-.87-18.18-3V556.58c6.49-5.41,14.5-9.31,24.68-9.31,18.4,0,25.54,13,25.54,34,0,29.86-11.47,43.93-32,43.93m8-96.52a34.88,34.88,0,0,0-26.19,11.58V522l-0.06-24.24H491.54L491.6,636c9.31,3.9,22.08,6.06,35.93,6.06,35.5,0,52.6-22.72,52.6-61.89,0-30.95-15.8-51.51-43.73-51.51"/> + </g> + <g id="g32"> + <path id="path34" class="cls-2" d="M109.84,513.08c16.88,0,27.7,5.63,34.85,11.25l8.19-14.18c-11.16-9.78-26.16-15-42.17-15-40.47,0-68.83,24.67-68.83,74.44,0,52.15,30.59,72.5,65.58,72.5a111,111,0,0,0,42.21-8.22l-0.4-55.72V560.58H97.32v17.53h33.12l0.4,42.31c-4.33,2.16-11.9,3.9-22.08,3.9-28.14,0-47-17.7-47-55,0-37.87,19.48-56.26,48.05-56.26"/> + </g> + <g id="g36"> + <path id="path38" class="cls-2" d="M243.79,497.72H225.17l0.06,23.8v82.23c0,22.94,10,38.3,38.31,38.3A64.16,64.16,0,0,0,275,641V624.31a57,57,0,0,1-8.66.65c-15.58,0-22.51-8-22.51-21.42v-56.7H275V531.26H243.85l-0.06-33.54h0Z"/> + </g> + <path id="path40" class="cls-2" d="M177.94,639.46h18.61V531.26H177.94v108.2h0Z"/> + <path id="path42" class="cls-2" d="M177.94,516.33h18.61V497.72H177.94v18.61h0Z"/> + <g id="g44"> + <path id="path46" class="cls-3" d="M525.05,266.23l-24-74L453.36,45.6a8.19,8.19,0,0,0-15.58,0L390.12,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24l-24,74a16.38,16.38,0,0,0,6,18.31L311,435.71,519.1,284.54a16.38,16.38,0,0,0,6-18.31"/> + </g> + <g id="g48"> + <path id="path50" class="cls-4" d="M311,435.71h0l79.12-243.47H231.88L311,435.71h0Z"/> + </g> + <g id="g56"> + <path id="path58" class="cls-3" d="M311,435.71L231.88,192.24H121L311,435.71h0Z"/> + </g> + <g id="g64"> + <path id="path66" class="cls-5" d="M121,192.24h0l-24,74a16.37,16.37,0,0,0,6,18.31L311,435.7,121,192.24h0Z"/> + </g> + <g id="g72"> + <path id="path74" class="cls-4" d="M121,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24h0Z"/> + </g> + <g id="g76"> + <path id="path78" class="cls-3" d="M311,435.71l79.12-243.47H501L311,435.71h0Z"/> + </g> + <g id="g80"> + <path id="path82" class="cls-5" d="M501,192.24h0l24,74a16.37,16.37,0,0,1-6,18.31L311,435.7,501,192.24h0Z"/> + </g> + <g id="g84"> + <path id="path86" class="cls-4" d="M501,192.24H390.12L437.78,45.6a8.19,8.19,0,0,1,15.58,0L501,192.24h0Z"/> + </g> +</svg> diff --git a/spec/fixtures/unsanitized.svg b/spec/fixtures/unsanitized.svg new file mode 100644 index 00000000000..3957557334b --- /dev/null +++ b/spec/fixtures/unsanitized.svg @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 622 682" filterMe="test"> + <iframe src="http://www.google.com"></iframe> + <defs> + <style>.cls-1{fill:#30353e;}.cls-2{fill:#8c929d;}.cls-3{fill:#fc6d26;}.cls-4{fill:#e24329;}.cls-5{fill:#fca326;}</style> + </defs> + <title>stacked_wm</title> + <path id="bg" class="cls-1" d="M622,681H0V-1H622V681h0Z"/> + <g id="g12"> + <path id="path14" class="cls-2" d="M316.89,497.72h-19l0.06,141.74H375V621.93h-58l-0.06-124.22h0Z"/> + </g> + <g id="g24"> + <path id="path26" class="cls-2" d="M448.32,614.57a32.46,32.46,0,0,1-23.59,10c-14.5,0-20.35-7.14-20.35-16.45,0-14.07,9.74-20.77,30.52-20.77a86.46,86.46,0,0,1,13.42,1.08v26.19h0Zm-19.7-85.91a63.45,63.45,0,0,0-40.5,14.53l6.73,11.66c7.79-4.54,17.32-9.09,31-9.09,15.58,0,22.51,8,22.51,21.42v6.93a81.48,81.48,0,0,0-13.2-1.08c-33.33,0-50.22,11.69-50.22,36.14,0,21.86,13.42,32.89,33.76,32.89,13.71,0,26.84-6.28,31.38-16.45l3.46,13.85h13.42V567c0-22.94-10-38.3-38.31-38.3h0Z"/> + </g> + <g id="g28"> + <path id="path30" class="cls-2" d="M528.4,625.18c-7.14,0-13.42-.87-18.18-3V556.58c6.49-5.41,14.5-9.31,24.68-9.31,18.4,0,25.54,13,25.54,34,0,29.86-11.47,43.93-32,43.93m8-96.52a34.88,34.88,0,0,0-26.19,11.58V522l-0.06-24.24H491.54L491.6,636c9.31,3.9,22.08,6.06,35.93,6.06,35.5,0,52.6-22.72,52.6-61.89,0-30.95-15.8-51.51-43.73-51.51"/> + </g> + <g id="g32"> + <path id="path34" class="cls-2" d="M109.84,513.08c16.88,0,27.7,5.63,34.85,11.25l8.19-14.18c-11.16-9.78-26.16-15-42.17-15-40.47,0-68.83,24.67-68.83,74.44,0,52.15,30.59,72.5,65.58,72.5a111,111,0,0,0,42.21-8.22l-0.4-55.72V560.58H97.32v17.53h33.12l0.4,42.31c-4.33,2.16-11.9,3.9-22.08,3.9-28.14,0-47-17.7-47-55,0-37.87,19.48-56.26,48.05-56.26"/> + </g> + <g id="g36"> + <path id="path38" class="cls-2" d="M243.79,497.72H225.17l0.06,23.8v82.23c0,22.94,10,38.3,38.31,38.3A64.16,64.16,0,0,0,275,641V624.31a57,57,0,0,1-8.66.65c-15.58,0-22.51-8-22.51-21.42v-56.7H275V531.26H243.85l-0.06-33.54h0Z"/> + </g> + <path id="path40" class="cls-2" d="M177.94,639.46h18.61V531.26H177.94v108.2h0Z"/> + <path id="path42" class="cls-2" d="M177.94,516.33h18.61V497.72H177.94v18.61h0Z"/> + <g id="g44"> + <path id="path46" class="cls-3" d="M525.05,266.23l-24-74L453.36,45.6a8.19,8.19,0,0,0-15.58,0L390.12,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24l-24,74a16.38,16.38,0,0,0,6,18.31L311,435.71,519.1,284.54a16.38,16.38,0,0,0,6-18.31"/> + </g> + <g id="g48"> + <path id="path50" class="cls-4" d="M311,435.71h0l79.12-243.47H231.88L311,435.71h0Z"/> + </g> + <g id="g56"> + <path id="path58" class="cls-3" d="M311,435.71L231.88,192.24H121L311,435.71h0Z"/> + </g> + <g id="g64"> + <path id="path66" class="cls-5" d="M121,192.24h0l-24,74a16.37,16.37,0,0,0,6,18.31L311,435.7,121,192.24h0Z"/> + </g> + <g id="g72"> + <path id="path74" class="cls-4" d="M121,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24h0Z"/> + </g> + <g id="g76"> + <path id="path78" class="cls-3" d="M311,435.71l79.12-243.47H501L311,435.71h0Z"/> + </g> + <g id="g80"> + <path id="path82" class="cls-5" d="M501,192.24h0l24,74a16.37,16.37,0,0,1-6,18.31L311,435.7,501,192.24h0Z"/> + </g> + <g id="g84"> + <path id="path86" class="cls-4" d="M501,192.24H390.12L437.78,45.6a8.19,8.19,0,0,1,15.58,0L501,192.24h0Z"/> + </g> +</svg> diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 87849230dbe..6d1c02db297 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -67,4 +67,16 @@ describe BlobHelper do expect(result).to eq(expected) end end + + describe "#sanitize_svg" do + let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') } + let(:data) { open(input_svg_path).read } + let(:expected_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'sanitized.svg') } + let(:expected) { open(expected_svg_path).read } + + it 'should retain essential elements' do + blob = OpenStruct.new(data: data) + expect(sanitize_svg(blob).data).to eq(expected) + end + end end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 543593cf389..bffe2c18b6f 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -30,6 +30,18 @@ describe IssuesHelper do expect(url_for_project_issues).to eq "" end + it 'returns an empty string if project_url is invalid' do + expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' } + + expect(url_for_project_issues(project)).to eq '' + end + + it 'returns an empty string if project_path is invalid' do + expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' } + + expect(url_for_project_issues(project, only_path: true)).to eq '' + end + describe "when external tracker was enabled and then config removed" do before do @project = ext_project @@ -68,6 +80,18 @@ describe IssuesHelper do expect(url_for_issue(issue.iid)).to eq "" end + it 'returns an empty string if issue_url is invalid' do + expect(project).to receive_message_chain('issues_tracker.issue_url') { 'javascript:alert("foo");' } + + expect(url_for_issue(issue.iid, project)).to eq '' + end + + it 'returns an empty string if issue_path is invalid' do + expect(project).to receive_message_chain('issues_tracker.issue_path') { 'javascript:alert("foo");' } + + expect(url_for_issue(issue.iid, project, only_path: true)).to eq '' + end + describe "when external tracker was enabled and then config removed" do before do @project = ext_project @@ -105,6 +129,18 @@ describe IssuesHelper do expect(url_for_new_issue).to eq "" end + it 'returns an empty string if issue_url is invalid' do + expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' } + + expect(url_for_new_issue(project)).to eq '' + end + + it 'returns an empty string if issue_path is invalid' do + expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' } + + expect(url_for_new_issue(project, only_path: true)).to eq '' + end + describe "when external tracker was enabled and then config removed" do before do @project = ext_project diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 62389188d2c..29bcb8c5892 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -131,4 +131,13 @@ describe ProjectsHelper do end end end + + describe '#sanitized_import_error' do + it 'removes the repo path' do + repo = File.join(Gitlab.config.gitlab_shell.repos_path, '/namespace/test.git') + import_error = "Could not clone #{repo}\n" + + expect(sanitize_repo_path(import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git') + end + end end diff --git a/spec/javascripts/merge_request_widget_spec.js.coffee b/spec/javascripts/merge_request_widget_spec.js.coffee new file mode 100644 index 00000000000..c0bd8a29e43 --- /dev/null +++ b/spec/javascripts/merge_request_widget_spec.js.coffee @@ -0,0 +1,49 @@ +#= require merge_request_widget + +describe 'MergeRequestWidget', -> + + beforeEach -> + window.notifyPermissions = () -> + window.notify = () -> + @opts = { + ci_status_url:"http://sampledomain.local/ci/getstatus", + ci_status:"", + ci_message: { + normal: "Build {{status}} for \"{{title}}\"", + preparing: "{{status}} build for \"{{title}}\"" + }, + ci_title: { + preparing: "{{status}} build", + normal: "Build {{status}}" + }, + gitlab_icon:"gitlab_logo.png", + builds_path:"http://sampledomain.local/sampleBuildsPath" + } + @class = new MergeRequestWidget(@opts) + @ciStatusData = {"title":"Sample MR title","sha":"12a34bc5","status":"success","coverage":98} + + describe 'getCIStatus', -> + beforeEach -> + spyOn(jQuery, 'getJSON').and.callFake (req, cb) => + cb(@ciStatusData) + + it 'should call showCIStatus even if a notification should not be displayed', -> + spy = spyOn(@class, 'showCIStatus').and.stub() + @class.getCIStatus(false) + expect(spy).toHaveBeenCalledWith(@ciStatusData.status) + + it 'should call showCIStatus when a notification should be displayed', -> + spy = spyOn(@class, 'showCIStatus').and.stub() + @class.getCIStatus(true) + expect(spy).toHaveBeenCalledWith(@ciStatusData.status) + + it 'should call showCICoverage when the coverage rate is set', -> + spy = spyOn(@class, 'showCICoverage').and.stub() + @class.getCIStatus(false) + expect(spy).toHaveBeenCalledWith(@ciStatusData.coverage) + + it 'should not call showCICoverage when the coverage rate is not set', -> + @ciStatusData.coverage = null + spy = spyOn(@class, 'showCICoverage').and.stub() + @class.getCIStatus(false) + expect(spy).not.toHaveBeenCalled() diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb index aa0699f2ebf..af839f42421 100644 --- a/spec/lib/gitlab/bitbucket_import/client_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb @@ -34,18 +34,32 @@ describe Gitlab::BitbucketImport::Client, lib: true do it 'retrieves issues over a number of pages' do stub_request(:get, "https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=0"). - to_return(status: 200, - body: first_sample_data.to_json, - headers: {}) + to_return(status: 200, + body: first_sample_data.to_json, + headers: {}) stub_request(:get, "https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=50"). - to_return(status: 200, - body: second_sample_data.to_json, - headers: {}) + to_return(status: 200, + body: second_sample_data.to_json, + headers: {}) issues = client.issues(project_id) expect(issues.count).to eq(95) end end + + context 'project import' do + it 'calls .from_project with no errors' do + project = create(:empty_project) + project.create_or_update_import_data(credentials: + { user: "git", + password: nil, + bb_session: { bitbucket_access_token: "test", + bitbucket_access_token_secret: "test" } }) + project.import_url = "ssh://git@bitbucket.org/test/test.git" + + expect { described_class.from_project(project) }.to_not raise_error + end + end end diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb index a324a82e69f..55e86d4ceac 100644 --- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb @@ -2,23 +2,25 @@ require 'spec_helper' describe Gitlab::GithubImport::CommentFormatter, lib: true do let(:project) { create(:project) } - let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') } + let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') } let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') } - let(:base_data) do + let(:base) do { body: "I'm having a problem with this.", user: octocat, + commit_id: nil, + diff_hunk: nil, created_at: created_at, updated_at: updated_at } end - subject(:comment) { described_class.new(project, raw_data)} + subject(:comment) { described_class.new(project, raw)} describe '#attributes' do context 'when do not reference a portion of the diff' do - let(:raw_data) { OpenStruct.new(base_data) } + let(:raw) { double(base) } it 'returns formatted attributes' do expected = { @@ -36,24 +38,23 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do end context 'when on a portion of the diff' do - let(:diff_data) do + let(:diff) do { body: 'Great stuff', commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e', - diff_hunk: '@@ -16,33 +16,40 @@ public class Connection : IConnection...', - path: 'file1.txt', - position: 1 + diff_hunk: "@@ -1,5 +1,9 @@\n class User\n def name\n- 'John Doe'\n+ 'Jane Doe'", + path: 'file1.txt' } end - let(:raw_data) { OpenStruct.new(base_data.merge(diff_data)) } + let(:raw) { double(base.merge(diff)) } it 'returns formatted attributes' do expected = { project: project, note: "*Created by: octocat*\n\nGreat stuff", commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e', - line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_0_1', + line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_4_3', author_id: project.creator_id, created_at: created_at, updated_at: updated_at @@ -64,15 +65,10 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do end context 'when author is a GitLab user' do - let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) } + let(:raw) { double(base.merge(user: octocat)) } - it 'returns project#creator_id as author_id when is not a GitLab user' do - expect(comment.attributes.fetch(:author_id)).to eq project.creator_id - end - - it 'returns GitLab user id as author_id when is a GitLab user' do + it 'returns GitLab user id as author_id' do gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github') - expect(comment.attributes.fetch(:author_id)).to eq gl_user.id end end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 04bc2dcfb16..37a27d73aab 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -43,51 +43,65 @@ describe WebHook, models: true do end describe "execute" do + let(:project) { create(:project) } + let(:project_hook) { create(:project_hook) } + before(:each) do - @project_hook = create(:project_hook) - @project = create(:project) - @project.hooks << [@project_hook] + project.hooks << [project_hook] @data = { before: 'oldrev', after: 'newrev', ref: 'ref' } - WebMock.stub_request(:post, @project_hook.url) + WebMock.stub_request(:post, project_hook.url) + end + + context 'when token is defined' do + let(:project_hook) { create(:project_hook, :token) } + + it 'POSTs to the webhook URL' do + project_hook.execute(@data, 'push_hooks') + expect(WebMock).to have_requested(:post, project_hook.url).with( + headers: { 'Content-Type' => 'application/json', + 'X-Gitlab-Event' => 'Push Hook', + 'X-Gitlab-Token' => project_hook.token } + ).once + end end it "POSTs to the webhook URL" do - @project_hook.execute(@data, 'push_hooks') - expect(WebMock).to have_requested(:post, @project_hook.url).with( - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' } + project_hook.execute(@data, 'push_hooks') + expect(WebMock).to have_requested(:post, project_hook.url).with( + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' } ).once end it "POSTs the data as JSON" do - @project_hook.execute(@data, 'push_hooks') - expect(WebMock).to have_requested(:post, @project_hook.url).with( - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' } + project_hook.execute(@data, 'push_hooks') + expect(WebMock).to have_requested(:post, project_hook.url).with( + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' } ).once end it "catches exceptions" do expect(WebHook).to receive(:post).and_raise("Some HTTP Post error") - expect { @project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError) + expect { project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError) end it "handles SSL exceptions" do expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error')) - expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error']) + expect(project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error']) end it "handles 200 status code" do - WebMock.stub_request(:post, @project_hook.url).to_return(status: 200, body: "Success") + WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success") - expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) + expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) end it "handles 2xx status codes" do - WebMock.stub_request(:post, @project_hook.url).to_return(status: 201, body: "Success") + WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success") - expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) + expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) end end end diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index 31b2c90122d..e771f35811e 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -27,86 +27,51 @@ describe BambooService, models: true do end describe 'Validations' do - describe '#bamboo_url' do - it 'does not validate the presence of bamboo_url if service is not active' do - bamboo_service = service - bamboo_service.active = false - - expect(bamboo_service).not_to validate_presence_of(:bamboo_url) - end - - it 'validates the presence of bamboo_url if service is active' do - bamboo_service = service - bamboo_service.active = true - - expect(bamboo_service).to validate_presence_of(:bamboo_url) - end - end + subject { service } - describe '#build_key' do - it 'does not validate the presence of build_key if service is not active' do - bamboo_service = service - bamboo_service.active = false + context 'when service is active' do + before { subject.active = true } - expect(bamboo_service).not_to validate_presence_of(:build_key) - end + it { is_expected.to validate_presence_of(:build_key) } + it { is_expected.to validate_presence_of(:bamboo_url) } + it_behaves_like 'issue tracker service URL attribute', :bamboo_url - it 'validates the presence of build_key if service is active' do - bamboo_service = service - bamboo_service.active = true + describe '#username' do + it 'does not validate the presence of username if password is nil' do + subject.password = nil - expect(bamboo_service).to validate_presence_of(:build_key) - end - end + expect(subject).not_to validate_presence_of(:username) + end - describe '#username' do - it 'does not validate the presence of username if service is not active' do - bamboo_service = service - bamboo_service.active = false + it 'validates the presence of username if password is present' do + subject.password = 'secret' - expect(bamboo_service).not_to validate_presence_of(:username) + expect(subject).to validate_presence_of(:username) + end end - it 'does not validate the presence of username if username is nil' do - bamboo_service = service - bamboo_service.active = true - bamboo_service.password = nil + describe '#password' do + it 'does not validate the presence of password if username is nil' do + subject.username = nil - expect(bamboo_service).not_to validate_presence_of(:username) - end + expect(subject).not_to validate_presence_of(:password) + end - it 'validates the presence of username if service is active and username is present' do - bamboo_service = service - bamboo_service.active = true - bamboo_service.password = 'secret' + it 'validates the presence of password if username is present' do + subject.username = 'john' - expect(bamboo_service).to validate_presence_of(:username) + expect(subject).to validate_presence_of(:password) + end end end - describe '#password' do - it 'does not validate the presence of password if service is not active' do - bamboo_service = service - bamboo_service.active = false - - expect(bamboo_service).not_to validate_presence_of(:password) - end - - it 'does not validate the presence of password if username is nil' do - bamboo_service = service - bamboo_service.active = true - bamboo_service.username = nil - - expect(bamboo_service).not_to validate_presence_of(:password) - end - - it 'validates the presence of password if service is active and username is present' do - bamboo_service = service - bamboo_service.active = true - bamboo_service.username = 'john' + context 'when service is inactive' do + before { subject.active = false } - expect(bamboo_service).to validate_presence_of(:password) - end + it { is_expected.not_to validate_presence_of(:build_key) } + it { is_expected.not_to validate_presence_of(:bamboo_url) } + it { is_expected.not_to validate_presence_of(:username) } + it { is_expected.not_to validate_presence_of(:password) } end end diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 88cd624877a..60364df2015 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -26,6 +26,23 @@ describe BuildkiteService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:token) } + it_behaves_like 'issue tracker service URL attribute', :project_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:token) } + end + end + describe 'commits methods' do before do @project = Project.new diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb index 7c23c2efccd..236df8f047d 100644 --- a/spec/models/project_services/builds_email_service_spec.rb +++ b/spec/models/project_services/builds_email_service_spec.rb @@ -1,76 +1,71 @@ require 'spec_helper' describe BuildsEmailService do - let(:build) { create(:ci_build) } - let(:data) { Gitlab::BuildDataBuilder.build(build) } - let!(:project) { create(:project, :public, ci_id: 1) } - let(:service) { described_class.new(project: project, active: true) } + let(:data) { Gitlab::BuildDataBuilder.build(create(:ci_build)) } + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:recipients) } + + context 'when pusher is added' do + before { subject.add_pusher = true } + + it { is_expected.not_to validate_presence_of(:recipients) } + end + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:recipients) } + end + end describe '#execute' do it 'sends email' do - service.recipients = 'test@gitlab.com' + subject.recipients = 'test@gitlab.com' data[:build_status] = 'failed' + expect(BuildEmailWorker).to receive(:perform_async) - service.execute(data) + + subject.execute(data) end it 'does not send email with succeeded build and notify_only_broken_builds on' do - expect(service).to receive(:notify_only_broken_builds).and_return(true) + expect(subject).to receive(:notify_only_broken_builds).and_return(true) data[:build_status] = 'success' + expect(BuildEmailWorker).not_to receive(:perform_async) - service.execute(data) + + subject.execute(data) end it 'does not send email with failed build and build_allow_failure is true' do data[:build_status] = 'failed' data[:build_allow_failure] = true + expect(BuildEmailWorker).not_to receive(:perform_async) - service.execute(data) + + subject.execute(data) end it 'does not send email with unknown build status' do data[:build_status] = 'foo' - expect(BuildEmailWorker).not_to receive(:perform_async) - service.execute(data) - end - it 'does not send email when recipients list is empty' do - service.recipients = ' ,, ' - data[:build_status] = 'failed' expect(BuildEmailWorker).not_to receive(:perform_async) - service.execute(data) - end - end - - describe 'validations' do - - context 'when pusher is not added' do - before { service.add_pusher = false } - - it 'does not allow empty recipient input' do - service.recipients = '' - expect(service.valid?).to be false - end - - it 'does allow non-empty recipient input' do - service.recipients = 'test@example.com' - expect(service.valid?).to be true - end + subject.execute(data) end - context 'when pusher is added' do - before { service.add_pusher = true } + it 'does not send email when recipients list is empty' do + subject.recipients = ' ,, ' + data[:build_status] = 'failed' - it 'does allow empty recipient input' do - service.recipients = '' - expect(service.valid?).to be true - end + expect(BuildEmailWorker).not_to receive(:perform_async) - it 'does allow non-empty recipient input' do - service.recipients = 'test@example.com' - expect(service.valid?).to be true - end + subject.execute(data) end end end diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb new file mode 100644 index 00000000000..3e6da42803b --- /dev/null +++ b/spec/models/project_services/campfire_service_spec.rb @@ -0,0 +1,42 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require 'spec_helper' + +describe CampfireService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + end + end +end diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb new file mode 100644 index 00000000000..ff976f8ec59 --- /dev/null +++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb @@ -0,0 +1,49 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require 'spec_helper' + +describe CustomIssueTrackerService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it { is_expected.to validate_presence_of(:new_issue_url) } + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + it { is_expected.not_to validate_presence_of(:new_issue_url) } + end + end +end diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index a2cf68a9e38..3a8e67438fc 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -28,25 +28,18 @@ describe DroneCiService, models: true do describe 'validations' do context 'active' do - before { allow(subject).to receive(:activated?).and_return(true) } + before { subject.active = true } it { is_expected.to validate_presence_of(:token) } it { is_expected.to validate_presence_of(:drone_url) } - it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) } - it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) } - it { is_expected.not_to allow_value('this is not url').for(:drone_url) } - it { is_expected.not_to allow_value('http//noturl').for(:drone_url) } - it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) } + it_behaves_like 'issue tracker service URL attribute', :drone_url end context 'inactive' do - before { allow(subject).to receive(:activated?).and_return(false) } + before { subject.active = false } it { is_expected.not_to validate_presence_of(:token) } it { is_expected.not_to validate_presence_of(:drone_url) } - it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) } - it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) } - it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) } end end diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb new file mode 100644 index 00000000000..e6f78898c82 --- /dev/null +++ b/spec/models/project_services/emails_on_push_service_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe EmailsOnPushService do + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:recipients) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:recipients) } + end + end +end diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index d37978720bf..5fe5ea7d2df 100644 --- a/spec/models/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -28,13 +28,18 @@ describe ExternalWikiService, models: true do it { should have_one :service_hook } end - describe "Validations" do - context "active" do - before do - subject.active = true - end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:external_wiki_url) } + it_behaves_like 'issue tracker service URL attribute', :external_wiki_url + end + + context 'when service is inactive' do + before { subject.active = false } - it { should validate_presence_of :external_wiki_url } + it { is_expected.not_to validate_presence_of(:external_wiki_url) } end end diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index ff7fbcaa004..b7e627e6518 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -26,6 +26,20 @@ describe FlowdockService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index ecb3ccb1673..a08f1ac229f 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -26,6 +26,22 @@ describe GemnasiumService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + it { is_expected.to validate_presence_of(:api_key) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + it { is_expected.not_to validate_presence_of(:api_key) } + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 3518dbd1728..7a1f106d6e3 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -26,6 +26,20 @@ describe GitlabIssueTrackerService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + subject { described_class.new(project: create(:project), active: true) } + + it { is_expected.to validate_presence_of(:issues_url) } + it_behaves_like 'issue tracker service URL attribute', :issues_url + end + + context 'when service is inactive' do + subject { described_class.new(project: create(:project), active: false) } + + it { is_expected.not_to validate_presence_of(:issues_url) } + end + end describe 'project and issue urls' do let(:project) { create(:project) } diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index d878162a220..6fb5cad5011 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -26,6 +26,20 @@ describe HipchatService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + end + end + describe "Execute" do let(:hipchat) { HipchatService.new } let(:user) { create(:user, username: 'username') } diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index b783b1a576e..4ee022a5171 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -29,14 +29,16 @@ describe IrkerService, models: true do end describe 'Validations' do - before do - subject.active = true - subject.properties['recipients'] = _recipients + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:recipients) } end - context 'active' do - let(:_recipients) { nil } - it { should validate_presence_of :recipients } + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:recipients) } end end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 2f8193170ae..5309cfb99ff 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -26,6 +26,30 @@ describe JiraService, models: true do it { is_expected.to have_one :service_hook } end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:api_url) } + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it { is_expected.to validate_presence_of(:new_issue_url) } + it_behaves_like 'issue tracker service URL attribute', :api_url + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:api_url) } + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + it { is_expected.not_to validate_presence_of(:new_issue_url) } + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project) } @@ -72,7 +96,7 @@ describe JiraService, models: true do context "when a password was previously set" do before do - @jira_service = JiraService.create( + @jira_service = JiraService.create!( project: create(:project), properties: { api_url: 'http://jira.example.com/rest/api/2', diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb new file mode 100644 index 00000000000..f37edd4d970 --- /dev/null +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -0,0 +1,42 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require 'spec_helper' + +describe PivotaltrackerService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:token) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:token) } + end + end +end diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index 96039f9491b..555d9757b47 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -27,14 +27,20 @@ describe PushoverService, models: true do end describe 'Validations' do - context 'active' do - before do - subject.active = true - end + context 'when service is active' do + before { subject.active = true } - it { is_expected.to validate_presence_of :api_key } - it { is_expected.to validate_presence_of :user_key } - it { is_expected.to validate_presence_of :priority } + it { is_expected.to validate_presence_of(:api_key) } + it { is_expected.to validate_presence_of(:user_key) } + it { is_expected.to validate_presence_of(:priority) } + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:api_key) } + it { is_expected.not_to validate_presence_of(:user_key) } + it { is_expected.not_to validate_presence_of(:priority) } end end diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb new file mode 100644 index 00000000000..7d14f6e8280 --- /dev/null +++ b/spec/models/project_services/redmine_service_spec.rb @@ -0,0 +1,49 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require 'spec_helper' + +describe RedmineService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it { is_expected.to validate_presence_of(:new_issue_url) } + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + it { is_expected.not_to validate_presence_of(:new_issue_url) } + end + end +end diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 478d59be08b..a97b7560137 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -26,13 +26,18 @@ describe SlackService, models: true do it { is_expected.to have_one :service_hook } end - describe "Validations" do - context "active" do - before do - subject.active = true - end + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } - it { is_expected.to validate_presence_of :webhook } + it { is_expected.to validate_presence_of(:webhook) } + it_behaves_like 'issue tracker service URL attribute', :webhook + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:webhook) } end end diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index bc7423cee69..ad24b895170 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -27,86 +27,51 @@ describe TeamcityService, models: true do end describe 'Validations' do - describe '#teamcity_url' do - it 'does not validate the presence of teamcity_url if service is not active' do - teamcity_service = service - teamcity_service.active = false - - expect(teamcity_service).not_to validate_presence_of(:teamcity_url) - end - - it 'validates the presence of teamcity_url if service is active' do - teamcity_service = service - teamcity_service.active = true - - expect(teamcity_service).to validate_presence_of(:teamcity_url) - end - end + subject { service } - describe '#build_type' do - it 'does not validate the presence of build_type if service is not active' do - teamcity_service = service - teamcity_service.active = false + context 'when service is active' do + before { subject.active = true } - expect(teamcity_service).not_to validate_presence_of(:build_type) - end + it { is_expected.to validate_presence_of(:build_type) } + it { is_expected.to validate_presence_of(:teamcity_url) } + it_behaves_like 'issue tracker service URL attribute', :teamcity_url - it 'validates the presence of build_type if service is active' do - teamcity_service = service - teamcity_service.active = true + describe '#username' do + it 'does not validate the presence of username if password is nil' do + subject.password = nil - expect(teamcity_service).to validate_presence_of(:build_type) - end - end + expect(subject).not_to validate_presence_of(:username) + end - describe '#username' do - it 'does not validate the presence of username if service is not active' do - teamcity_service = service - teamcity_service.active = false + it 'validates the presence of username if password is present' do + subject.password = 'secret' - expect(teamcity_service).not_to validate_presence_of(:username) + expect(subject).to validate_presence_of(:username) + end end - it 'does not validate the presence of username if username is nil' do - teamcity_service = service - teamcity_service.active = true - teamcity_service.password = nil + describe '#password' do + it 'does not validate the presence of password if username is nil' do + subject.username = nil - expect(teamcity_service).not_to validate_presence_of(:username) - end + expect(subject).not_to validate_presence_of(:password) + end - it 'validates the presence of username if service is active and username is present' do - teamcity_service = service - teamcity_service.active = true - teamcity_service.password = 'secret' + it 'validates the presence of password if username is present' do + subject.username = 'john' - expect(teamcity_service).to validate_presence_of(:username) + expect(subject).to validate_presence_of(:password) + end end end - describe '#password' do - it 'does not validate the presence of password if service is not active' do - teamcity_service = service - teamcity_service.active = false - - expect(teamcity_service).not_to validate_presence_of(:password) - end - - it 'does not validate the presence of password if username is nil' do - teamcity_service = service - teamcity_service.active = true - teamcity_service.username = nil - - expect(teamcity_service).not_to validate_presence_of(:password) - end - - it 'validates the presence of password if service is active and username is present' do - teamcity_service = service - teamcity_service.active = true - teamcity_service.username = 'john' + context 'when service is inactive' do + before { subject.active = false } - expect(teamcity_service).to validate_presence_of(:password) - end + it { is_expected.not_to validate_presence_of(:build_type) } + it { is_expected.not_to validate_presence_of(:teamcity_url) } + it { is_expected.not_to validate_presence_of(:username) } + it { is_expected.not_to validate_presence_of(:password) } end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e33c7d62ff4..5b1cf71337e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -798,4 +798,18 @@ describe Project, models: true do end end end + + describe '#protected_branch?' do + let(:project) { create(:empty_project) } + + it 'returns true when a branch is a protected branch' do + project.protected_branches.create!(name: 'foo') + + expect(project.protected_branch?('foo')).to eq(true) + end + + it 'returns false when a branch is not a protected branch' do + expect(project.protected_branch?('foo')).to eq(false) + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 397bb5a8028..34a13f9b5c9 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -561,7 +561,7 @@ describe Repository, models: true do end describe :skip_merged_commit do - subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } } + subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } } it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') } end @@ -858,13 +858,30 @@ describe Repository, models: true do end describe '#add_tag' do - it 'adds a tag' do - expect(repository).to receive(:before_push_tag) + context 'with a valid target' do + let(:user) { build_stubbed(:user) } - expect_any_instance_of(Gitlab::Shell).to receive(:add_tag). - with(repository.path_with_namespace, '8.5', 'master', 'foo') + it 'creates the tag using rugged' do + expect(repository.rugged.tags).to receive(:create). + with('8.5', repository.commit('master').id, + hash_including(message: 'foo', + tagger: hash_including(name: user.name, email: user.email))). + and_call_original - repository.add_tag('8.5', 'master', 'foo') + repository.add_tag(user, '8.5', 'master', 'foo') + end + + it 'returns a Gitlab::Git::Tag object' do + tag = repository.add_tag(user, '8.5', 'master', 'foo') + + expect(tag).to be_a(Gitlab::Git::Tag) + end + end + + context 'with an invalid target' do + it 'returns false' do + expect(repository.add_tag(user, '8.5', 'bar', 'foo')).to be false + end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index e28998d51b5..cb82ca7802d 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -32,6 +32,41 @@ describe API::API, api: true do expect(response.status).to eq(401) end end + + context "since optional parameter" do + it "should return project commits since provided parameter" do + commits = project.repository.commits("master") + since = commits.second.created_at + + get api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user) + + expect(json_response.size).to eq 2 + expect(json_response.first["id"]).to eq(commits.first.id) + expect(json_response.second["id"]).to eq(commits.second.id) + end + end + + context "until optional parameter" do + it "should return project commits until provided parameter" do + commits = project.repository.commits("master") + before = commits.second.created_at + + get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user) + + expect(json_response.size).to eq(commits.size - 1) + expect(json_response.first["id"]).to eq(commits.second.id) + expect(json_response.second["id"]).to eq(commits.third.id) + end + end + + context "invalid xmlschema date parameters" do + it "should return an invalid parameter error message" do + get api("/projects/#{project.id}/repository/commits?since=invalid-date", user) + + expect(response.status).to eq(400) + expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format" + end + end end describe "GET /projects:id/repository/commits/:sha" do diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 344f0fe0b7f..241995041bb 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -127,7 +127,7 @@ describe API::API, api: true do describe 'GET /projects/:id/milestones/:milestone_id/issues' do before do - milestone.issues << create(:issue) + milestone.issues << create(:issue, project: project) end it 'should return project issues for a particular milestone' do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) @@ -140,5 +140,34 @@ describe API::API, api: true do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues") expect(response.status).to eq(401) end + + describe 'confidential issues' do + let(:public_project) { create(:project, :public) } + let(:milestone) { create(:milestone, project: public_project) } + let(:issue) { create(:issue, project: public_project) } + let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } + before do + public_project.team << [user, :developer] + milestone.issues << issue << confidential_issue + end + + it 'returns confidential issues to team members' do + get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(2) + expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) + end + + it 'does not return confidential issues to regular users' do + get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + end + end end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index ec9eda0a2ed..49091fc0f49 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } - let!(:project) { create(:project, namespace: user.namespace ) } + let!(:project) { create(:project, namespace: user.namespace) } let!(:issue) { create(:issue, project: project, author: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } let!(:snippet) { create(:project_snippet, project: project, author: user) } @@ -45,7 +45,7 @@ describe API::API, api: true do end it "should return a 404 error when issue id not found" do - get api("/projects/#{project.id}/issues/123/notes", user) + get api("/projects/#{project.id}/issues/12345/notes", user) expect(response.status).to eq(404) end @@ -106,7 +106,7 @@ describe API::API, api: true do end it "should return a 404 error if issue note not found" do - get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user) + get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) expect(response.status).to eq(404) end @@ -134,7 +134,7 @@ describe API::API, api: true do end it "should return a 404 error if snippet note not found" do - get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user) + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user) expect(response.status).to eq(404) end end @@ -191,6 +191,27 @@ describe API::API, api: true do expect(response.status).to eq(401) end end + + context 'when user does not have access to create noteable' do + let(:private_issue) { create(:issue, project: create(:project, :private)) } + + ## + # We are posting to project user has access to, but we use issue id + # from a different project, see #15577 + # + before do + post api("/projects/#{project.id}/issues/#{private_issue.id}/notes", user), + body: 'Hi!' + end + + it 'responds with 500' do + expect(response.status).to eq 500 + end + + it 'does not create new note' do + expect(private_issue.notes.reload).to be_empty + end + end end describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do @@ -211,7 +232,7 @@ describe API::API, api: true do end it 'should return a 404 error when note id not found' do - put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user), + put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user), body: 'Hello!' expect(response.status).to eq(404) end @@ -233,7 +254,7 @@ describe API::API, api: true do it 'should return a 404 error when note id not found' do put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/123", user), body: "Hello!" + "notes/12345", user), body: "Hello!" expect(response.status).to eq(404) end end @@ -248,7 +269,7 @@ describe API::API, api: true do it 'should return a 404 error when note id not found' do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ - "notes/123", user), body: "Hello!" + "notes/12345", user), body: "Hello!" expect(response.status).to eq(404) end end @@ -268,7 +289,7 @@ describe API::API, api: true do end it 'returns a 404 error when note id not found' do - delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user) + delete api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) expect(response.status).to eq(404) end @@ -288,7 +309,7 @@ describe API::API, api: true do it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/123", user) + "notes/12345", user) expect(response.status).to eq(404) end @@ -308,7 +329,7 @@ describe API::API, api: true do it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/merge_requests/"\ - "#{merge_request.id}/notes/123", user) + "#{merge_request.id}/notes/12345", user) expect(response.status).to eq(404) end diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 3722ddf5a33..9706d060cfa 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -15,4 +15,91 @@ describe API::API, api: true do expect(json_response['expires_at']).to be_nil end end + + describe 'GET /projects/:project_id/snippets/' do + it 'all snippets available to team member' do + project = create(:project, :public) + user = create(:user) + project.team << [user, :developer] + public_snippet = create(:project_snippet, :public, project: project) + internal_snippet = create(:project_snippet, :internal, project: project) + private_snippet = create(:project_snippet, :private, project: project) + + get api("/projects/#{project.id}/snippets/", user) + + expect(response.status).to eq(200) + expect(json_response.size).to eq(3) + expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) + end + + it 'hides private snippets from regular user' do + project = create(:project, :public) + user = create(:user) + create(:project_snippet, :private, project: project) + + get api("/projects/#{project.id}/snippets/", user) + expect(response.status).to eq(200) + expect(json_response.size).to eq(0) + end + end + + describe 'POST /projects/:project_id/snippets/' do + it 'creates a new snippet' do + admin = create(:admin) + project = create(:project) + params = { + title: 'Test Title', + file_name: 'test.rb', + code: 'puts "hello world"', + visibility_level: Gitlab::VisibilityLevel::PUBLIC + } + + post api("/projects/#{project.id}/snippets/", admin), params + + expect(response.status).to eq(201) + snippet = ProjectSnippet.find(json_response['id']) + expect(snippet.content).to eq(params[:code]) + expect(snippet.title).to eq(params[:title]) + expect(snippet.file_name).to eq(params[:file_name]) + expect(snippet.visibility_level).to eq(params[:visibility_level]) + end + end + + describe 'PUT /projects/:project_id/snippets/:id/' do + it 'updates snippet' do + admin = create(:admin) + snippet = create(:project_snippet, author: admin) + new_content = 'New content' + + put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content + + expect(response.status).to eq(200) + snippet.reload + expect(snippet.content).to eq(new_content) + end + end + + describe 'DELETE /projects/:project_id/snippets/:id/' do + it 'deletes snippet' do + admin = create(:admin) + snippet = create(:project_snippet, author: admin) + + delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) + + expect(response.status).to eq(200) + end + end + + describe 'GET /projects/:project_id/snippets/:id/raw' do + it 'returns raw text' do + admin = create(:admin) + snippet = create(:project_snippet, author: admin) + + get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin) + + expect(response.status).to eq(200) + expect(response.content_type).to eq 'text/plain' + expect(response.body).to eq(snippet.content) + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index fccd08bd6da..66193eac051 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -11,7 +11,7 @@ describe API::API, api: true do let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) } let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) } - let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') } + let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, :master, user: user, project: project) } let(:project_member2) { create(:project_member, :developer, user: user3, project: project) } let(:user4) { create(:user) } diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index edcb2bedbf7..12e170b232f 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -147,7 +147,7 @@ describe API::API, api: true do tag_name: 'v8.0.0', ref: 'master' expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag already exists') + expect(json_response['message']).to eq('Tag v8.0.0 already exists') end it 'should return 400 if ref name is invalid' do @@ -155,7 +155,7 @@ describe API::API, api: true do tag_name: 'mytag', ref: 'foo' expect(response.status).to eq(400) - expect(json_response['message']).to eq('Invalid reference name') + expect(json_response['message']).to eq('Target foo is invalid') end end diff --git a/spec/services/create_tag_service_spec.rb b/spec/services/create_tag_service_spec.rb new file mode 100644 index 00000000000..91f9e663b66 --- /dev/null +++ b/spec/services/create_tag_service_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe CreateTagService, services: true do + let(:project) { create(:project) } + let(:repository) { project.repository } + let(:user) { create(:user) } + let(:service) { described_class.new(project, user) } + + describe '#execute' do + it 'creates the tag and returns success' do + response = service.execute('v42.42.42', 'master', 'Foo') + + expect(response[:status]).to eq(:success) + expect(response[:tag]).to be_a Gitlab::Git::Tag + expect(response[:tag].name).to eq('v42.42.42') + end + + context 'when target is invalid' do + it 'returns an error' do + response = service.execute('v1.1.0', 'foo', 'Foo') + + expect(response).to eq(status: :error, + message: 'Target foo is invalid') + end + end + + context 'when tag already exists' do + it 'returns an error' do + expect(repository).to receive(:add_tag). + with(user, 'v1.1.0', 'master', 'Foo'). + and_raise(Rugged::TagError) + + response = service.execute('v1.1.0', 'master', 'Foo') + + expect(response).to eq(status: :error, + message: 'Tag v1.1.0 already exists') + end + end + + context 'when pre-receive hook fails' do + it 'returns an error' do + expect(repository).to receive(:add_tag). + with(user, 'v1.1.0', 'master', 'Foo'). + and_raise(GitHooksService::PreReceiveError) + + response = service.execute('v1.1.0', 'master', 'Foo') + + expect(response).to eq(status: :error, + message: 'Tag creation was rejected by Git hook') + end + end + end +end diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 2a5e4ac3ec4..c15e26189a5 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -7,10 +7,11 @@ describe Issues::MoveService, services: true do let(:description) { 'Some issue description' } let(:old_project) { create(:project) } let(:new_project) { create(:project) } + let(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') } let(:old_issue) do create(:issue, title: title, description: description, - project: old_project, author: author) + project: old_project, author: author, milestone: milestone1) end let(:move_service) do @@ -21,11 +22,24 @@ describe Issues::MoveService, services: true do before do old_project.team << [user, :reporter] new_project.team << [user, :reporter] + + ['label1', 'label2'].each do |label| + old_issue.labels << create(:label, + project_id: old_project.id, + title: label) + end + + new_project.labels << create(:label, title: 'label1') + new_project.labels << create(:label, title: 'label2') end end describe '#execute' do shared_context 'issue move executed' do + let!(:milestone2) do + create(:milestone, project_id: new_project.id, title: 'v9.0') + end + let!(:new_issue) { move_service.execute(old_issue, new_project) } end @@ -39,6 +53,23 @@ describe Issues::MoveService, services: true do expect(new_issue.project).to eq new_project end + it 'assigns milestone to new issue' do + expect(new_issue.reload.milestone.title).to eq 'v9.0' + expect(new_issue.reload.milestone).to eq(milestone2) + end + + it 'assign labels to new issue' do + expected_label_titles = new_issue.reload.labels.map(&:title) + expect(expected_label_titles).to include 'label1' + expect(expected_label_titles).to include 'label2' + expect(expected_label_titles.size).to eq 2 + + new_issue.labels.each do |label| + expect(new_project.labels).to include(label) + expect(old_project.labels).not_to include(label) + end + end + it 'rewrites issue title' do expect(new_issue.title).to eq title end @@ -72,11 +103,6 @@ describe Issues::MoveService, services: true do expect(new_issue.author).to eq author end - it 'removes data that is invalid in new context' do - expect(new_issue.milestone).to be_nil - expect(new_issue.labels).to be_empty - end - it 'creates a new internal id for issue' do expect(new_issue.iid).to be 1 end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb new file mode 100644 index 00000000000..782d74ec5ec --- /dev/null +++ b/spec/services/merge_requests/build_service_spec.rb @@ -0,0 +1,181 @@ +require 'spec_helper' + +describe MergeRequests::BuildService, services: true do + include RepoHelpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:issue_confidential) { false } + let(:issue) { create(:issue, project: project, title: 'A bug', confidential: issue_confidential) } + let(:description) { nil } + let(:source_branch) { 'feature-branch' } + let(:target_branch) { 'master' } + let(:merge_request) { service.execute } + let(:compare) { double(:compare, commits: commits) } + let(:commit_1) { double(:commit_1, safe_message: "Initial commit\n\nCreate the app") } + let(:commit_2) { double(:commit_2, safe_message: 'This is a bad commit message!') } + let(:commits) { nil } + + let(:service) do + MergeRequests::BuildService.new(project, user, + description: description, + source_branch: source_branch, + target_branch: target_branch) + end + + before do + allow(CompareService).to receive_message_chain(:new, :execute).and_return(compare) + end + + describe 'execute' do + context 'missing source branch' do + let(:source_branch) { '' } + + it 'forbids the merge request from being created' do + expect(merge_request.can_be_created).to eq(false) + end + + it 'adds an error message to the merge request' do + expect(merge_request.errors).to contain_exactly('You must select source and target branch') + end + end + + context 'missing target branch' do + let(:target_branch) { '' } + + it 'forbids the merge request from being created' do + expect(merge_request.can_be_created).to eq(false) + end + + it 'adds an error message to the merge request' do + expect(merge_request.errors).to contain_exactly('You must select source and target branch') + end + end + + context 'no commits in the diff' do + let(:commits) { [] } + + it 'forbids the merge request from being created' do + expect(merge_request.can_be_created).to eq(false) + end + end + + context 'one commit in the diff' do + let(:commits) { [commit_1] } + + it 'allows the merge request to be created' do + expect(merge_request.can_be_created).to eq(true) + end + + it 'uses the title of the commit as the title of the merge request' do + expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first) + end + + it 'uses the description of the commit as the description of the merge request' do + expect(merge_request.description).to eq(commit_1.safe_message.split(/\n+/, 2).last) + end + + context 'merge request already has a description set' do + let(:description) { 'Merge request description' } + + it 'keeps the description from the initial params' do + expect(merge_request.description).to eq(description) + end + end + + context 'commit has no description' do + let(:commits) { [commit_2] } + + it 'uses the title of the commit as the title of the merge request' do + expect(merge_request.title).to eq(commit_2.safe_message) + end + + it 'sets the description to nil' do + expect(merge_request.description).to be_nil + end + end + + context 'branch starts with issue IID followed by a hyphen' do + let(:source_branch) { "#{issue.iid}-fix-issue" } + + it 'appends "Closes #$issue-iid" to the description' do + expect(merge_request.description).to eq("#{commit_1.safe_message.split(/\n+/, 2).last}\nCloses ##{issue.iid}") + end + + context 'merge request already has a description set' do + let(:description) { 'Merge request description' } + + it 'appends "Closes #$issue-iid" to the description' do + expect(merge_request.description).to eq("#{description}\nCloses ##{issue.iid}") + end + end + + context 'commit has no description' do + let(:commits) { [commit_2] } + + it 'sets the description to "Closes #$issue-iid"' do + expect(merge_request.description).to eq("Closes ##{issue.iid}") + end + end + end + end + + context 'more than one commit in the diff' do + let(:commits) { [commit_1, commit_2] } + + it 'allows the merge request to be created' do + expect(merge_request.can_be_created).to eq(true) + end + + it 'uses the title of the branch as the merge request title' do + expect(merge_request.title).to eq('Feature branch') + end + + it 'does not add a description' do + expect(merge_request.description).to be_nil + end + + context 'merge request already has a description set' do + let(:description) { 'Merge request description' } + + it 'keeps the description from the initial params' do + expect(merge_request.description).to eq(description) + end + end + + context 'branch starts with GitLab issue IID followed by a hyphen' do + let(:source_branch) { "#{issue.iid}-fix-issue" } + + it 'sets the title to: Resolves "$issue-title"' do + expect(merge_request.title).to eq("Resolve \"#{issue.title}\"") + end + + context 'issue does not exist' do + let(:source_branch) { "#{issue.iid.succ}-fix-issue" } + + it 'uses the title of the branch as the merge request title' do + expect(merge_request.title).to eq("#{issue.iid.succ} fix issue") + end + end + + context 'issue is confidential' do + let(:issue_confidential) { true } + + it 'uses the title of the branch as the merge request title' do + expect(merge_request.title).to eq("#{issue.iid} fix issue") + end + end + end + + context 'branch starts with external issue IID followed by a hyphen' do + let(:source_branch) { '12345-fix-issue' } + + before { allow(project).to receive(:default_issues_tracker?).and_return(false) } + + it 'sets the title to: Resolves External Issue $issue-iid' do + expect(merge_request.title).to eq('Resolve External Issue 12345') + end + end + end + end +end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index d7c72dc0811..4bbc4ddc3ab 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -10,7 +10,7 @@ describe NotificationService, services: true do end describe 'Keys' do - describe :new_key do + describe '#new_key' do let!(:key) { create(:personal_key) } it { expect(notification.new_key(key)).to be_truthy } @@ -22,7 +22,7 @@ describe NotificationService, services: true do end describe 'Email' do - describe :new_email do + describe '#new_email' do let!(:email) { create(:email) } it { expect(notification.new_email(email)).to be_truthy } @@ -147,8 +147,8 @@ describe NotificationService, services: true do ActionMailer::Base.deliveries.clear end - describe :new_note do - it do + describe '#new_note' do + it 'notifies the team members' do notification.new_note(note) # Notify all team members @@ -177,6 +177,39 @@ describe NotificationService, services: true do end end + context 'project snippet note' do + let(:project) { create(:empty_project, :public) } + let(:snippet) { create(:project_snippet, project: project, author: create(:user)) } + let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: snippet.project.id, note: '@all mentioned') } + + before do + build_team(note.project) + note.project.team << [note.author, :master] + ActionMailer::Base.deliveries.clear + end + + describe '#new_note' do + it 'notifies the team members' do + notification.new_note(note) + + # Notify all team members + note.project.team.members.each do |member| + # User with disabled notification should not be notified + next if member.id == @u_disabled.id + # Author should not be notified + next if member.id == note.author.id + should_email(member) + end + + should_email(note.noteable.author) + should_not_email(note.author) + should_email(@u_mentioned) + should_not_email(@u_disabled) + should_email(@u_not_mentioned) + end + end + end + context 'commit note' do let(:project) { create(:project, :public) } let(:note) { create(:note_on_commit, project: project) } @@ -187,7 +220,7 @@ describe NotificationService, services: true do allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer) end - describe :new_note, :perform_enqueued_jobs do + describe '#new_note, #perform_enqueued_jobs' do it do notification.new_note(note) @@ -230,7 +263,7 @@ describe NotificationService, services: true do ActionMailer::Base.deliveries.clear end - describe :new_issue do + describe '#new_issue' do it do notification.new_issue(issue, @u_disabled) @@ -289,7 +322,7 @@ describe NotificationService, services: true do end end - describe :reassigned_issue do + describe '#reassigned_issue' do it 'emails new assignee' do notification.reassigned_issue(issue, @u_disabled) @@ -419,7 +452,7 @@ describe NotificationService, services: true do end end - describe :close_issue do + describe '#close_issue' do it 'should sent email to issue assignee and issue author' do notification.close_issue(issue, @u_disabled) @@ -435,7 +468,7 @@ describe NotificationService, services: true do end end - describe :reopen_issue do + describe '#reopen_issue' do it 'should send email to issue assignee and issue author' do notification.reopen_issue(issue, @u_disabled) @@ -461,7 +494,7 @@ describe NotificationService, services: true do ActionMailer::Base.deliveries.clear end - describe :new_merge_request do + describe '#new_merge_request' do it do notification.new_merge_request(merge_request, @u_disabled) @@ -483,7 +516,7 @@ describe NotificationService, services: true do end end - describe :reassigned_merge_request do + describe '#reassigned_merge_request' do it do notification.reassigned_merge_request(merge_request, merge_request.author) @@ -498,7 +531,7 @@ describe NotificationService, services: true do end end - describe :relabel_merge_request do + describe '#relabel_merge_request' do let(:label) { create(:label, merge_requests: [merge_request]) } let(:label2) { create(:label) } let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } } @@ -527,7 +560,7 @@ describe NotificationService, services: true do end end - describe :closed_merge_request do + describe '#closed_merge_request' do it do notification.close_mr(merge_request, @u_disabled) @@ -542,7 +575,7 @@ describe NotificationService, services: true do end end - describe :merged_merge_request do + describe '#merged_merge_request' do it do notification.merge_mr(merge_request, @u_disabled) @@ -557,7 +590,7 @@ describe NotificationService, services: true do end end - describe :reopen_merge_request do + describe '#reopen_merge_request' do it do notification.reopen_mr(merge_request, @u_disabled) @@ -581,7 +614,7 @@ describe NotificationService, services: true do ActionMailer::Base.deliveries.clear end - describe :project_was_moved do + describe '#project_was_moved' do it do notification.project_was_moved(project, "gitlab/gitlab") diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 596d607f2a1..576d16e7ea3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -51,10 +51,4 @@ FactoryGirl::SyntaxRunner.class_eval do include RSpec::Mocks::ExampleMethods end -# Work around a Rails 4.2.5.1 issue -# See https://github.com/rspec/rspec-rails/issues/1532 -RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator.class_eval do - alias_method :find_all_anywhere, :find_all -end - ActiveRecord::Migration.maintain_test_schema! diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb new file mode 100644 index 00000000000..b6d7436c360 --- /dev/null +++ b/spec/support/issue_tracker_service_shared_example.rb @@ -0,0 +1,7 @@ +RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr| + it { is_expected.to allow_value('https://example.com').for(url_attr) } + + it { is_expected.not_to allow_value('example.com').for(url_attr) } + it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) } + it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) } +end diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb index 087e4c667d8..5a03bb77ebd 100644 --- a/spec/workers/repository_check/single_repository_worker_spec.rb +++ b/spec/workers/repository_check/single_repository_worker_spec.rb @@ -12,7 +12,7 @@ describe RepositoryCheck::SingleRepositoryWorker do subject.perform(project.id) expect(project.reload.last_repository_check_failed).to eq(false) - destroy_wiki(project) + break_wiki(project) subject.perform(project.id) expect(project.reload.last_repository_check_failed).to eq(true) @@ -20,15 +20,38 @@ describe RepositoryCheck::SingleRepositoryWorker do it 'skips wikis when disabled' do project = create(:project_empty_repo, wiki_enabled: false) - # Make sure the test would fail if it checked the wiki repo - destroy_wiki(project) + # Make sure the test would fail if the wiki repo was checked + break_wiki(project) subject.perform(project.id) expect(project.reload.last_repository_check_failed).to eq(false) end - def destroy_wiki(project) - FileUtils.rm_rf(project.wiki.repository.path_to_repo) + it 'creates missing wikis' do + project = create(:project_empty_repo, wiki_enabled: true) + FileUtils.rm_rf(wiki_path(project)) + + subject.perform(project.id) + + expect(project.reload.last_repository_check_failed).to eq(false) + end + + it 'does not create a wiki if the main repo does not exist at all' do + project = create(:project_empty_repo) + FileUtils.rm_rf(project.repository.path_to_repo) + FileUtils.rm_rf(wiki_path(project)) + + subject.perform(project.id) + + expect(File.exist?(wiki_path(project))).to eq(false) + end + + def break_wiki(project) + FileUtils.rm_rf(wiki_path(project) + '/objects') + end + + def wiki_path(project) + project.wiki.repository.path_to_repo end end |