diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2016-06-28 14:38:05 +0200 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2016-06-28 14:38:05 +0200 |
commit | 2846f95d2a6a7418fb655f6588037bd3173fe77f (patch) | |
tree | 6ee5282cf9851c08c65ce024486782d4b9a3a78c | |
parent | c019585cb83b1852451184663085e6f0e0d12024 (diff) | |
parent | 365015e3c935afd8e4d3073078712cccd3077204 (diff) | |
download | gitlab-ce-2846f95d2a6a7418fb655f6588037bd3173fe77f.tar.gz |
Merge branch 'master' into refactor/ci-config-move-global-entries
* master: (352 commits)
Display last commit of deleted branch in push events (!4699)
add changelog
add missing attribute to attr_encrypted so it is fully backwards-compatible
Add "GitLab team members only" to diagram link
doc: note that .gitattributes uses default branch
use the conf lexer so we have highlighted comments
first draft of docs
support cgi style options, such as erb?parent=json
move the path alias to a more appropriate location
make #custom_language private
appease rubocop
add an alias for Snippet#path
appease rubocop
check the tag so that an instance will pass too
fix the spec, using project.change_head
Revert "bump the master sha for gitlab-test!9"
bump the master sha for gitlab-test!9
add custom highlighting via .gitattributes
Rename Licenses API to License Templates API
Check for conflict with wiki projects when creating a new project.
...
552 files changed, 7573 insertions, 3362 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 219077d79b8..b917c645ff8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -134,6 +134,11 @@ spinach 9 10: *spinach-knapsack image: "ruby:2.3" only: - master + cache: + key: "ruby-23" + paths: + - vendor/apt + - vendor/ruby .rspec-knapsack-ruby23: &rspec-knapsack-ruby23 <<: *rspec-knapsack diff --git a/CHANGELOG b/CHANGELOG index 44e6a194745..1484edaceab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,74 @@ Please view this file on the master branch, on stable branches it's out of date. -v 8.9.0 (unreleased) +v 8.10.0 (unreleased) + - Replace Haml with Hamlit to make view rendering faster. !3666 + - Wrap code blocks on Activies and Todos page. !4783 (winniehell) + - Display last commit of deleted branch in push events !4699 (winniehell) + - Add Sidekiq queue duration to transaction metrics. + - Make images fit to the size of the viewport !4810 + - Fix check for New Branch button on Issue page !4630 (winniehell) + - Fix MR-auto-close text added to description. !4836 + - Fix pagination when sorting by columns with lots of ties (like priority) + - Exclude email check from the standard health check + - Fix changing issue state columns in milestone view + - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) + - Check for conflicts with existing Project's wiki path when creating a new project. + - Add API endpoint for a group issues !4520 (mahcsig) + - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) + +v 8.9.3 (unreleased) + - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem + +v 8.9.2 + - Fix visibility of snippets when searching. + - Fix an information disclosure when requesting access to a group containing private projects. + - Update omniauth-saml to 1.6.0 !4951 + +v 8.9.1 + - Refactor labels documentation. !3347 + - Eager load award emoji on notes. !4628 + - Fix some CI wording in documentation. !4660 + - Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720 + - Add documentation for the export & import features. !4732 + - Add some docs for Docker Registry configuration. !4738 + - Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744 + - Display group/project access requesters separately in the admin area. !4798 + - Add documentation and examples for configuring cloud storage for registry images. !4812 + - Clarifies documentation about artifact expiry. !4831 + - Fix the Network graph links. !4832 + - Fix MR-auto-close text added to description. !4836 + - Add documentation for award emoji now that comments can be awarded with emojis. !4839 + - Fix typo in export failure email. !4847 + - Fix header vertical centering. !4170 + - Fix subsequent SAML sign ins. !4718 + - Set button label when picking an option from status dropdown. !4771 + - Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775 + - Handle external issues in IssueReferenceFilter. !4789 + - Support for rendering/redacting multiple documents. !4828 + - Update Todos documentation and screenshots to include new functionality. !4840 + - Hide nav arrows by default. !4843 + - Added bottom padding to label color suggestion link. !4845 + - Use jQuery objects in ref dropdown. !4850 + - Fix GitLab project import issues related to notes and builds. !4855 + - Restrict header logo to 36px so it doesn't overflow. !4861 + - Fix unwanted label unassignment. !4863 + - Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869 + - Restore old behavior around diff notes to outdated discussions. !4870 + - Fix merge requests project settings help link anchor. !4873 + - Fix 404 when accessing pipelines as guest user on public projects. !4881 + - Remove width restriction for logo on sign-in page. !4888 + - Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884 + - Apply selected value as label. !4886 + - Fix temp file being deleted after the request while importing a GitLab project. !4894 + - Fix pagination when sorting by columns with lots of ties (like priority) + - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. + - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) + - Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912 + - Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915 + - Remove duplicate 'New Page' button on edit wiki page + +v 8.9.0 + - Fix builds API response not including commit data - Fix error when CI job variables key specified but not defined - Fix pipeline status when there are no builds in pipeline - Fix Error 500 when using closes_issues API with an external issue tracker @@ -8,17 +76,21 @@ v 8.9.0 (unreleased) - Bulk assign/unassign labels to issues. - Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - Show Star and Fork buttons on mobile. + - Performance improvements on RelativeLinkFilter - Fix endless redirections when accessing user OAuth applications when they are disabled - Allow enabling wiki page events from Webhook management UI - Bump rouge to 1.11.0 - Fix issue with arrow keys not working in search autocomplete dropdown - Fix an issue where note polling stopped working if a window was in the background during a refresh. + - Pre-processing Markdown now only happens when needed - Make EmailsOnPushWorker use Sidekiq mailers queue - Redesign all Devise emails. !4297 - Don't show 'Leave Project' to group members - Fix wiki page events' webhook to point to the wiki repository + - Add a border around images to differentiate them from the background. - Don't show tags for revert and cherry-pick operations + - Show image ID on registry page - Fix issue todo not remove when leave project !4150 (Long Nguyen) - Allow customisable text on the 'nearly there' page after a user signs up - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support @@ -55,6 +127,7 @@ v 8.9.0 (unreleased) - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged - Don't allow MRs to be merged when commits were added since the last review / page load - Add DB index on users.state + - Limit email on push diff size to 30 files / 150 KB - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database - Changed the Slack build message to use the singular duration if necessary (Aran Koning) - Fix race condition on merge when build succeeds @@ -69,6 +142,7 @@ v 8.9.0 (unreleased) - Todos will display target state if issuable target is 'Closed' or 'Merged' - Validate only and except regexp - Fix bug when sorting issues by milestone due date and filtering by two or more labels + - POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project - Add support for using Yubikeys (U2F) for two-factor authentication - Link to blank group icon doesn't throw a 404 anymore - Remove 'main language' feature @@ -76,13 +150,16 @@ v 8.9.0 (unreleased) - Pipelines can be canceled only when there are running builds - Allow authentication using personal access tokens - Use downcased path to container repository as this is expected path by Docker + - Allow to use CI token to fetch LFS objects - Custom notification settings - Projects pending deletion will render a 404 page - Measure queue duration between gitlab-workhorse and Rails - Added Gfm autocomplete for labels + - Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114 - Make Omniauth providers specs to not modify global configuration - Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir) - Make authentication service for Container Registry to be compatible with < Docker 1.11 + - Make it possible to lock a runner from being enabled for other projects - Add Application Setting to configure Container Registry token expire delay (default 5min) - Cache assigned issue and merge request counts in sidebar nav - Use Knapsack only in CI environment @@ -100,6 +177,7 @@ v 8.9.0 (unreleased) - An indicator is now displayed at the top of the comment field for confidential issues. - Show categorised search queries in the search autocomplete - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented + - Dropdown for `.gitlab-ci.yml` templates - Improve issuables APIs performance when accessing notes !4471 - Add sorting dropdown to tags page !4423 - External links now open in a new tab @@ -128,10 +206,17 @@ v 8.9.0 (unreleased) - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - Set inverse_of for Project/Service association to reduce the number of queries - Update tanuki logo highlight/loading colors + - Remove explicit Gitlab::Metrics.action assignments, are already automatic. - Use Git cached counters for branches and tags on project page + - Cache participable participants in an instance variable. - Filter parameters for request_uri value on instrumented transactions. + - Remove duplicated keys add UNIQUE index to keys fingerprint column + - ExtractsPath get ref_names from repository cache, if not there access git. - Cache user todo counts from TodoService - Ensure Todos counters doesn't count Todos for projects pending delete + - Add left/right arrows horizontal navigation + - Add tooltip to pin/unpin navbar + - Add new sub nav style to Wiki and Graphs sub navigation v 8.8.5 - Import GitHub repositories respecting the API rate limit !4166 @@ -30,7 +30,7 @@ gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-google-oauth2', '~> 0.2.0' gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos -gem 'omniauth-saml', '~> 1.5.0' +gem 'omniauth-saml', '~> 1.6.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' @@ -48,7 +48,7 @@ gem 'attr_encrypted', '~> 3.0.0' gem 'u2f', '~> 0.2.1' # Browser detection -gem "browser", '~> 2.0.3' +gem "browser", '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library @@ -76,7 +76,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' gem "kaminari", "~> 0.17.0" # HAML -gem "haml-rails", '~> 0.9.0' +gem 'hamlit', '~> 2.5' # Files attachments gem "carrierwave", '~> 0.10.0' @@ -234,7 +234,7 @@ gem 'net-ssh', '~> 3.0.1' gem 'base32', '~> 0.3.0' # Sentry integration -gem 'sentry-raven', '~> 0.15' +gem 'sentry-raven', '~> 1.1.0' gem 'premailer-rails', '~> 1.9.0' @@ -330,7 +330,7 @@ gem "newrelic_rpm", '~> 3.14' gem 'octokit', '~> 4.3.0' -gem "mail_room", "~> 0.7" +gem "mail_room", "~> 0.8" gem 'email_reply_parser', '~> 0.5.8' diff --git a/Gemfile.lock b/Gemfile.lock index 49e548fb94f..66660f546e7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -98,7 +98,7 @@ GEM autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) brakeman (3.3.2) - browser (2.0.3) + browser (2.2.0) builder (3.2.2) bullet (5.0.0) activesupport (>= 3.0.0) @@ -277,7 +277,7 @@ GEM posix-spawn (~> 0.3) gitlab_emoji (0.3.1) gemojione (~> 2.2, >= 2.2.1) - gitlab_git (10.2.0) + gitlab_git (10.2.3) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -320,14 +320,10 @@ GEM grape-entity (0.4.8) activesupport multi_json (>= 1.3.2) - haml (4.0.7) + hamlit (2.5.0) + temple (~> 0.7.6) + thor tilt - haml-rails (0.9.0) - actionpack (>= 4.0.1) - activesupport (>= 4.0.1) - haml (>= 4.0.6, < 5.0) - html2haml (>= 1.0.1) - railties (>= 4.0.1) hashie (3.4.3) health_check (1.5.1) rails (>= 2.3.0) @@ -337,11 +333,6 @@ GEM html-pipeline (1.11.0) activesupport (>= 2) nokogiri (~> 1.4) - html2haml (2.0.0) - erubis (~> 2.7.0) - haml (~> 4.0.0) - nokogiri (~> 1.6.0) - ruby_parser (~> 3.5) htmlentities (4.3.4) http_parser.rb (0.5.3) httparty (0.13.7) @@ -398,7 +389,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.7.0) + mail_room (0.8.0) method_source (0.8.2) mime-types (2.99.2) mimemagic (0.3.0) @@ -468,9 +459,9 @@ GEM omniauth-oauth2 (1.3.1) oauth2 (~> 1.0) omniauth (~> 1.2) - omniauth-saml (1.5.0) + omniauth-saml (1.6.0) omniauth (~> 1.3) - ruby-saml (~> 1.1, >= 1.1.1) + ruby-saml (~> 1.3) omniauth-shibboleth (1.2.1) omniauth (>= 1.0.0) omniauth-twitter (1.2.1) @@ -631,9 +622,8 @@ GEM ruby-fogbugz (0.2.1) crack (~> 0.4) ruby-progressbar (1.8.1) - ruby-saml (1.1.2) + ruby-saml (1.3.0) nokogiri (>= 1.5.10) - uuid (~> 2.3) ruby_parser (3.8.2) sexp_processor (~> 4.1) rubyntlm (0.5.2) @@ -665,7 +655,7 @@ GEM activesupport (>= 3.1, < 4.3) select2-rails (3.5.9.3) thor (~> 0.14) - sentry-raven (0.15.6) + sentry-raven (1.1.0) faraday (>= 0.7.6) settingslogic (2.0.9) sexp_processor (4.7.0) @@ -733,6 +723,7 @@ GEM railties (>= 3.2.5, < 6) teaspoon-jasmine (2.2.0) teaspoon (>= 1.0.0) + temple (0.7.7) term-ansicolor (1.3.2) tins (~> 1.0) test_after_commit (0.4.2) @@ -833,7 +824,7 @@ DEPENDENCIES binding_of_caller (~> 0.7.2) bootstrap-sass (~> 3.3.0) brakeman (~> 3.3.0) - browser (~> 2.0.3) + browser (~> 2.2) bullet bundler-audit byebug @@ -882,7 +873,7 @@ DEPENDENCIES gon (~> 6.0.1) grape (~> 0.13.0) grape-entity (~> 0.4.2) - haml-rails (~> 0.9.0) + hamlit (~> 2.5) health_check (~> 1.5.1) hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) @@ -899,7 +890,7 @@ DEPENDENCIES license_finder licensee (~> 8.0.0) loofah (~> 2.0.3) - mail_room (~> 0.7) + mail_room (~> 0.8) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) @@ -920,7 +911,7 @@ DEPENDENCIES omniauth-gitlab (~> 1.0.0) omniauth-google-oauth2 (~> 0.2.0) omniauth-kerberos (~> 0.3.0) - omniauth-saml (~> 1.5.0) + omniauth-saml (~> 1.6.0) omniauth-shibboleth (~> 1.2.0) omniauth-twitter (~> 1.2.0) omniauth_crowd (~> 2.2.0) @@ -960,7 +951,7 @@ DEPENDENCIES sdoc (~> 0.3.20) seed-fu (~> 2.3.5) select2-rails (~> 3.5.9) - sentry-raven (~> 0.15) + sentry-raven (~> 1.1.0) settingslogic (~> 2.0.9) sham_rack shoulda-matchers (~> 2.8.0) @@ -1 +1 @@ -8.9.0-pre +8.10.0-pre diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index 3f61ea1eaf4..cf46f15a156 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -7,6 +7,7 @@ labelsPath: "/api/:version/projects/:id/labels" licensePath: "/api/:version/licenses/:key" gitignorePath: "/api/:version/gitignores/:key" + gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key" group: (group_id, callback) -> url = Api.buildUrl(Api.groupPath) @@ -110,6 +111,12 @@ $.get url, (gitignore) -> callback(gitignore) + gitlabCiYml: (key, callback) -> + url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key) + + $.get url, (file) -> + callback(file) + buildUrl: (url) -> url = gon.relative_url_root + url if gon.relative_url_root? return url.replace(':version', gon.api_version) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 2f9f6c3ef5b..5c5a4ca7670 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -50,7 +50,7 @@ #= require_directory ./ci #= require_directory ./commit #= require_directory ./extensions -#= require_directory ./lib +#= require_directory ./lib/utils #= require_directory ./u2f #= require_directory . #= require fuzzaldrin-plus @@ -121,6 +121,11 @@ window.onload = -> setTimeout shiftWindow, 100 $ -> + + $document = $(document) + $window = $(window) + $body = $('body') + gl.utils.preventDisabledButtons() bootstrapBreakpoint = bp.getBreakpointSize() @@ -152,7 +157,7 @@ $ -> ), 1 # Initialize tooltips - $('body').tooltip( + $body.tooltip( selector: '.has-tooltip, [data-toggle="tooltip"]' placement: (_, el) -> $el = $(el) @@ -171,7 +176,7 @@ $ -> flash.show() # Disable form buttons while a form is submitting - $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> + $body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> buttons = $('[type="submit"]', @) switch e.type @@ -184,7 +189,7 @@ $ -> $('.account-box').hover -> $(@).toggleClass('hover') # Commit show suppressed diff - $(document).on 'click', '.diff-content .js-show-suppressed-diff', -> + $document.on 'click', '.diff-content .js-show-suppressed-diff', -> $container = $(@).parent() $container.next('table').show() $container.remove() @@ -197,13 +202,13 @@ $ -> $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left") # Show/hide comments on diff - $("body").on "click", ".js-toggle-diff-comments", (e) -> + $body.on "click", ".js-toggle-diff-comments", (e) -> $(@).toggleClass('active') $(@).closest(".diff-file").find(".notes_holder").toggle() e.preventDefault() - $(document).off "click", '.js-confirm-danger' - $(document).on "click", '.js-confirm-danger', (e) -> + $document.off "click", '.js-confirm-danger' + $document.on "click", '.js-confirm-danger', (e) -> e.preventDefault() btn = $(e.target) text = btn.data("confirm-danger-message") @@ -211,7 +216,7 @@ $ -> new ConfirmDangerModal(form, text) - $(document).on 'click', 'button', -> + $document.on 'click', 'button', -> $(this).blur() $('input[type="search"]').each -> @@ -219,7 +224,7 @@ $ -> $this.attr 'value', $this.val() return - $(document) + $document .off 'keyup', 'input[type="search"]' .on 'keyup', 'input[type="search"]' , (e) -> $this = $(this) @@ -227,7 +232,7 @@ $ -> $sidebarGutterToggle = $('.js-sidebar-toggle') - $(document) + $document .off 'breakpoint:change' .on 'breakpoint:change', (e, breakpoint) -> if breakpoint is 'sm' or breakpoint is 'xs' @@ -239,14 +244,14 @@ $ -> oldBootstrapBreakpoint = bootstrapBreakpoint bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint != oldBootstrapBreakpoint - $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) + $document.trigger('breakpoint:change', [bootstrapBreakpoint]) checkInitialSidebarSize = -> bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint is "xs" or "sm" - $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) + $document.trigger('breakpoint:change', [bootstrapBreakpoint]) - $(window) + $window .off "resize.app" .on "resize.app", (e) -> fitSidebarForSize() @@ -256,29 +261,45 @@ $ -> new Aside() # Sidenav pinning - if $(window).width() < 1440 and $.cookie('pin_nav') is 'true' - $.cookie('pin_nav', 'false') + if $window.width() < 1440 and $.cookie('pin_nav') is 'true' + $.cookie('pin_nav', 'false', { path: '/' }) $('.page-with-sidebar') .toggleClass('page-sidebar-collapsed page-sidebar-expanded') .removeClass('page-sidebar-pinned') $('.navbar-fixed-top').removeClass('header-pinned-nav') - $(document) + $document .off 'click', '.js-nav-pin' .on 'click', '.js-nav-pin', (e) -> e.preventDefault() + $pinBtn = $(e.currentTarget) + $page = $ '.page-with-sidebar' + $topNav = $ '.navbar-fixed-top' + $tooltip = $ "##{$pinBtn.attr('aria-describedby')}" + doPinNav = not $page.is('.page-sidebar-pinned') + tooltipText = 'Pin navigation' + $(this).toggleClass 'is-active' - if $.cookie('pin_nav') is 'true' - $.cookie 'pin_nav', 'false' - $('.page-with-sidebar') - .removeClass('page-sidebar-pinned') - .toggleClass('page-sidebar-collapsed page-sidebar-expanded') - $('.navbar-fixed-top') - .removeClass('header-pinned-nav') - .toggleClass('header-collapsed header-expanded') + if doPinNav + $page.addClass('page-sidebar-pinned') + $topNav.addClass('header-pinned-nav') else - $.cookie 'pin_nav', 'true' - $('.page-with-sidebar').addClass('page-sidebar-pinned') - $('.navbar-fixed-top').addClass('header-pinned-nav') + $tooltip.remove() # Remove it immediately when collapsing the sidebar + $page.removeClass('page-sidebar-pinned') + .toggleClass('page-sidebar-collapsed page-sidebar-expanded') + $topNav.removeClass('header-pinned-nav') + .toggleClass('header-collapsed header-expanded') + + # Save settings + $.cookie 'pin_nav', doPinNav, { path: '/' } + + if $.cookie('pin_nav') is 'true' or doPinNav + tooltipText = 'Unpin navigation' + + # Update tooltip text immediately + $tooltip.find('.tooltip-inner').text(tooltipText) + + # Persist tooltip title + $pinBtn.attr('title', tooltipText).tooltip('fixTitle') diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 030f1564862..37d0adaa625 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -341,7 +341,9 @@ class @AwardsHandler for emoji in frequentlyUsedEmojis $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul) - $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used')) + $('.emoji-menu-content') + .prepend(ul) + .prepend($('<h5>').text('Frequently used')) @frequentEmojiBlockRendered = true @@ -356,7 +358,7 @@ class @AwardsHandler if term # Generate a search result block - h5 = $('<h5>').text('Search results').addClass('emoji-search') + h5 = $('<h5>').text('Search results') 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() diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.coffee b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee new file mode 100644 index 00000000000..d9a03d05529 --- /dev/null +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee @@ -0,0 +1,23 @@ +#= require blob/template_selector + +class @BlobCiYamlSelector extends TemplateSelector + requestFile: (query) -> + Api.gitlabCiYml query.name, @requestFileSuccess.bind(@) + +class @BlobCiYamlSelectors + constructor: (opts) -> + { + @$dropdowns = $('.js-gitlab-ci-yml-selector') + @editor + } = opts + + @$dropdowns.each (i, dropdown) => + $dropdown = $(dropdown) + + new BlobCiYamlSelector( + pattern: /(.gitlab-ci.yml)/, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), + dropdown: $dropdown, + editor: @editor + ) diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee index 636f909dbd0..19e584519d7 100644 --- a/app/assets/javascripts/blob/edit_blob.js.coffee +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -15,6 +15,7 @@ class @EditBlob new BlobLicenseSelectors { @editor } new BlobGitignoreSelectors { @editor } + new BlobCiYamlSelectors { @editor } initModePanesAndLinks: -> @$editModePanes = $(".js-edit-mode-pane") diff --git a/app/assets/javascripts/blob/template_selector.js.coffee b/app/assets/javascripts/blob/template_selector.js.coffee index e76e303189d..40c9169beac 100644 --- a/app/assets/javascripts/blob/template_selector.js.coffee +++ b/app/assets/javascripts/blob/template_selector.js.coffee @@ -19,6 +19,7 @@ class @TemplateSelector data: @data, filterable: true, selectable: true, + toggleLabel: @toggleLabel, search: fields: ['name'] clicked: @onClick @@ -31,6 +32,9 @@ class @TemplateSelector @onFilenameUpdate() ) + toggleLabel: (item) -> + item.name + onFilenameUpdate: -> return unless @$input.length diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 2a8a1f05b35..703128fecb3 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -58,7 +58,7 @@ class GitLabDropdownFilter filter: (search_text) -> data = @options.data() - if data? + if data? and not @options.filterByText results = data if search_text isnt '' @@ -102,10 +102,11 @@ class GitLabDropdownFilter $el = $(@) matches = fuzzaldrinPlus.match($el.text().trim(), search_text) - if matches.length - $el.show() - else - $el.hide() + unless $el.is('.dropdown-header') + if matches.length + $el.show() + else + $el.hide() else elements.show() @@ -191,6 +192,7 @@ class GitLabDropdown if @options.filterable @filter = new GitLabDropdownFilter @filterInput, filterInputBlur: @filterInputBlur + filterByText: @options.filterByText remote: @options.filterRemote query: @options.data keys: searchFields @@ -278,7 +280,7 @@ class GitLabDropdown html = @renderData(data) # Render the full menu - full_html = @renderMenu(html.join("")) + full_html = @renderMenu(html) @appendMenu(full_html) @@ -349,7 +351,8 @@ class GitLabDropdown if @options.renderMenu menu_html = @options.renderMenu(html) else - menu_html = "<ul>#{html}</ul>" + menu_html = $('<ul />') + .append(html) return menu_html @@ -358,7 +361,9 @@ class GitLabDropdown selector = '.dropdown-content' if @dropdown.find(".dropdown-toggle-page").length selector = ".dropdown-page-one .dropdown-content" - $(selector, @dropdown).html html + $(selector, @dropdown) + .empty() + .append(html) # Render the row renderItem: (data, group = false, index = false) -> @@ -457,7 +462,7 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel - @updateLabel() + @updateLabel(selectedObject, el, @) else selectedObject else if el.hasClass(INDETERMINATE_CLASS) @@ -484,7 +489,7 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel - @updateLabel(selectedObject, el) + @updateLabel(selectedObject, el, @) if value? if !field.length and fieldName @addInput(fieldName, value) @@ -583,8 +588,8 @@ class GitLabDropdown # Scroll the dropdown content up $dropdownContent.scrollTop(listItemTop - dropdownContentTop) - updateLabel: (selected = null, el = null) => - $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el) + updateLabel: (selected = null, el = null, instance = null) => + $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance) $.fn.glDropdown = (opts) -> return @.each -> diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee index d540cc4dc46..77512d187c9 100644 --- a/app/assets/javascripts/gl_form.js.coffee +++ b/app/assets/javascripts/gl_form.js.coffee @@ -34,6 +34,8 @@ class @GLForm # form and textarea event listeners @addEventListeners() + gl.text.init(@form) + # hide discard button @form.find('.js-note-discard').hide() @@ -42,6 +44,7 @@ class @GLForm clearEventListeners: -> @textarea.off 'focus' @textarea.off 'blur' + gl.text.removeListeners(@form) addEventListeners: -> @textarea.on 'focus', -> diff --git a/app/assets/javascripts/graphs/application.js.coffee b/app/assets/javascripts/graphs/application.js.coffee index 91f81a5d249..e0f681acf0b 100644 --- a/app/assets/javascripts/graphs/application.js.coffee +++ b/app/assets/javascripts/graphs/application.js.coffee @@ -4,5 +4,4 @@ # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the # the compiled file. # -#= require Chart #= require_tree . diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee index 584d281a510..834a81af459 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee @@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph class @ContributorsAuthorGraph extends ContributorsGraph constructor: (@data) -> - @width = $('.content').width()/2 - 100 + # Don't split graph size in half for mobile devices. + if $(window).width() < 768 + @width = $('.content').width() - 80 + else + @width = ($('.content').width() / 2) - 100 @height = 200 @x = null @y = null diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee index d0901be1509..6a108c033ea 100644 --- a/app/assets/javascripts/issuable.js.coffee +++ b/app/assets/javascripts/issuable.js.coffee @@ -68,12 +68,15 @@ issuable_created = false Turbolinks.visit(issuesUrl); initChecks: -> + @issuableBulkActions = $('.bulk-update').data('bulkActions') + $('.check_all_issues').off('click').on('click', -> $('.selected_issue').prop('checked', @checked) Issuable.checkChanged() ) - $('.selected_issue').off('change').on('change', Issuable.checkChanged) + $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(@)) + checkChanged: -> checked_issues = $('.selected_issue:checked') @@ -88,3 +91,6 @@ issuable_created = false $('#update_issues_ids').val [] $('.issues_bulk_update').hide() $('.issues-other-filters').show() + @issuableBulkActions.willUpdateLabels = false + + return true diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 157361404e0..f446aa49cde 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -99,7 +99,7 @@ class @Issue # If the user doesn't have the required permissions the container isn't # rendered at all. - return unless $container + return if $container.length is 0 $.getJSON($container.data('path')) .error -> diff --git a/app/assets/javascripts/issue_status_select.js.coffee b/app/assets/javascripts/issue_status_select.js.coffee index c5740f27ddd..ed50e2e698f 100644 --- a/app/assets/javascripts/issue_status_select.js.coffee +++ b/app/assets/javascripts/issue_status_select.js.coffee @@ -6,6 +6,13 @@ class @IssueStatusSelect $(el).glDropdown( selectable: true fieldName: fieldName + toggleLabel: (selected, el, instance) => + label = 'Author' + $item = instance.dropdown.find('.is-active') + label = $item.text() if $item.length + label + clicked: (item, $el, e)-> + e.preventDefault() id: (obj, el) -> $(el).data("id") ) diff --git a/app/assets/javascripts/issues-bulk-assignment.js.coffee b/app/assets/javascripts/issues-bulk-assignment.js.coffee index b454f9389dd..6b0e69dbae7 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js.coffee +++ b/app/assets/javascripts/issues-bulk-assignment.js.coffee @@ -7,6 +7,11 @@ class @IssuableBulkActions @issues = @getElement('.issues-list .issue') } = opts + # Save instance + @form.data 'bulkActions', @ + + @willUpdateLabels = false + @bindEvents() # Fixes bulk-assign not working when navigating through pages @@ -87,11 +92,12 @@ class @IssuableBulkActions add_label_ids : [] remove_label_ids : [] - @getLabelsToApply().map (id) -> - formData.update.add_label_ids.push id + if @willUpdateLabels + @getLabelsToApply().map (id) -> + formData.update.add_label_ids.push id - @getLabelsToRemove().map (id) -> - formData.update.remove_label_ids.push id + @getLabelsToRemove().map (id) -> + formData.update.remove_label_ids.push id formData diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 6a10db10eb1..e95fd96a83f 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -319,6 +319,8 @@ class @LabelsSelect multiSelect: $dropdown.hasClass 'js-multiselect' clicked: (label) -> + _this.enableBulkLabelDropdown() + if $dropdown.hasClass('js-filter-bulk-update') return @@ -377,3 +379,8 @@ class @LabelsSelect label_ids.push $("#issue_#{issue_id}").data('labels') _.intersection.apply _, label_ids + + enableBulkLabelDropdown: -> + if $('.selected_issue:checked').length + issuableBulkActions = $('.bulk-update').data('bulkActions') + issuableBulkActions.willUpdateLabels = true diff --git a/app/assets/javascripts/layout_nav.js.coffee b/app/assets/javascripts/layout_nav.js.coffee index f8f0aea427e..f639f7f5892 100644 --- a/app/assets/javascripts/layout_nav.js.coffee +++ b/app/assets/javascripts/layout_nav.js.coffee @@ -3,11 +3,10 @@ hideEndFade = ($scrollingTabs) -> $this = $(@) $this - .find('.fade-right') - .toggleClass('end-scroll', $this.width() is $this.prop('scrollWidth')) + .siblings('.fade-right') + .toggleClass('scrolling', $this.width() < $this.prop('scrollWidth')) $ -> - $('.fade-left').addClass('end-scroll') hideEndFade($('.scrolling-tabs')) @@ -21,5 +20,5 @@ $ -> currentPosition = $this.scrollLeft() maxPosition = $this.prop('scrollWidth') - $this.outerWidth() - $this.find('.fade-left').toggleClass('end-scroll', currentPosition is 0) - $this.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition) + $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0) + $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1) diff --git a/app/assets/javascripts/lib/chart.js.coffee b/app/assets/javascripts/lib/chart.js.coffee new file mode 100644 index 00000000000..82217fc5107 --- /dev/null +++ b/app/assets/javascripts/lib/chart.js.coffee @@ -0,0 +1 @@ +#= require Chart diff --git a/app/assets/javascripts/lib/d3.js.coffee b/app/assets/javascripts/lib/d3.js.coffee new file mode 100644 index 00000000000..74f0a0bb06a --- /dev/null +++ b/app/assets/javascripts/lib/d3.js.coffee @@ -0,0 +1 @@ +#= require d3 diff --git a/app/assets/javascripts/lib/raphael.js.coffee b/app/assets/javascripts/lib/raphael.js.coffee new file mode 100644 index 00000000000..ab8e5979b87 --- /dev/null +++ b/app/assets/javascripts/lib/raphael.js.coffee @@ -0,0 +1,3 @@ +#= require raphael +#= require g.raphael +#= require g.bar diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/utils/animate.js.coffee index ec3b44d6126..ec3b44d6126 100644 --- a/app/assets/javascripts/lib/animate.js.coffee +++ b/app/assets/javascripts/lib/utils/animate.js.coffee diff --git a/app/assets/javascripts/lib/common_utils.js.coffee b/app/assets/javascripts/lib/utils/common_utils.js.coffee index e39dcb2daa9..e39dcb2daa9 100644 --- a/app/assets/javascripts/lib/common_utils.js.coffee +++ b/app/assets/javascripts/lib/utils/common_utils.js.coffee diff --git a/app/assets/javascripts/lib/datetime_utility.js.coffee b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee index 948d6dbf07e..948d6dbf07e 100644 --- a/app/assets/javascripts/lib/datetime_utility.js.coffee +++ b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee diff --git a/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb b/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb index 80f9936b9c2..80f9936b9c2 100644 --- a/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb +++ b/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb diff --git a/app/assets/javascripts/lib/jquery.timeago.js b/app/assets/javascripts/lib/utils/jquery.timeago.js index cc17aa7d3d1..cc17aa7d3d1 100644 --- a/app/assets/javascripts/lib/jquery.timeago.js +++ b/app/assets/javascripts/lib/utils/jquery.timeago.js diff --git a/app/assets/javascripts/lib/md5.js b/app/assets/javascripts/lib/utils/md5.js index b63716eaad2..b63716eaad2 100644 --- a/app/assets/javascripts/lib/md5.js +++ b/app/assets/javascripts/lib/utils/md5.js diff --git a/app/assets/javascripts/lib/notify.js.coffee b/app/assets/javascripts/lib/utils/notify.js.coffee index 9e28353ac34..9e28353ac34 100644 --- a/app/assets/javascripts/lib/notify.js.coffee +++ b/app/assets/javascripts/lib/utils/notify.js.coffee diff --git a/app/assets/javascripts/lib/utils/text_utility.js.coffee b/app/assets/javascripts/lib/utils/text_utility.js.coffee new file mode 100644 index 00000000000..bb2772dfed2 --- /dev/null +++ b/app/assets/javascripts/lib/utils/text_utility.js.coffee @@ -0,0 +1,79 @@ +((w) -> + w.gl ?= {} + w.gl.text ?= {} + + gl.text.randomString = -> Math.random().toString(36).substring(7) + + gl.text.replaceRange = (s, start, end, substitute) -> + s.substring(0, start) + substitute + s.substring(end); + + gl.text.selectedText = (text, textarea) -> + text.substring(textarea.selectionStart, textarea.selectionEnd) + + gl.text.insertText = (textArea, text, tag, selected, wrap) -> + selectedSplit = selected.split('\n') + startChar = if not wrap and textArea.selectionStart > 0 then '\n' else '' + + if selectedSplit.length > 1 and not wrap + insertText = selectedSplit.map((val) -> + if val.indexOf(tag) is 0 + "#{val.replace(tag, '')}" + else + "#{tag}#{val}" + ).join('\n') + else + insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}" + + if document.queryCommandSupported('insertText') + document.execCommand 'insertText', false, insertText + else + try + document.execCommand("ms-beginUndoUnit") + + textArea.value = @replaceRange( + text, + textArea.selectionStart, + textArea.selectionEnd, + insertText) + try + document.execCommand("ms-endUndoUnit") + + @moveCursor(textArea, tag, wrap) + + gl.text.moveCursor = (textArea, tag, wrapped) -> + return unless textArea.setSelectionRange + + if textArea.selectionStart is textArea.selectionEnd + if wrapped + pos = textArea.selectionStart - tag.length + else + pos = textArea.selectionStart + + textArea.setSelectionRange pos, pos + + gl.text.updateText = (textArea, tag, wrap) -> + $textArea = $(textArea) + oldVal = $textArea.val() + textArea = $textArea.get(0) + text = $textArea.val() + selected = @selectedText(text, textArea) + $textArea.focus() + + @insertText(textArea, text, tag, selected, wrap) + + gl.text.init = (form) -> + self = @ + $('.js-md', form) + .off 'click' + .on 'click', -> + $this = $(@) + self.updateText( + $this.closest('.md-area').find('textarea'), + $this.data('md-tag'), + not $this.data('md-prepend') + ) + + gl.text.removeListeners = (form) -> + $('.js-md', form).off() + +) window diff --git a/app/assets/javascripts/lib/type_utility.js.coffee b/app/assets/javascripts/lib/utils/type_utility.js.coffee index 957f0d86b36..957f0d86b36 100644 --- a/app/assets/javascripts/lib/type_utility.js.coffee +++ b/app/assets/javascripts/lib/utils/type_utility.js.coffee diff --git a/app/assets/javascripts/lib/url_utility.js.coffee b/app/assets/javascripts/lib/utils/url_utility.js.coffee index e8085e1c2e4..e8085e1c2e4 100644 --- a/app/assets/javascripts/lib/url_utility.js.coffee +++ b/app/assets/javascripts/lib/utils/url_utility.js.coffee diff --git a/app/assets/javascripts/lib/utf8_encode.js b/app/assets/javascripts/lib/utils/utf8_encode.js index 39ffe44dae0..39ffe44dae0 100644 --- a/app/assets/javascripts/lib/utf8_encode.js +++ b/app/assets/javascripts/lib/utils/utf8_encode.js diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee index 0037a3a21c2..a19e68b39e2 100644 --- a/app/assets/javascripts/milestone.js.coffee +++ b/app/assets/javascripts/milestone.js.coffee @@ -4,18 +4,10 @@ class @Milestone type: "PUT" url: issue_url data: data - success: (data) -> - if data.saved == true - if data.assignee_avatar_url - img_tag = $('<img/>') - img_tag.attr('src', data.assignee_avatar_url) - img_tag.addClass('avatar s16') - $(li).find('.assignee-icon').html(img_tag) - else - $(li).find('.assignee-icon').html('') - $(li).effect 'highlight' - else - new Flash("Issue update failed", 'alert') + success: (_data) => + @successCallback(_data, li) + error: (data) -> + new Flash("Issue update failed", 'alert') dataType: "json" @sortIssues: (data) -> @@ -25,9 +17,10 @@ class @Milestone type: "PUT" url: sort_issues_url data: data - success: (data) -> - if data.saved != true - new Flash("Issues update failed", 'alert') + success: (_data) => + @successCallback(_data) + error: -> + new Flash("Issues update failed", 'alert') dataType: "json" @sortMergeRequests: (data) -> @@ -37,9 +30,10 @@ class @Milestone type: "PUT" url: sort_mr_url data: data - success: (data) -> - if data.saved != true - new Flash("MR update failed", 'alert') + success: (_data) => + @successCallback(_data) + error: (data) -> + new Flash("Issue update failed", 'alert') dataType: "json" @updateMergeRequest: (li, merge_request_url, data) -> @@ -47,20 +41,23 @@ class @Milestone type: "PUT" url: merge_request_url data: data - success: (data) -> - if data.saved == true - if data.assignee_avatar_url - img_tag = $('<img/>') - img_tag.attr('src', data.assignee_avatar_url) - img_tag.addClass('avatar s16') - $(li).find('.assignee-icon').html(img_tag) - else - $(li).find('.assignee-icon').html('') - $(li).effect 'highlight' - else - new Flash("Issue update failed", 'alert') + success: (_data) => + @successCallback(_data, li) + error: (data) -> + new Flash("Issue update failed", 'alert') dataType: "json" + @successCallback: (data, element) => + if data.assignee + img_tag = $('<img/>') + img_tag.attr('src', data.assignee.avatar_url) + img_tag.addClass('avatar s16') + $(element).find('.assignee-icon').html(img_tag) + else + $(element).find('.assignee-icon').html('') + + $(element).effect 'highlight' + constructor: -> oldMouseStart = $.ui.sortable.prototype._mouseStart $.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) -> @@ -81,8 +78,10 @@ class @Milestone stop: (event, ui) -> $(".issues-sortable-list").css "min-height", "0px" update: (event, ui) -> - data = $(this).sortable("serialize") - Milestone.sortIssues(data) + # Prevents sorting from container which element has been removed. + if $(this).find(ui.item).length > 0 + data = $(this).sortable("serialize") + Milestone.sortIssues(data) receive: (event, ui) -> new_state = $(this).data('state') diff --git a/app/assets/javascripts/network/application.js.coffee b/app/assets/javascripts/network/application.js.coffee index cb9eead855b..f75f63869c5 100644 --- a/app/assets/javascripts/network/application.js.coffee +++ b/app/assets/javascripts/network/application.js.coffee @@ -4,9 +4,6 @@ # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the # the compiled file. # -#= require raphael -#= require g.raphael -#= require g.bar #= require_tree . $ -> diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index e2d3241437b..17f7e180127 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -102,12 +102,15 @@ class @Notes keydownNoteText: (e) -> $this = $(this) - if $this.val() is '' and e.which is 38 #aka the up key + if $this.val() is '' and e.which is 38 and not isMetaKey e myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last") if myLastNote.length myLastNoteEditBtn = myLastNote.find('.js-note-edit') myLastNoteEditBtn.trigger('click', [true, myLastNote]) + isMetaKey = (e) -> + (e.metaKey or e.ctrlKey or e.altKey or e.shiftKey) + initRefresh: -> clearInterval(Notes.interval) Notes.interval = setInterval => diff --git a/app/assets/javascripts/notifications_dropdown.js.coffee b/app/assets/javascripts/notifications_dropdown.js.coffee index 74d2298c1fa..0bbd082c156 100644 --- a/app/assets/javascripts/notifications_dropdown.js.coffee +++ b/app/assets/javascripts/notifications_dropdown.js.coffee @@ -1,5 +1,5 @@ class @NotificationsDropdown - $ -> + constructor: -> $(document) .off 'click', '.update-notification' .on 'click', '.update-notification', (e) -> @@ -18,7 +18,8 @@ class @NotificationsDropdown .off 'ajax:success', '.notification-form' .on 'ajax:success', '.notification-form', (e, data) -> if data.saved - new Flash('Notification settings saved', 'notice') - $(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html) + $(e.currentTarget) + .closest('.notification-dropdown') + .replaceWith(data.html) else new Flash('Failed to save new settings', 'alert') diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index d12bad97a05..3288c801388 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -19,6 +19,7 @@ class @Project $('.clone').text(url) # Ref switcher + @initRefSwitcher() $('.project-refs-select').on 'change', -> $(@).parents('form').submit() @@ -34,7 +35,6 @@ class @Project $(@).parents('.no-password-message').remove() e.preventDefault() - @projectSelectDropdown() projectSelectDropdown: -> @@ -50,3 +50,42 @@ class @Project changeProject: (url) -> window.location = url + + initRefSwitcher: -> + $('.js-project-refs-dropdown').each -> + $dropdown = $(@) + selected = $dropdown.data('selected') + + $dropdown.glDropdown( + data: (term, callback) -> + $.ajax( + url: $dropdown.data('refs-url') + data: + ref: $dropdown.data('ref') + ).done (refs) -> + callback(refs) + selectable: true + filterable: true + filterByText: true + fieldName: 'ref' + renderRow: (ref) -> + if ref.header? + $('<li />') + .addClass('dropdown-header') + .text(ref.header) + else + link = $('<a />') + .attr('href', '#') + .addClass(if ref is selected then 'is-active' else '') + .text(ref) + .attr('data-ref', escape(ref)) + + $('<li />') + .append(link) + id: (obj, $el) -> + $el.attr('data-ref') + toggleLabel: (obj, $el) -> + $el.text().trim() + clicked: (e) -> + $dropdown.closest('form').submit() + ) diff --git a/app/assets/javascripts/users/application.js.coffee b/app/assets/javascripts/users/application.js.coffee index 647ffbf5f45..91cacfece46 100644 --- a/app/assets/javascripts/users/application.js.coffee +++ b/app/assets/javascripts/users/application.js.coffee @@ -1,8 +1,2 @@ -# This is a manifest file that'll be compiled into including all the files listed below. -# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically -# be included in the compiled file accessible from http://example.com/assets/application.js -# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -# the compiled file. # -#= require d3 #= require_tree . diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 3cbddc59f11..a306b8f3f29 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -37,3 +37,4 @@ @import "framework/timeline.scss"; @import "framework/typography.scss"; @import "framework/zen.scss"; +@import "framework/blank"; diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss new file mode 100644 index 00000000000..40b5171a8c6 --- /dev/null +++ b/app/assets/stylesheets/framework/blank.scss @@ -0,0 +1,23 @@ +.blank-state { + padding-top: 20px; + padding-bottom: 20px; + text-align: center; +} + +.blank-state-no-icon { + padding-top: 40px; + padding-bottom: 40px; +} + +.blank-state-title { + margin-top: 0; + margin-bottom: 5px; + font-size: 19px; + font-weight: normal; +} + +.blank-state-text { + margin-top: 0; + margin-bottom: $gl-padding; + font-size: 15px; +} diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index d5fe5bc2ef1..38023818709 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -97,6 +97,22 @@ } } +.sub-header-block { + background-color: $white-light; + border-bottom: 1px solid $white-dark; + padding: 11px 0; + margin-bottom: 11px; + + .oneline { + line-height: 35px; + } + + &.no-bottom-space { + border-bottom: 0; + margin-bottom: 0; + } +} + .cover-block { text-align: center; background: $background-color; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index d4d579a083d..00111dfa706 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -461,10 +461,12 @@ } } - .ui-state-active, - .ui-state-hover { - color: $md-link-color; - background-color: $calendar-hover-bg; + .ui-datepicker-calendar { + .ui-state-hover, + .ui-state-active { + color: #fff; + border: 0; + } } .ui-datepicker-prev, diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index a7bcb456560..c32ce5195c6 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -26,7 +26,6 @@ header { text-align: center; #tanuki-logo, img { - width: 36px; height: 36px; } } @@ -132,6 +131,10 @@ header { transition-duration: .3s; z-index: 999; + svg, img { + height: 36px; + } + &:hover { cursor: pointer; } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index fd885b38680..fd8eaa8a691 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -65,6 +65,11 @@ a { padding-top: 0; line-height: 1; + border-bottom: 1px solid $border-color; + + &.btn.btn-xs { + padding: 2px 5px; + } } } } @@ -97,5 +102,30 @@ white-space: pre-wrap; word-break: keep-all; } + + @include bulleted-list; + } +} + +.toolbar-group { + float: left; + margin-right: -5px; + margin-left: $gl-padding; + + &:first-child { + margin-left: 0; + } +} + +.toolbar-btn { + float: left; + padding: 0 5px; + color: #959494; + background: transparent; + border: 0; + outline: 0; + + &:hover { + color: $gl-link-color; } } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 828e7224231..5ec5a96a597 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -110,3 +110,17 @@ font-size: 16px; line-height: 24px; } + +@mixin bulleted-list { + > ul { + list-style-type: disc; + + ul { + list-style-type: circle; + + ul { + list-style-type: square; + } + } + } +}
\ No newline at end of file diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index a55918f8711..6211bc04597 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -1,6 +1,6 @@ @mixin fade($gradient-direction, $rgba, $gradient-color) { - visibility: visible; - opacity: 1; + visibility: hidden; + opacity: 0; z-index: 2; position: absolute; bottom: 12px; @@ -13,11 +13,18 @@ background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%); background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%); - &.end-scroll { - visibility: hidden; - opacity: 0; + &.scrolling { + visibility: visible; + opacity: 1; transition-duration: .3s; } + + .fa { + position: relative; + top: 3px; + font-size: 13px; + color: $btn-placeholder-gray; + } } @mixin scrolling-links() { @@ -25,6 +32,7 @@ overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; + &::-webkit-scrollbar { display: none; } @@ -104,10 +112,6 @@ width: 50%; line-height: 28px; - &.wiki-page { - padding: 16px 10px 11px; - } - /* Small devices (phones, tablets, 768px and lower) */ @media (max-width: $screen-sm-min) { width: 100%; @@ -136,7 +140,7 @@ } /* Small devices (phones, tablets, 768px and lower) */ - @media (max-width: $screen-sm-max) { + @media (max-width: $screen-xs-max) { width: 100%; } } @@ -220,6 +224,7 @@ form { display: block; height: auto; + margin-bottom: 14px; input { width: 100%; @@ -268,7 +273,7 @@ float: right; padding: 7px 0 0; - @media (max-width: $screen-xs-max) { + @media (max-width: $screen-sm-max) { display: none; } @@ -299,33 +304,9 @@ } .nav-links { - @include scrolling-links(); border-bottom: none; height: 51px; - svg { - position: relative; - top: 2px; - margin-right: 2px; - height: 15px; - width: auto; - - path, - polygon { - fill: $layout-link-gray; - } - } - - .fade-right { - @include fade(left, rgba(250, 250, 250, 0.4), $background-color); - right: 0; - } - - .fade-left { - @include fade(right, rgba(250, 250, 250, 0.4), $background-color); - left: 0; - } - li { a { @@ -361,18 +342,6 @@ } } } - - .nav-control { - - .fade-right { - @media (min-width: $screen-xs-max) { - right: 68px; - } - @media (max-width: $screen-xs-min) { - right: 0; - } - } - } } .scrolling-tabs-container { @@ -380,15 +349,42 @@ .nav-links { @include scrolling-links(); + } + + .fade-right { + @include fade(left, rgba(255, 255, 255, 0.4), $background-color); + right: -5px; + + .fa { + right: -7px; + } + } + + .fade-left { + @include fade(right, rgba(255, 255, 255, 0.4), $background-color); + left: -5px; + + .fa { + left: -7px; + } + } + + &.sub-nav-scroll { .fade-right { - @include fade(left, rgba(255, 255, 255, 0.4), $background-color); right: 0; + + .fa { + right: -23px; + } } .fade-left { - @include fade(right, rgba(255, 255, 255, 0.4), $background-color); left: 0; + + .fa { + left: 10px; + } } } } @@ -401,21 +397,19 @@ .fade-right { @include fade(left, rgba(255, 255, 255, 0.4), $white-light); - right: 0; + right: -5px; + + .fa { + right: -7px; + } } .fade-left { @include fade(right, rgba(255, 255, 255, 0.4), $white-light); - left: 0; - } - - &.event-filter { - .fade-right { - visibility: hidden; + left: -5px; - @media (max-width: $screen-xs-max) { - visibility: visible; - } + .fa { + left: -7px; } } } diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index ae7bdf14c40..874416e1007 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -9,6 +9,10 @@ margin-top: -2px; float: right; } + + .dropdown-menu-toggle { + line-height: 20px; + } } .panel-body { diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index f242706ebe4..21d87cc9d34 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -165,11 +165,6 @@ background-size: 16px 16px !important; } -/** Branch/tag selector **/ -.project-refs-form .select2-container { - width: 160px !important; -} - .select2-results .select2-no-results, .select2-results .select2-searching, .select2-results .select2-ajax-error, diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index a0bb3427af0..98f917ce69b 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -91,7 +91,6 @@ text-decoration: none; font-weight: normal; outline: none; - white-space: nowrap; &:hover, &:active, diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 6211f3a52eb..5faedfedd66 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -8,8 +8,9 @@ .emoji-menu { position: absolute; margin-top: 3px; - z-index: 1000; - min-width: 160px; + padding: $gl-padding; + z-index: 9; + width: 300px; font-size: 14px; background-color: $award-emoji-menu-bg; border: 1px solid $award-emoji-menu-border; @@ -33,20 +34,18 @@ } .emoji-menu-content { - padding: $gl-padding; - width: 300px; height: 300px; overflow-y: scroll; - - input.emoji-search { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC"); - background-repeat: no-repeat; - background-position: right 5px center; - background-size: 16px; - } } } +.emoji-search { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC"); + background-repeat: no-repeat; + background-position: right 5px center; + background-size: 16px; +} + .emoji-menu-list { list-style: none; padding-left: 0; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 761e33f0df7..de534d28421 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -80,9 +80,14 @@ .commit { padding: 10px 0; + position: relative; @media (min-width: $screen-sm-min) { - padding-left: 46px; + padding-left: 20px; + + .commit-info-block { + padding-left: 44px; + } } &:not(:last-child) { @@ -95,8 +100,11 @@ vertical-align: baseline; } + .avatar { - margin-left: -46px; + position: absolute; + top: 10px; + left: 16px; } .item-title { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 1a7d5f9666e..5286b73cc50 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -4,6 +4,11 @@ margin-bottom: $gl-padding; border-radius: 3px; + .commit-short-id { + font-family: $regular_font; + font-weight: 400; + } + .diff-header { position: relative; background: $background-color; diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index a34b06f1054..1aa4e06d975 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -60,13 +60,14 @@ .encoding-selector, .license-selector, - .gitignore-selector { + .gitignore-selector, + .gitlab-ci-yml-selector { display: inline-block; vertical-align: top; font-family: $regular_font; } - .gitignore-selector, .license-selector { + .gitignore-selector, .license-selector, .gitlab-ci-yml-selector { .dropdown { line-height: 21px; } @@ -76,4 +77,10 @@ width: 220px; } } + + .gitlab-ci-yml-selector { + .dropdown-menu-toggle { + width: 250px; + } + } } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 6c36f603daf..a2145956eb5 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -54,6 +54,10 @@ } } + code { + white-space: pre-wrap; + } + pre { border: none; background: #f9f9f9; diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 4a95b7b852e..00ab42bec5c 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -57,4 +57,12 @@ .documentation { padding: 7px; + + // Border around images in the help pages. + img:not(.emoji) { + border: 1px solid $table-border-gray; + padding: 5px; + margin: 5px; + max-height: calc(100vh - 100px); + } } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 687117233f6..542fa244689 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -4,6 +4,14 @@ margin-right: 1px; } } + + // Border around images in issue and MR descriptions. + .description img:not(.emoji) { + border: 1px solid $table-border-gray; + padding: 5px; + margin: 5px; + max-height: calc(100vh - 100px); + } } .issuable-filter-count { diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 046c38aba44..47bfd144930 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -6,6 +6,7 @@ height: 30px; display: inline-block; margin-right: 10px; + margin-bottom: 10px; } &.suggest-colors-dropdown { @@ -50,11 +51,10 @@ .label-row { .label-name { - display: block; + display: inline-block; margin-bottom: 10px; @media (min-width: $screen-sm-min) { - display: inline-block; width: 200px; margin-bottom: 0; } @@ -63,6 +63,7 @@ .label-description { display: block; margin-bottom: 10px; + margin-left: 50px; @media (min-width: $screen-sm-min) { display: inline-block; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index e67271adfb1..aca82f7f7bf 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -119,7 +119,12 @@ margin-bottom: 0; } - @media (max-width: $screen-sm-max) { + .btn-grouped { + margin-left: 0; + margin-right: 7px; + } + + @media (max-width: $screen-xs-max) { h4 { font-size: 15px; } @@ -131,10 +136,14 @@ .btn, .btn-group, .accept-action { - width: 100%; margin-bottom: 4px; } + .accept-action { + width: 100%; + text-align: center; + } + .accept-control { width: 100%; text-align: center; @@ -284,7 +293,7 @@ margin-bottom: 0; } - @media (min-width: $screen-sm-min) { + @media (min-width: $screen-xs-min) { float: left; width: 50%; margin-bottom: 0; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 577dddae741..3784010348a 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -179,6 +179,10 @@ border-top: 1px solid $border-color; } +.md-helper { + padding-top: 10px; +} + .toolbar-button { padding: 0; background: none; @@ -219,3 +223,16 @@ float: left; } } + +.note-form-actions { + @media (max-width: $screen-xs-max) { + .btn { + float: none; + width: 100%; + + &:not(:last-child) { + margin-bottom: 10px; + } + } + } +} diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 35d728aec83..ee7c98f805b 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -84,24 +84,14 @@ ul.notes { word-wrap: break-word; @include md-typography; + // Reset ul style types since we're nested inside a ul already + @include bulleted-list; + // On diffs code should wrap nicely and not overflow code { white-space: pre-wrap; } - // Reset ul style types since we're nested inside a ul already - & > ul { - list-style-type: disc; - - ul { - list-style-type: circle; - - ul { - list-style-type: square; - } - } - } - ul.task-list { ul:not(.task-list) { padding-left: 1.3em; @@ -117,6 +107,14 @@ ul.notes { code { word-break: keep-all; } + + // Border around images in issue and MR comments. + img:not(.emoji) { + border: 1px solid $table-border-gray; + padding: 5px; + margin: 5px 0; + max-height: calc(100vh - 100px); + } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index f138a2f5387..d3e59d7fdb9 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -101,7 +101,8 @@ .notifications-btn { - .fa-bell { + .fa-bell, + .fa-spinner { margin-right: 6px; } @@ -373,7 +374,7 @@ a.deploy-project-label { .project-stats { margin-top: $gl-padding; margin-bottom: 0; - padding: 16px 0; + padding: 0; background-color: $white-light; font-size: 0; @@ -382,13 +383,14 @@ a.deploy-project-label { } .nav li { - display: inline; + display: inline-block; + margin: 16px 0; + margin-right: 16px; } .nav > li > a { background-color: transparent; - margin-right: 12px; - padding: 0 10px; + padding: 5px 10px; font-size: 15px; color: $notes-light-color; } @@ -402,12 +404,17 @@ a.deploy-project-label { font-size: 17px; } - li.missing a { - color: #5a6069; - border: 1px dashed #dce0e5; + li.missing { + border: 1px dashed $border-gray-light; + border-radius: $border-radius-default; + + a { + color: $notes-light-color; + display: block; + } &:hover { - background-color: #f0f2f5; + background-color: $gray-normal; } } @@ -616,3 +623,9 @@ pre.light-well { color: $gl-text-green; } } + +.project-refs-form { + .dropdown-menu { + width: 300px; + } +} diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss index 85a0304196c..69288b31cc4 100644 --- a/app/assets/stylesheets/pages/stat_graph.scss +++ b/app/assets/stylesheets/pages/stat_graph.scss @@ -14,24 +14,38 @@ font-size: 10px; } +#contributors-master { + @include make-md-column(12); + + svg { + width: 100%; + } +} + #contributors { .contributors-list { margin: 0 0 10px; list-style: none; padding: 0; + + svg { + width: 100%; + } } .person { - &:nth-child(even) { - float: right; - } - float: left; + @include make-md-column(6); margin-top: 10px; + + @media (max-width: $screen-sm-min) { + width: 100%; + } } .person .spark { display: block; background: #f3f3f3; + width: 100%; } .person .area-contributor { diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index afc00a68572..cf16d070cfe 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -62,6 +62,10 @@ } } + code { + white-space: pre-wrap; + } + pre { border: none; background: #f9f9f9; diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb index 26cf74e4849..4b0ec54b3f4 100644 --- a/app/controllers/admin/appearances_controller.rb +++ b/app/controllers/admin/appearances_controller.rb @@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController end def preview + render 'preview', layout: 'devise' end def create diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb index d25619d94e0..bf20c5305a7 100644 --- a/app/controllers/admin/runner_projects_controller.rb +++ b/app/controllers/admin/runner_projects_controller.rb @@ -1,15 +1,14 @@ class Admin::RunnerProjectsController < Admin::ApplicationController before_action :project, only: [:create] - def index - @runner_projects = project.runner_projects.all - @runner_project = project.runner_projects.new - end - def create @runner = Ci::Runner.find(params[:runner_project][:runner_id]) - if @runner.assign_to(@project, current_user) + return head(403) if @runner.is_shared? || @runner.locked? + + runner_project = @runner.assign_to(@project, current_user) + + if runner_project.persisted? redirect_to admin_runner_path(@runner) else redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project' diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index dd1bc6f5d52..9cc31620d9f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base render_404 end + rescue_from Gitlab::Access::AccessDeniedError do |exception| + render_403 + end + def redirect_back_or_default(default: root_path, options: {}) redirect_to request.referer.present? ? :back : default, options end diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index a24273fad0b..52dc396af6a 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -21,29 +21,18 @@ module MembershipActions def leave @member = membershipable.members.find_by(user_id: current_user) - return render_403 unless @member + Members::DestroyService.new(@member, current_user).execute source_type = @member.real_source_type.humanize(capitalize: false) - - if can?(current_user, action_member_permission(:destroy, @member), @member) - notice = - if @member.request? - "Your access request to the #{source_type} has been withdrawn." - else - "You left the \"#{@member.source.human_name}\" #{source_type}." - end - @member.destroy - - redirect_to [:dashboard, @member.real_source_type.tableize], notice: notice - else - if cannot_leave? - alert = "You can not leave the \"#{@member.source.human_name}\" #{source_type}." - alert << " Transfer or delete the #{source_type}." - redirect_to polymorphic_url(membershipable), alert: alert + notice = + if @member.request? + "Your access request to the #{source_type} has been withdrawn." else - render_403 + "You left the \"#{@member.source.human_name}\" #{source_type}." end - end + redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize] + + redirect_to redirect_path, notice: notice end protected @@ -51,8 +40,4 @@ module MembershipActions def membershipable raise NotImplementedError end - - def cannot_leave? - raise NotImplementedError - end end diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb index 71ba6153021..de6bc689bb7 100644 --- a/app/controllers/dashboard/groups_controller.rb +++ b/app/controllers/dashboard/groups_controller.rb @@ -1,5 +1,5 @@ class Dashboard::GroupsController < Dashboard::ApplicationController def index - @group_members = current_user.group_members.page(params[:page]) + @group_members = current_user.group_members.includes(:source).page(params[:page]) end end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index d0f2e2949f0..2c49fe3833e 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -36,9 +36,7 @@ class Groups::GroupMembersController < Groups::ApplicationController def destroy @group_member = @group.group_members.find(params[:id]) - return render_403 unless can?(current_user, :destroy_group_member, @group_member) - - @group_member.destroy + Members::DestroyService.new(@group_member, current_user).execute respond_to do |format| format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } @@ -68,8 +66,4 @@ class Groups::GroupMembersController < Groups::ApplicationController # MembershipActions concern alias_method :membershipable, :group - - def cannot_leave? - @group.last_owner?(current_user) - end end diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index f99aa490d3e..513348c39af 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -12,9 +12,13 @@ class Import::GitlabProjectsController < Import::BaseController return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) end + imported_file = project_params[:file].path + "-import" + + FileUtils.copy_entry(project_params[:file].path, imported_file) + @project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id], current_user, - File.expand_path(project_params[:file].path), + File.expand_path(imported_file), project_params[:path]).execute if @project.saved? diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 776ba92c9ab..996909a28c6 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController end def require_branch_head - unless @repository.branch_names.include?(@ref) + unless @repository.branch_exists?(@ref) redirect_to( namespace_project_tree_path(@project.namespace, @project, @ref), notice: "This action is not allowed unless you are on a branch" diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index cd8b2911674..7599fec3cdf 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -16,6 +16,7 @@ class Projects::BlobController < Projects::ApplicationController before_action :from_merge_request, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update] before_action :editor_variables, except: [:show, :preview, :diff] + before_action :validate_diff_params, only: :diff def new commit unless @repository.empty? @@ -146,4 +147,10 @@ class Projects::BlobController < Projects::ApplicationController file_content_encoding: params[:encoding] } end + + def validate_diff_params + if [:since, :to, :offset].any? { |key| params[key].blank? } + render nothing: true + end + end end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 6751737d15e..d162a5a3165 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -18,9 +18,16 @@ class Projects::CommitController < Projects::ApplicationController apply_diff_view_cookie! @grouped_diff_notes = commit.notes.grouped_diff_notes + @notes = commit.notes.non_diff_notes.fresh + + Banzai::NoteRenderer.render( + @grouped_diff_notes.values.flatten + @notes, + @project, + current_user, + ) @note = @project.build_commit_note(commit) - @notes = commit.notes.non_diff_notes.fresh + @noteable = @commit @comments_target = { noteable_type: 'Commit', diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 4e2d3bebb2e..8b8df680739 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -62,8 +62,12 @@ class Projects::IssuesController < Projects::ApplicationController end def show + raw_notes = @issue.notes_with_associations.fresh + + @notes = Banzai::NoteRenderer. + render(raw_notes, @project, current_user, @path, @project_wiki, @ref) + @note = @project.notes.new(noteable: @issue) - @notes = @issue.notes.with_associations.fresh @noteable = @issue respond_to do |format| @@ -111,6 +115,7 @@ class Projects::IssuesController < Projects::ApplicationController render :edit end end + format.json do render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 851822d805a..39c8ba40ca2 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -85,6 +85,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController @grouped_diff_notes = @merge_request.notes.grouped_diff_notes + Banzai::NoteRenderer.render( + @grouped_diff_notes.values.flatten, + @project, + current_user, + @path, + @project_wiki, + @ref + ) + respond_to do |format| format.html format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } } @@ -190,7 +199,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge return access_denied! unless @merge_request.can_be_merged_by?(current_user) - unless @merge_request.mergeable? + # Disable the CI check if merge_when_build_succeeds is enabled since we have + # to wait until CI completes to know + unless @merge_request.mergeable?(skip_ci_check: merge_when_build_succeeds_active?) @status = :failed return end @@ -204,8 +215,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) - if params[:merge_when_build_succeeds].present? - if @merge_request.pipeline && @merge_request.pipeline.active? + if params[:merge_when_build_succeeds].present? + unless @merge_request.pipeline + @status = :failed + return + end + + if @merge_request.pipeline.active? MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) .execute(@merge_request) @status = :merge_when_build_succeeds @@ -320,8 +336,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_show_vars # Build a note object for comment form @note = @project.notes.new(noteable: @merge_request) - @notes = @merge_request.mr_and_commit_notes.inc_author.fresh - @discussions = @notes.discussions + + @discussions = @merge_request.mr_and_commit_notes. + inc_author_project_award_emoji. + fresh. + discussions + + @notes = Banzai::NoteRenderer.render( + @discussions.flatten, + @project, + current_user, + @path, + @project_wiki, + @ref + ) + @noteable = @merge_request # Get commits from repository @@ -368,4 +397,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController def ensure_ref_fetched @merge_request.ensure_ref_fetched end + + def merge_when_build_succeeds_active? + params[:merge_when_build_succeeds].present? && + @merge_request.pipeline && @merge_request.pipeline.active? + end end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 836f79ff080..e14fe26dde7 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -24,6 +24,10 @@ class Projects::NotesController < Projects::ApplicationController def create @note = Notes::CreateService.new(project, current_user, note_params).execute + if @note.is_a?(Note) + Banzai::NoteRenderer.render([@note], @project, current_user) + end + respond_to do |format| format.json { render json: note_json(@note) } format.html { redirect_back_or_default } @@ -33,6 +37,10 @@ class Projects::NotesController < Projects::ApplicationController def update @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) + if @note.is_a?(Note) + Banzai::NoteRenderer.render([@note], @project, current_user) + end + respond_to do |format| format.json { render json: note_json(@note) } format.html { redirect_back_or_default } @@ -118,6 +126,8 @@ class Projects::NotesController < Projects::ApplicationController name: note.name } elsif note.valid? + Banzai::NoteRenderer.render([note], @project, current_user) + { valid: true, id: note.id, diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 127bd1a4318..487963fdcd7 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController end def commit - @commit ||= @pipeline.commit_data + @commit ||= @pipeline.commit end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 35d067cd029..6ba32d33403 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -50,9 +50,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController def destroy @project_member = @project.project_members.find(params[:id]) - return render_403 unless can?(current_user, :destroy_project_member, @project_member) - - @project_member.destroy + Members::DestroyService.new(@project_member, current_user).execute respond_to do |format| format.html do @@ -98,8 +96,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController # MembershipActions concern alias_method :membershipable, :project - - def cannot_leave? - current_user == @project.owner - end end diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb index bedeb4a295c..dc1a18f8d42 100644 --- a/app/controllers/projects/runner_projects_controller.rb +++ b/app/controllers/projects/runner_projects_controller.rb @@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController def create @runner = Ci::Runner.find(params[:runner_project][:runner_id]) + return head(403) if @runner.is_shared? || @runner.locked? return head(403) unless current_user.ci_authorized_runners.include?(@runner) path = runners_path(project) + runner_project = @runner.assign_to(project, current_user) - if @runner.assign_to(project, current_user) + if runner_project.persisted? redirect_to path else redirect_to path, alert: 'Failed adding runner to project' diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 0b4fa572501..53c36635efe 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController layout 'project_settings' def index - @runners = project.runners.ordered - @specific_runners = current_user.ci_authorized_runners. - where.not(id: project.runners). - ordered.page(params[:page]).per(20) + @project_runners = project.runners.ordered + @assignable_runners = current_user.ci_authorized_runners. + assignable_for(project).ordered.page(params[:page]).per(20) @shared_runners = Ci::Runner.shared.active @shared_runners_count = @shared_runners.count(:all) end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 8044c637825..2b1f50fd01e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,7 +1,7 @@ class ProjectsController < Projects::ApplicationController include ExtractsPath - before_action :authenticate_user!, except: [:show, :activity] + before_action :authenticate_user!, except: [:show, :activity, :refs] before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? @@ -251,6 +251,24 @@ class ProjectsController < Projects::ApplicationController } end + def refs + options = { + 'Branches' => @repository.branch_names, + } + + unless @repository.tag_count.zero? + options['Tags'] = VersionSorter.rsort(@repository.tag_names) + end + + # If reference is commit id - we should add it to branch/tag selectbox + ref = Addressable::URI.unescape(params[:ref]) + if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/ + options['Commits'] = [ref] + end + + render json: options.to_json + end + private def determine_layout @@ -285,8 +303,14 @@ class ProjectsController < Projects::ApplicationController project.repository_exists? && !project.empty_repo? end - # Override get_id from ExtractsPath, which returns the branch and file path + # Override extract_ref from ExtractsPath, which returns the branch and file path # for the blob/tree, which in this case is just the root of the default branch. + # This way we avoid to access the repository.ref_names. + def extract_ref(_id) + [get_id, ''] + end + + # Override get_id from ExtractsPath in this case is just the root of the default branch. def get_id project.repository.root_ref end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 439b015b3b8..62d13a4b4f3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -101,22 +101,6 @@ module ApplicationHelper 'Never' end - def grouped_options_refs - repository = @project.repository - - options = [ - ['Branches', repository.branch_names], - ['Tags', VersionSorter.rsort(repository.tag_names)] - ] - - # If reference is commit id - we should add it to branch/tag selectbox - if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/ - options << ['Commit', [@ref]] - end - - grouped_options_for_select(options, @ref || @project.default_branch) - end - # Define whenever show last push event # with suggestion to create MR def show_last_push_widget?(event) @@ -132,7 +116,7 @@ module ApplicationHelper return false if project.merge_requests.where(source_branch: event.branch_name).opened.any? # Skip if user removed branch right after that - return false unless project.repository.branch_names.include?(event.branch_name) + return false unless project.repository.branch_exists?(event.branch_name) true end @@ -213,7 +197,7 @@ module ApplicationHelper def render_markup(file_name, file_content) if gitlab_markdown?(file_name) - Haml::Helpers.preserve(markdown(file_content)) + Hamlit::RailsHelpers.preserve(markdown(file_content)) elsif asciidoc?(file_name) asciidoc(file_content) elsif plain?(file_name) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 5b54b34070c..428a42266d0 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,10 +1,10 @@ module BlobHelper - def highlighter(blob_name, blob_content, nowrap: false) - Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap) + def highlighter(blob_name, blob_content, repository: nil, nowrap: false) + Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap, repository: repository) end - def highlight(blob_name, blob_content, nowrap: false, plain: false) - Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain) + def highlight(blob_name, blob_content, repository: nil, nowrap: false, plain: false) + Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain, repository: repository) end def no_highlight_files @@ -29,7 +29,7 @@ module BlobHelper if !on_top_of_branch?(project, ref) button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' } elsif can_edit_blob?(blob, project, ref) - link_to "Edit", edit_path, class: 'btn btn-file-option' + link_to "Edit", edit_path, class: 'btn btn-sm' elsif can?(current_user, :fork_project, project) continue_params = { to: edit_path, @@ -186,12 +186,16 @@ module BlobHelper end def gitignore_names - return @gitignore_names if defined?(@gitignore_names) + @gitignore_names ||= + Gitlab::Template::Gitignore.categories.keys.map do |k| + [k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }] + end.to_h + end - @gitignore_names = { - Global: Gitlab::Gitignore.global.map { |gitignore| { name: gitignore.name } }, - # Note that the key here doesn't cover it really - Languages: Gitlab::Gitignore.languages_frameworks.map{ |gitignore| { name: gitignore.name } } - } + def gitlab_ci_ymls + @gitlab_ci_ymls ||= + Gitlab::Template::GitlabCiYml.categories.keys.map do |k| + [k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }] + end.to_h end end diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index 3ee3fc74f0c..c533659b600 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -10,7 +10,7 @@ module BranchesHelper end def can_push_branch?(project, branch_name) - return false unless project.repository.branch_names.include?(branch_name) + return false unless project.repository.branch_exists?(branch_name) ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 067a00660aa..1a259656f31 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -50,8 +50,6 @@ module GitlabMarkdownHelper context[:project] ||= @project - text = Banzai.pre_process(text, context) - html = Banzai.render(text, context) context.merge!( @@ -185,4 +183,17 @@ module GitlabMarkdownHelper '' end end + + def markdown_toolbar_button(options = {}) + data = options[:data].merge({ container: "body" }) + content_tag :button, + type: "button", + class: "toolbar-btn js-md has-tooltip hidden-xs", + tabindex: -1, + data: data, + title: options[:title], + aria: { label: options[:title] } do + icon(options[:icon]) + end + end end diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb index 91dd91718dc..0e456214d37 100644 --- a/app/helpers/javascript_helper.rb +++ b/app/helpers/javascript_helper.rb @@ -1,7 +1,5 @@ module JavascriptHelper - def page_specific_javascripts(js = nil) - @page_specific_javascripts = js unless js.nil? - - @page_specific_javascripts + def page_specific_javascript_tag(js) + javascript_include_tag asset_path(js), { "data-turbolinks-track" => true } end end diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index 6dde2e9847d..45311690293 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -12,6 +12,11 @@ module Emails @member_id = member_id admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email) + # A project in a group can have no explicit owners/masters, in that case + # we fallbacks to the group's owners/masters. + if admins.empty? && member_source.respond_to?(:group) && member_source.group + admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email) + end mail(to: admins, subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}")) diff --git a/app/models/ability.rb b/app/models/ability.rb index 9c58b956007..f5950879ccb 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -196,7 +196,8 @@ class Ability @public_project_rules ||= project_guest_rules + [ :download_code, :fork_project, - :read_commit_status + :read_commit_status, + :read_pipeline ] end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index d618c84e983..2b0bec33131 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -300,18 +300,12 @@ module Ci project.valid_runners_token? token end - def can_be_served?(runner) - return false unless has_tags? || runner.run_untagged? - - (tag_list - runner.tag_list).empty? - end - def has_tags? tag_list.any? end def any_runners_online? - project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) } + project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) } end def stuck? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 5b264ecffc5..10324bf2257 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -37,22 +37,22 @@ module Ci end def git_author_name - commit_data.author_name if commit_data + commit.try(:author_name) end def git_author_email - commit_data.author_email if commit_data + commit.try(:author_email) end def git_commit_message - commit_data.message if commit_data + commit.try(:message) end def short_sha Ci::Pipeline.truncate_sha(sha) end - def commit_data + def commit @commit ||= project.commit(sha) rescue nil @@ -163,13 +163,26 @@ module Ci end def skip_ci? - git_commit_message =~ /(\[ci skip\])/ if git_commit_message + git_commit_message =~ /\[(ci skip|skip ci)\]/i if git_commit_message end def environments builds.where.not(environment: nil).success.pluck(:environment).uniq end + # Manually set the notes for a Ci::Pipeline + # There is no ActiveRecord relation between Ci::Pipeline and notes + # as they are related to a commit sha. This method helps importing + # them using the +Gitlab::ImportExport::RelationFactory+ class. + def notes=(notes) + notes.each do |note| + note[:id] = nil + note[:commit_id] = sha + note[:noteable_id] = self['id'] + note.save! + end + end + def notes Note.for_commit_id(sha) end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index adb65292208..b64ec79ec2b 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -4,7 +4,7 @@ module Ci LAST_CONTACT_TIME = 5.minutes.ago AVAILABLE_SCOPES = %w[specific shared active paused online] - FORM_EDITABLE = %i[description tag_list active run_untagged] + FORM_EDITABLE = %i[description tag_list active run_untagged locked] has_many :builds, class_name: 'Ci::Build' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' @@ -26,6 +26,13 @@ module Ci .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) end + scope :assignable_for, ->(project) do + # FIXME: That `to_sql` is needed to workaround a weird Rails bug. + # Without that, placeholders would miss one and couldn't match. + where(locked: false). + where.not("id IN (#{project.runners.select(:id).to_sql})").specific + end + validate :tag_constraints acts_as_taggable @@ -56,7 +63,7 @@ module Ci def assign_to(project, current_user = nil) self.is_shared = false if shared? self.save - project.runner_projects.create!(runner_id: self.id) + project.runner_projects.create(runner_id: self.id) end def display_name @@ -91,6 +98,10 @@ module Ci !shared? end + def can_pick?(build) + assignable_for?(build.project) && accepting_tags?(build) + end + def only_for?(project) projects == [project] end @@ -111,5 +122,13 @@ module Ci 'can not be empty when runner is not allowed to pick untagged jobs') end end + + def assignable_for?(project) + !locked? || projects.exists?(id: project.id) + end + + def accepting_tags?(build) + (run_untagged? || build.has_tags?) && (build.tag_list - tag_list).empty? + end end end diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index f8d5d4486fd..c9c47ec7419 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -13,6 +13,7 @@ module Ci attr_encrypted :value, mode: :per_attribute_iv_and_salt, + insecure_mode: true, key: Gitlab::Application.secrets.db_key_base, algorithm: 'aes-256-cbc' end diff --git a/app/models/commit.rb b/app/models/commit.rb index d69d518fadd..174ccbaea6c 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -271,6 +271,32 @@ class Commit merged_merge_request ? 'merge request' : 'commit' end + # Get the URI type of the given path + # + # Used to build URLs to files in the repository in GFM. + # + # path - String path to check + # + # Examples: + # + # uri_type('doc/README.md') # => :blob + # uri_type('doc/logo.png') # => :raw + # uri_type('doc/api') # => :tree + # uri_type('not/found') # => :nil + # + # Returns a symbol + def uri_type(path) + entry = @raw.tree.path(path) + if entry[:type] == :blob + blob = Gitlab::Git::Blob.new(name: entry[:name]) + blob.image? ? :raw : :blob + else + entry[:type] + end + rescue Rugged::TreeError + nil + end + private def repo_changes diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index ab13db4b297..e437e3417a8 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -8,6 +8,8 @@ class CommitStatus < ActiveRecord::Base belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true belongs_to :user + delegate :commit, to: :pipeline + validates :pipeline, presence: true, unless: :importing? validates_presence_of :name diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index 539c7c31e30..06beff177b1 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -2,10 +2,11 @@ module Awardable extend ActiveSupport::Concern included do - has_many :award_emoji, as: :awardable, dependent: :destroy + has_many :award_emoji, -> { includes(:user) }, as: :awardable, dependent: :destroy if self < Participable - participant :award_emoji_with_associations + # By default we always load award_emoji user association + participant :award_emoji end end @@ -34,12 +35,9 @@ module Awardable end end - def award_emoji_with_associations - award_emoji.includes(:user) - end - def grouped_awards(with_thumbs: true) - awards = award_emoji_with_associations.group_by(&:name) + # By default we always load award_emoji user association + awards = award_emoji.group_by(&:name) if with_thumbs awards[AwardEmoji::UPVOTE_NAME] ||= [] diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 0ccd3474b81..d6f55885dd6 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -19,9 +19,14 @@ module Issuable belongs_to :milestone has_many :notes, as: :noteable, dependent: :destroy do def authors_loaded? - # We check first if we're loaded to not load unnecesarily. + # We check first if we're loaded to not load unnecessarily. loaded? && to_a.all? { |note| note.association(:author).loaded? } end + + def award_emojis_loaded? + # We check first if we're loaded to not load unnecessarily. + loaded? && to_a.all? { |note| note.association(:award_emoji).loaded? } + end end has_many :label_links, as: :target, dependent: :destroy has_many :labels, through: :label_links @@ -49,7 +54,7 @@ module Issuable scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) } scope :join_project, -> { joins(:project) } - scope :inc_notes_with_associations, -> { includes(notes: :author) } + scope :inc_notes_with_associations, -> { includes(notes: [ :project, :author, :award_emoji ]) } scope :references_project, -> { references(:project) } scope :non_archived, -> { join_project.where(projects: { archived: false }) } @@ -112,15 +117,18 @@ module Issuable end def sort(method, excluded_labels: []) - case method.to_s - when 'milestone_due_asc' then order_milestone_due_asc - when 'milestone_due_desc' then order_milestone_due_desc - when 'downvotes_desc' then order_downvotes_desc - when 'upvotes_desc' then order_upvotes_desc - when 'priority' then order_labels_priority(excluded_labels: excluded_labels) - else - order_by(method) - end + sorted = case method.to_s + when 'milestone_due_asc' then order_milestone_due_asc + when 'milestone_due_desc' then order_milestone_due_desc + when 'downvotes_desc' then order_downvotes_desc + when 'upvotes_desc' then order_upvotes_desc + when 'priority' then order_labels_priority(excluded_labels: excluded_labels) + else + order_by(method) + end + + # Break ties with the ID column for pagination + sorted.order(id: :desc) end def order_labels_priority(excluded_labels: []) @@ -257,7 +265,14 @@ module Issuable # already have their authors loaded (possibly because the scope # `inc_notes_with_associations` was used) and skip the inclusion if that's # the case. - notes.authors_loaded? ? notes : notes.includes(:author) + includes = [] + includes << :author unless notes.authors_loaded? + includes << :award_emoji unless notes.award_emojis_loaded? + if includes.any? + notes.includes(includes) + else + notes + end end def updated_tasks diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 9056722f45e..9822844357d 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -53,6 +53,16 @@ module Participable # # Returns an Array of User instances. def participants(current_user = nil) + @participants ||= Hash.new do |hash, user| + hash[user] = raw_participants(user) + end + + @participants[current_user] + end + + private + + def raw_participants(current_user = nil) current_user ||= author ext = Gitlab::ReferenceExtractor.new(project, current_user) participants = Set.new diff --git a/app/models/event.rb b/app/models/event.rb index 716039fb54b..d7d23c7ae6d 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -315,7 +315,7 @@ class Event < ActiveRecord::Base def body? if push? - push_with_commits? + push_with_commits? || rm_ref? elsif note? true else diff --git a/app/models/group.rb b/app/models/group.rb index e66e04371b2..c70c719e338 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -11,7 +11,7 @@ class Group < Namespace has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members has_many :owners, - -> { where(members: { access_level: Gitlab::Access::OWNER }) }, + -> { where(members: { requested_at: nil, access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :user diff --git a/app/models/key.rb b/app/models/key.rb index 0532e84f47d..b9bc38a0436 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -9,7 +9,7 @@ class Key < ActiveRecord::Base before_validation :strip_white_space, :generate_fingerprint validates :title, presence: true, length: { within: 0..255 } - validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true + validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ } validates :key, format: { without: /\n|\r/, message: 'should be a single line' } validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' } diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 95fd510eb3a..33d2a69ebaf 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -20,7 +20,7 @@ class LegacyDiffNote < Note end def discussion_id - @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code, active?) + @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code) end def diff_file_hash diff --git a/app/models/member.rb b/app/models/member.rb index 4ee3f1bb5c2..c74a16367db 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -48,7 +48,6 @@ class Member < ActiveRecord::Base after_create :post_create_hook, unless: [:pending?, :importing?] after_update :post_update_hook, unless: [:pending?, :importing?] after_destroy :post_destroy_hook, unless: :pending? - after_destroy :post_decline_request, if: :request? delegate :name, :username, :email, to: :user, prefix: true @@ -188,7 +187,7 @@ class Member < ActiveRecord::Base end def send_request - # override in subclass + notification_service.new_access_request(self) end def post_create_hook @@ -215,10 +214,6 @@ class Member < ActiveRecord::Base post_create_hook end - def post_decline_request - # override in subclass - end - def system_hook_service SystemHooksService.new end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 363db877968..2f13d339c89 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -33,12 +33,6 @@ class GroupMember < Member super end - def send_request - notification_service.new_group_access_request(self) - - super - end - def post_create_hook notification_service.new_group_member(self) @@ -64,10 +58,4 @@ class GroupMember < Member super end - - def post_decline_request - notification_service.decline_group_access_request(self) - - super - end end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 250ee04fd1d..e9d3a82ba15 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -111,12 +111,6 @@ class ProjectMember < Member super end - def send_request - notification_service.new_project_access_request(self) - - super - end - def post_create_hook unless owner? event_service.join_project(self.project, self.user) @@ -152,12 +146,6 @@ class ProjectMember < Member super end - def post_decline_request - notification_service.decline_project_access_request(self) - - super - end - def event_service EventCreateService.new end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 36bc98bdb1e..f5c5b7c1306 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -264,19 +264,19 @@ class MergeRequest < ActiveRecord::Base self.title.sub(WIP_REGEX, "") end - def mergeable? - return false unless mergeable_state? + def mergeable?(skip_ci_check: false) + return false unless mergeable_state?(skip_ci_check: skip_ci_check) check_if_can_be_merged can_be_merged? end - def mergeable_state? + def mergeable_state?(skip_ci_check: false) return false unless open? return false if work_in_progress? return false if broken? - return false unless mergeable_ci_state? + return false unless skip_ci_check || mergeable_ci_state? true end diff --git a/app/models/note.rb b/app/models/note.rb index 8d164647550..8db500a5219 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -6,6 +6,10 @@ class Note < ActiveRecord::Base include Awardable include Importable + # Attribute containing rendered and redacted Markdown as generated by + # Banzai::ObjectRenderer. + attr_accessor :note_html + default_value_for :system, false attr_mentionable :note, pipeline: :note @@ -49,11 +53,13 @@ class Note < ActiveRecord::Base scope :fresh, ->{ order(created_at: :asc, id: :asc) } scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author, ->{ includes(:author) } + scope :inc_author_project_award_emoji, ->{ includes(:project, :author, :award_emoji) } scope :legacy_diff_notes, ->{ where(type: 'LegacyDiffNote') } scope :non_diff_notes, ->{ where(type: ['Note', nil]) } scope :with_associations, -> do + # FYI noteable cannot be loaded for LegacyDiffNote for commits includes(:author, :noteable, :updated_by, project: [:project_members, { group: [:group_members] }]) end diff --git a/app/models/project.rb b/app/models/project.rb index ca3bc04e2dd..96837364423 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -163,6 +163,7 @@ class Project < ActiveRecord::Base validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validate :visibility_level_allowed_by_group validate :visibility_level_allowed_as_fork + validate :check_wiki_path_conflict add_authentication_token_field :runners_token before_save :ensure_runners_token @@ -539,6 +540,16 @@ class Project < ActiveRecord::Base self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.") end + def check_wiki_path_conflict + return if path.blank? + + path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki" + + if Project.where(namespace_id: namespace_id, path: path_to_check).exists? + errors.add(:name, 'has already been taken') + end + end + def to_param path end diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index ca8a9b4217b..331123a5a5b 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -7,6 +7,7 @@ class ProjectImportData < ActiveRecord::Base marshal: true, encode: true, mode: :per_attribute_iv_and_salt, + insecure_mode: true, algorithm: 'aes-256-cbc' serialize :data, JSON diff --git a/app/models/repository.rb b/app/models/repository.rb index bbd7682d8e7..2a6a3b086c2 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -130,7 +130,7 @@ class Repository end def find_tag(name) - raw_repository.tags.find { |tag| tag.name == name } + tags.find { |tag| tag.name == name } end def add_branch(user, branch_name, target) @@ -191,8 +191,12 @@ class Repository end end + def ref_names + branch_names + tag_names + end + def branch_names - cache.fetch(:branch_names) { branches.map(&:name) } + @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) } end def branch_exists?(branch_name) @@ -267,6 +271,7 @@ class Repository def expire_branches_cache cache.expire(:branch_names) + @branch_names = nil @local_branches = nil end @@ -332,10 +337,6 @@ class Repository @lookup_cache ||= {} end - def expire_branch_names - cache.expire(:branch_names) - end - def expire_avatar_cache(branch_name = nil, revision = nil) # Avatars are pulled from the default branch, thus if somebody pushes to a # different branch there's no need to expire anything. @@ -977,6 +978,10 @@ class Repository raw_repository.ls_files(actual_ref) end + def gitattribute(path, name) + raw_repository.attributes(path)[name] + end + def copy_gitattributes(ref) actual_ref = ref || root_ref begin diff --git a/app/models/snippet.rb b/app/models/snippet.rb index f8034cb5e6b..5ec933601ac 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -20,6 +20,7 @@ class Snippet < ActiveRecord::Base length: { within: 0..255 }, format: { with: Gitlab::Regex.file_name_regex, message: Gitlab::Regex.file_name_regex_message } + validates :content, presence: true validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } @@ -81,6 +82,11 @@ class Snippet < ActiveRecord::Base 0 end + # alias for compatibility with blobs and highlighting + def path + file_name + end + def name file_name end @@ -135,7 +141,16 @@ class Snippet < ActiveRecord::Base end def accessible_to(user) - where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user) + return are_public unless user.present? + return all if user.admin? + + where( + 'visibility_level IN (:visibility_levels) + OR author_id = :author_id + OR project_id IN (:project_ids)', + visibility_levels: [Snippet::PUBLIC, Snippet::INTERNAL], + author_id: user.id, + project_ids: user.authorized_projects.select(:id)) end end end diff --git a/app/models/user.rb b/app/models/user.rb index 2e458329cb9..767d6366c79 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -25,6 +25,7 @@ class User < ActiveRecord::Base attr_encrypted :otp_secret, key: Gitlab::Application.config.secret_key_base, mode: :per_attribute_iv_and_salt, + insecure_mode: true, algorithm: 'aes-256-cbc' devise :two_factor_authenticatable, @@ -57,7 +58,7 @@ class User < ActiveRecord::Base # Groups has_many :members, dependent: :destroy - has_many :group_members, dependent: :destroy, source: 'GroupMember' + has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' has_many :groups, through: :group_members has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group @@ -65,7 +66,7 @@ class User < ActiveRecord::Base # Projects has_many :groups_projects, through: :groups, source: :projects has_many :personal_projects, through: :namespace, source: :projects - has_many :project_members, dependent: :destroy, class_name: 'ProjectMember' + has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, class_name: 'ProjectMember' has_many :projects, through: :project_members has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :users_star_projects, dependent: :destroy @@ -308,7 +309,7 @@ class User < ActiveRecord::Base def generate_password if self.force_random_password - self.password = self.password_confirmation = Devise.friendly_token.first(8) + self.password = self.password_confirmation = Devise.friendly_token.first(Devise.password_length.min) end end @@ -487,9 +488,8 @@ class User < ActiveRecord::Base events.recent.find do |event| project = Project.find_by_id(event.project_id) next unless project - repo = project.repository - if repo.branch_names.include?(event.branch_name) + if project.repository.branch_exists?(event.branch_name) merge_requests = MergeRequest.where("created_at >= ?", event.created_at). where(source_project_id: project.id, source_branch: event.branch_name) diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb index f0ed09a629a..9a187f5d694 100644 --- a/app/services/ci/register_build_service.rb +++ b/app/services/ci/register_build_service.rb @@ -21,7 +21,7 @@ module Ci end build = builds.find do |build| - build.can_be_served?(current_runner) + current_runner.can_pick?(build) end if build diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb new file mode 100644 index 00000000000..15358f80208 --- /dev/null +++ b/app/services/members/destroy_service.rb @@ -0,0 +1,21 @@ +module Members + class DestroyService < BaseService + attr_accessor :member, :current_user + + def initialize(member, user) + @member, @current_user = member, user + end + + def execute + unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member) + raise Gitlab::Access::AccessDeniedError + end + + member.destroy + + if member.request? && member.user != current_user + notification_service.decline_access_request(member) + end + end + end +end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 1b48899bb0a..7fe57747265 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -83,7 +83,7 @@ module MergeRequests closes_issue = "Closes ##{iid}" if merge_request.description.present? - merge_request.description << closes_issue.prepend("\n") + merge_request.description += closes_issue.prepend("\n") else merge_request.description = closes_issue end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 19832a19b2b..590350a11e5 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -181,15 +181,16 @@ class NotificationService end end - # Project access request - def new_project_access_request(project_member) - mailer.member_access_requested_email(project_member.real_source_type, project_member.id).deliver_later + # Members + def new_access_request(member) + mailer.member_access_requested_email(member.real_source_type, member.id).deliver_later end - def decline_project_access_request(project_member) - mailer.member_access_denied_email(project_member.real_source_type, project_member.project.id, project_member.user.id).deliver_later + def decline_access_request(member) + mailer.member_access_denied_email(member.real_source_type, member.source_id, member.user_id).deliver_later end + # Project invite def invite_project_member(project_member, token) mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later end @@ -216,15 +217,7 @@ class NotificationService mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later end - # Group access request - def new_group_access_request(group_member) - mailer.member_access_requested_email(group_member.real_source_type, group_member.id).deliver_later - end - - def decline_group_access_request(group_member) - mailer.member_access_denied_email(group_member.real_source_type, group_member.group.id, group_member.user.id).deliver_later - end - + # Group invite def invite_group_member(group_member, token) mailer.member_invited_email(group_member.real_source_type, group_member.id, token).deliver_later end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 540bf54b920..239bd17a035 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -159,8 +159,9 @@ class TodoService def create_todos(users, attributes) Array(users).map do |user| next if pending_todos(user, attributes).exists? - Todo.create(attributes.merge(user_id: user.id)) + todo = Todo.create(attributes.merge(user_id: user.id)) user.update_todos_count_cache + todo end end diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index d88f3ad314d..dc083e50178 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -46,7 +46,7 @@ Maximum file size is 1MB. Pages are optimized for a 72x72 px header logo .form-actions - = f.submit 'Save', class: 'btn btn-save' + = f.submit 'Save', class: 'btn btn-save append-right-10' - if @appearance.persisted? = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank' diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml index dd4a64e80bc..6c51639b840 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview.html.haml @@ -1,29 +1,9 @@ - page_title "Preview | Appearance" -%h3.page-title - Appearance settings - Preview -%hr +.login-box + .login-heading + %h3 Existing user? Sign in + %form + = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email" + = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password" + = button_tag "Sign in", class: "btn-create btn" -.ui-box - .title - Sign-in page - %div - .login-page - .container - .content - .login-title - %h1= brand_title - %hr - .container - .content - .row - .col-sm-7 - .brand-image - = brand_image - .brand_text - = brand_text - .col-sm-4 - .login-box - %h3.page-title Sign in - = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email" - = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password" - = button_tag "Sign in", class: "btn-create btn" diff --git a/app/views/admin/appearances/show.html.haml b/app/views/admin/appearances/show.html.haml index 089e8e4cb7a..454b779842c 100644 --- a/app/views/admin/appearances/show.html.haml +++ b/app/views/admin/appearances/show.html.haml @@ -1,7 +1,9 @@ - page_title "Appearance" + %h3.page-title Appearance settings %p.light You can modify the look and feel of GitLab here +%hr = render 'form' diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml index e9c7ca9d5aa..ecc46d86afe 100644 --- a/app/views/admin/application_settings/show.html.haml +++ b/app/views/admin/application_settings/show.html.haml @@ -1,4 +1,5 @@ - page_title "Settings" + %h3.page-title Settings %hr = render 'form' diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 5b8a0262ea0..50770465f07 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -88,28 +88,17 @@ = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr = button_tag 'Add users to group', class: "btn btn-create" + + = render 'shared/members/requests', membership_source: @group, members: @members.request + .panel.panel-default .panel-heading - %h3.panel-title - Members - %span.badge - #{@group.group_members.count} - %ul.well-list.group-users-list - - @members.each do |member| - - user = member.user - %li{class: dom_class(member), id: (dom_id(user) if user)} - .list-item-name - - if user - %strong - = link_to user.name, admin_user_path(user) - - else - %strong - = member.invite_email - (invited) - %span.pull-right.light - = member.human_access - - if can?(current_user, :destroy_group_member, member) - = link_to group_group_member_path(@group, member), data: { confirm: remove_member_message(member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do - %i.fa.fa-minus.fa-inverse + %strong= @group.name + group members + %span.badge= @group.members.non_request.size + .pull-right + = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@group, :members]), class: "btn btn-xs" + %ul.well-list.group-users-list.content-list + = render partial: 'shared/members/member', collection: @members.non_request, as: :member, locals: { show_controls: false } .panel-footer - = paginate @members, param_name: 'members_page', theme: 'gitlab' + = paginate @members.non_request, param_name: 'members_page', theme: 'gitlab' diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 9e55a562e18..461d588415d 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -135,44 +135,27 @@ - if @group .panel.panel-default .panel-heading - %strong #{@group.name} - group members (#{@group.group_members.count}) + %strong= @group.name + group members + %span.badge= @group_members.non_request.size .pull-right = link_to admin_group_path(@group), class: 'btn btn-xs' do - %i.fa.fa-pencil-square-o - %ul.well-list - - @group_members.each do |member| - = render 'shared/members/member', member: member, show_controls: false + = icon('pencil-square-o', text: 'Manage Access') + %ul.well-list.content-list + = render partial: 'shared/members/member', collection: @group_members.non_request, as: :member, locals: { show_controls: false } .panel-footer - = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' + = paginate @group_members.non_request, param_name: 'group_members_page', theme: 'gitlab' + + = render 'shared/members/requests', membership_source: @project, members: @project_members.request .panel.panel-default .panel-heading - Project members - %small - (#{@project.users.count}) + %strong= @project.name + project members + %span.badge= @project.users.size .pull-right - = link_to namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-xs" do - %i.fa.fa-pencil-square-o - Manage Access - %ul.well-list.project_members - - @project_members.each do |project_member| - - user = project_member.user - %li.project_member - .list-item-name - - if user - %strong - = link_to user.name, admin_user_path(user) - - else - %strong - = project_member.invite_email - (invited) - .pull-right - - if project_member.owner? - %span.light Owner - - else - %span.light= project_member.human_access - = link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_member_message(project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do - %i.fa.fa-times + = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@project, :members]), class: "btn btn-xs" + %ul.well-list.project_members.content-list + = render partial: 'shared/members/member', collection: @project_members.non_request, as: :member, locals: { show_controls: false } .panel-footer - = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' + = paginate @project_members.non_request, param_name: 'project_members_page', theme: 'gitlab' diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index e049b40bfab..61abfc6ecbe 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -28,7 +28,7 @@ .col-md-6 %h4 Restrict projects for this runner - if @runner.projects.any? - %table.table + %table.table.assigned-projects %thead %tr %th Assigned projects @@ -44,7 +44,7 @@ .pull-right = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs' - %table.table + %table.table.unassigned-projects %thead %tr %th Project diff --git a/app/views/admin/users/groups.html.haml b/app/views/admin/users/groups.html.haml index b0a709a568a..8f6d13b881a 100644 --- a/app/views/admin/users/groups.html.haml +++ b/app/views/admin/users/groups.html.haml @@ -1,11 +1,12 @@ - page_title "Groups", @user.name, "Users" = render 'admin/users/head' -- if @user.group_members.present? +- group_members = @user.group_members.includes(:source) +- if group_members.any? .panel.panel-default .panel-heading Groups: %ul.well-list - - @user.group_members.each do |group_member| + - group_members.each do |group_member| - group = group_member.group %li.group_member %span{class: ("list-item-name" unless group_member.owner?)} diff --git a/app/views/ci/errors/show.haml b/app/views/ci/errors/show.haml deleted file mode 100644 index 2788112c835..00000000000 --- a/app/views/ci/errors/show.haml +++ /dev/null @@ -1,2 +0,0 @@ -%h3.error Error -= @error diff --git a/app/views/ci/shared/_guide.html.haml b/app/views/ci/shared/_guide.html.haml deleted file mode 100644 index 09e7e653521..00000000000 --- a/app/views/ci/shared/_guide.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -.bs-callout.help-callout - %h4 How to setup CI for this project - - %ol - %li - Add at least one runner to the project. - Go to #{link_to 'Runners page', runners_path(@project), target: :blank} for instructions. - %li - Put the .gitlab-ci.yml in the root of your repository. Examples can be found in - #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}. - You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} - %li - Return to this page and refresh it, it should show a new build. diff --git a/app/views/ci/shared/_no_runners.html.haml b/app/views/ci/shared/_no_runners.html.haml deleted file mode 100644 index f56c37d9b37..00000000000 --- a/app/views/ci/shared/_no_runners.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.alert.alert-danger - %p - Now you need Runners to process your builds. - %span - Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it - - diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index d35f332e1e0..f7abad54286 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -13,7 +13,7 @@ Explore Projects .nav-controls - = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| + = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2" = render 'shared/projects/dropdown' - if current_user.can_create_project? diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml index 97401a2e618..8b38b4c2bd4 100644 --- a/app/views/emojis/index.html.haml +++ b/app/views/emojis/index.html.haml @@ -1,6 +1,6 @@ .emoji-menu + = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control", placeholder: "Seach emojis" .emoji-menu-content - = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control" - Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis| %h5.emoji-menu-title = Gitlab::AwardEmoji::CATEGORIES[category] diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index dc4ff17e31a..ea54ef226ec 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -1,3 +1,5 @@ +- project = event.project + .event-title %span.author_name= link_to_author event %span.event_label.pushed #{event.action_name} #{event.ref_type} @@ -5,19 +7,18 @@ %strong= event.ref_name - else %strong - = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), title: h(event.target_title) + = link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title) at - = link_to_project event.project + = link_to_project project - if event.push_with_commits? - - project = event.project .event-body %ul.well-list.event_commits - few_commits = event.commits[0...2] - few_commits.each do |commit| = render "events/commit", commit: commit, project: project, event: event - - create_mr = event.new_ref? && create_mr_button?(event.project.default_branch, event.ref_name, event.project) + - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) - if event.commits_count > 1 %li.commits-stat - if event.commits_count > 2 @@ -27,18 +28,26 @@ - from = event.commit_from - from_label = truncate_sha(from) - else - - from = event.project.default_branch + - from = project.default_branch - from_label = from - = link_to namespace_project_compare_path(event.project.namespace, event.project, from: from, to: event.commit_to) do + = link_to namespace_project_compare_path(project.namespace, project, from: from, to: event.commit_to) do Compare #{from_label}...#{truncate_sha(event.commit_to)} - if create_mr %span{"data-user-is" => event.author_id, "data-display" => "inline"} or - = link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do + = link_to create_mr_path(project.default_branch, event.ref_name, project) do create a merge request - elsif create_mr %li.commits-stat{"data-user-is" => event.author_id} - = link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do + = link_to create_mr_path(project.default_branch, event.ref_name, project) do Create Merge Request +- elsif event.rm_ref? + - repository = project.repository + - last_commit = repository.commit(event.commit_from) + - if last_commit + .event-body + %ul.well-list.event_commits + = render "events/commit", commit: last_commit, project: project, event: event + diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index a36531e095a..d6acade84f1 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -17,8 +17,7 @@ .panel-heading %strong #{@group.name} group members - %small - (#{@members.total_count}) + %span.badge= @members.non_request.size .controls = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 62ebd69485c..aecefbc6e8f 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -33,7 +33,7 @@ = link_to "#shared", 'data-toggle' => 'tab' do Shared Projects .nav-controls - = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| + = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false = render 'shared/projects/dropdown' - if can? current_user, :create_projects, @group diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 01648047ce2..8cc0b59edeb 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -28,8 +28,12 @@ .key ⌘ shift p - else .key ctrl shift p - %td Toggle Markdown preview + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Edit last comment (when focused on an empty textarea) %tbody %tr %th diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index e0ed657919e..757de92d6d4 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -30,8 +30,8 @@ = javascript_include_tag "application" - - if page_specific_javascripts - = javascript_include_tag page_specific_javascripts, {"data-turbolinks-track" => true} + - if content_for?(:page_specific_javascripts) + = yield :page_specific_javascripts = csrf_meta_tags diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 199ab3c38c3..2234bf79c87 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -13,7 +13,7 @@ = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' .username = current_user.username - = link_to '#', class: "nav-header-btn text-center pin-nav-btn #{'is-active' if pinned_nav?} js-nav-pin", title: 'Pin/Unpin navigation' do + = link_to '#', class: "nav-header-btn text-center pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do %span.sr-only Toggle navigation pinning = icon('thumb-tack') - if defined?(nav) && nav diff --git a/app/views/layouts/ci/_info.html.haml b/app/views/layouts/ci/_info.html.haml deleted file mode 100644 index 24c68a6dbf5..00000000000 --- a/app/views/layouts/ci/_info.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -- if current_user && current_user.is_admin? && Ci::Runner.count.zero? - = render 'ci/shared/no_runners' diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml deleted file mode 100644 index 2e56d0ac6a3..00000000000 --- a/app/views/layouts/ci/_page.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -.page-with-sidebar{ class: page_sidebar_class } - = render "layouts/broadcast" - .sidebar-wrapper.nicescroll{ class: nav_sidebar_class } - - - if defined?(sidebar) && sidebar - = render "layouts/ci/#{sidebar}" - - elsif current_user - = render 'layouts/nav/dashboard' - .collapse-nav - = render partial: 'layouts/collapse_button' - - if current_user - = link_to current_user, class: 'sidebar-user', title: "Profile" do - = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' - .username - = current_user.username - .content-wrapper - = render "layouts/flash" - = render 'layouts/ci/info' - %div{ class: container_class } - .content - .clearfix - = yield diff --git a/app/views/layouts/ci/notify.html.haml b/app/views/layouts/ci/notify.html.haml deleted file mode 100644 index 270b206df5e..00000000000 --- a/app/views/layouts/ci/notify.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -%html{lang: "en"} - %head - %meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"} - %title - GitLab CI - - %body - = yield :header - - %table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"} - %tr - %td{align: "left", style: "margin: 0; padding: 10px;"} - = yield - %br - %tr - %td{align: "left", style: "margin: 0; padding: 10px;"} - %p{style: "font-size:small;color:#777"} - - if @project - You're receiving this notification because you are the one who triggered a build on the #{@project.name} project. diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index f02ac949699..0f264cd2e06 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -1,60 +1,40 @@ -%ul.nav-links.scrolling-tabs +.scrolling-tabs-container{ class: nav_control_class } + = render 'layouts/nav/admin_settings' .fade-left - = nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do - = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do - %span - Overview - = nav_link(controller: %w(background_jobs logs health_check)) do - = link_to admin_background_jobs_path, title: 'Monitoring' do - %span - Monitoring - = nav_link(controller: :deploy_keys) do - = link_to admin_deploy_keys_path, title: 'Deploy Keys' do - %span - Deploy Keys - = nav_link(controller: :broadcast_messages) do - = link_to admin_broadcast_messages_path, title: 'Messages' do - %span - Messages - = nav_link(controller: :hooks) do - = link_to admin_hooks_path, title: 'Hooks' do - %span - Hooks - - = nav_link(controller: :appearances) do - = link_to admin_appearances_path, title: 'Appearances' do - %span - Appearance - - = nav_link(controller: :applications) do - = link_to admin_applications_path, title: 'Applications' do - %span - Applications - - = nav_link(controller: :services) do - = link_to admin_application_settings_services_path, title: 'Service Templates' do - %span - Service Templates - - = nav_link(controller: :labels) do - = link_to admin_labels_path, title: 'Labels' do - %span - Labels + = icon('arrow-left') + .fade-right + = icon('arrow-right') + %ul.nav-links.scrolling-tabs + = nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do + = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do + %span + Overview + = nav_link(controller: %w(background_jobs logs health_check)) do + = link_to admin_background_jobs_path, title: 'Monitoring' do + %span + Monitoring + = nav_link(controller: :broadcast_messages) do + = link_to admin_broadcast_messages_path, title: 'Messages' do + %span + Messages + = nav_link(controller: :hooks) do + = link_to admin_hooks_path, title: 'Hooks' do + %span + System Hooks - = nav_link(controller: :abuse_reports) do - = link_to admin_abuse_reports_path, title: "Abuse Reports" do - %span - Abuse Reports - %span.badge.count= number_with_delimiter(AbuseReport.count(:all)) + = nav_link(controller: :applications) do + = link_to admin_applications_path, title: 'Applications' do + %span + Applications - - if askimet_enabled? - = nav_link(controller: :spam_logs) do - = link_to admin_spam_logs_path, title: "Spam Logs" do + = nav_link(controller: :abuse_reports) do + = link_to admin_abuse_reports_path, title: "Abuse Reports" do %span - Spam Logs + Abuse Reports + %span.badge.count= number_with_delimiter(AbuseReport.count(:all)) - = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do - = link_to admin_application_settings_path, title: 'Settings' do - %span - Settings - .fade-right + - if askimet_enabled? + = nav_link(controller: :spam_logs) do + = link_to admin_spam_logs_path, title: "Spam Logs" do + %span + Spam Logs diff --git a/app/views/layouts/nav/_admin_settings.html.haml b/app/views/layouts/nav/_admin_settings.html.haml new file mode 100644 index 00000000000..38e9b80d129 --- /dev/null +++ b/app/views/layouts/nav/_admin_settings.html.haml @@ -0,0 +1,31 @@ +.controls + .dropdown.admin-settings-dropdown + %a.dropdown-new.btn.btn-default{href: '#', 'data-toggle' => 'dropdown'} + = icon('cog') + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right + = nav_link(controller: :deploy_keys) do + = link_to admin_deploy_keys_path, title: 'Deploy Keys' do + %span + Deploy Keys + + = nav_link(controller: :services) do + = link_to admin_application_settings_services_path, title: 'Service Templates' do + %span + Service Templates + + = nav_link(controller: :labels) do + = link_to admin_labels_path, title: 'Labels' do + %span + Labels + + = nav_link(controller: :appearances) do + = link_to admin_appearances_path, title: 'Appearances' do + %span + Appearance + + %li.divider + = nav_link(controller: :application_settings) do + = link_to admin_application_settings_path, title: 'Settings' do + %span + Settings diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 66361a644dd..5d657a9ac84 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,8 +1,10 @@ -%div{ class: nav_control_class } +.scrolling-tabs-container{ class: nav_control_class } = render 'layouts/nav/group_settings' - + .fade-left + = icon('arrow-left') + .fade-right + = icon('arrow-right') %ul.nav-links.scrolling-tabs - .fade-left = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: 'Home' do %span @@ -31,4 +33,3 @@ = link_to group_group_members_path(@group), title: 'Members' do %span Members - .fade-right diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index dac46648b9f..3a24b09ab7e 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,16 +1,22 @@ - if current_user - - if access = @group.users.find_by(id: current_user.id) - .controls - .dropdown.group-settings-dropdown - %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} - = icon('cog') - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right - - if can?(current_user, :admin_group, @group) - = nav_link(path: 'groups#projects') do - = link_to projects_group_path(@group), title: 'Projects' do - Projects - %li.divider - %li - = link_to edit_group_path(@group) do - Edit Group + - can_edit = can?(current_user, :admin_group, @group) + - member = @group.members.non_request.find_by(user_id: current_user.id) + - can_leave = member && can?(current_user, :destroy_group_member, member) + + .controls + .dropdown.group-settings-dropdown + %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} + = icon('cog') + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right + = nav_link(path: 'groups#projects') do + = link_to 'Projects', projects_group_path(@group), title: 'Projects' + %li.divider + - if can_edit + %li + = link_to 'Edit Group', edit_group_path(@group) + - if can_leave + %li + = link_to polymorphic_path([:leave, @group, :members]), + data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do + Leave Group diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index bb6f14a6225..f37f9b0f5a3 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,46 +1,49 @@ -%ul.nav-links.scrolling-tabs +.scrolling-tabs-container .fade-left - = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do - = link_to profile_path, title: 'Profile Settings' do - %span - Profile - = nav_link(controller: [:accounts, :two_factor_auths]) do - = link_to profile_account_path, title: 'Account' do - %span - Account - - if current_application_settings.user_oauth_applications? - = nav_link(controller: 'oauth/applications') do - = link_to applications_profile_path, title: 'Applications' do - %span - Applications - = nav_link(controller: :personal_access_tokens) do - = link_to profile_personal_access_tokens_path, title: 'Personal Access Tokens' do - %span - Personal Access Tokens - = nav_link(controller: :emails) do - = link_to profile_emails_path, title: 'Emails' do - %span - Emails - - unless current_user.ldap_user? - = nav_link(controller: :passwords) do - = link_to edit_profile_password_path, title: 'Password' do - %span - Password - = nav_link(controller: :notifications) do - = link_to profile_notifications_path, title: 'Notifications' do - %span - Notifications - - = nav_link(controller: :keys) do - = link_to profile_keys_path, title: 'SSH Keys' do - %span - SSH Keys - = nav_link(controller: :preferences) do - = link_to profile_preferences_path, title: 'Preferences' do - %span - Preferences - = nav_link(path: 'profiles#audit_log') do - = link_to audit_log_profile_path, title: 'Audit Log' do - %span - Audit Log + = icon('arrow-left') .fade-right + = icon('arrow-right') + %ul.nav-links.scrolling-tabs + = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do + = link_to profile_path, title: 'Profile Settings' do + %span + Profile + = nav_link(controller: [:accounts, :two_factor_auths]) do + = link_to profile_account_path, title: 'Account' do + %span + Account + - if current_application_settings.user_oauth_applications? + = nav_link(controller: 'oauth/applications') do + = link_to applications_profile_path, title: 'Applications' do + %span + Applications + = nav_link(controller: :personal_access_tokens) do + = link_to profile_personal_access_tokens_path, title: 'Personal Access Tokens' do + %span + Personal Access Tokens + = nav_link(controller: :emails) do + = link_to profile_emails_path, title: 'Emails' do + %span + Emails + - unless current_user.ldap_user? + = nav_link(controller: :passwords) do + = link_to edit_profile_password_path, title: 'Password' do + %span + Password + = nav_link(controller: :notifications) do + = link_to profile_notifications_path, title: 'Notifications' do + %span + Notifications + + = nav_link(controller: :keys) do + = link_to profile_keys_path, title: 'SSH Keys' do + %span + SSH Keys + = nav_link(controller: :preferences) do + = link_to profile_preferences_path, title: 'Preferences' do + %span + Preferences + = nav_link(path: 'profiles#audit_log') do + = link_to audit_log_profile_path, title: 'Audit Log' do + %span + Audit Log diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 39ea4920ccc..a4bb56aa56f 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -5,27 +5,31 @@ = icon('cog') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - - is_project_member = @project.users.exists?(current_user.id) - - access = @project.team.max_member_access(current_user.id) - can_edit = can?(current_user, :admin_project, @project) + -# We don't use @project.team.find_member because it searches for group members too... + - member = @project.members.non_request.find_by(user_id: current_user.id) + - can_leave = member && can?(current_user, :destroy_project_member, member) - = render 'layouts/nav/project_settings', access: access, can_edit: can_edit + = render 'layouts/nav/project_settings', can_edit: can_edit - - if can_edit || is_project_member + - if can_edit || can_leave %li.divider - if can_edit %li = link_to edit_project_path(@project) do Edit Project - - if is_project_member + - if can_leave %li = link_to polymorphic_path([:leave, @project, :members]), data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do Leave Project -%div{ class: nav_control_class } +.scrolling-tabs-container{ class: nav_control_class } + .fade-left + = icon('arrow-left') + .fade-right + = icon('arrow-right') %ul.nav-links.scrolling-tabs - .fade-left = nav_link(path: 'projects#show', html_options: {class: 'home'}) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do %span @@ -38,9 +42,9 @@ - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do - = link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do + = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do %span - Code + Repository - if project_nav_tab? :pipelines = nav_link(controller: [:pipelines, :builds, :environments]) do @@ -109,4 +113,3 @@ %li.hidden = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do Commits - .fade-right diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 13d32bd1354..51a54b4f262 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -3,7 +3,7 @@ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do %span Members -- if access && can_edit +- if can_edit - if @project.allowed_to_share_with_group? = nav_link(controller: :group_links) do = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do diff --git a/app/views/notify/project_was_not_exported_email.html.haml b/app/views/notify/project_was_not_exported_email.html.haml index c9e9ade2cf1..c888da29c17 100644 --- a/app/views/notify/project_was_not_exported_email.html.haml +++ b/app/views/notify/project_was_not_exported_email.html.haml @@ -6,4 +6,4 @@ %ul - @errors.each do |error| %li - error + #{error} diff --git a/app/views/notify/project_was_not_exported_email.text.erb b/app/views/notify/project_was_not_exported_email.text.erb deleted file mode 100644 index a07f6edacf7..00000000000 --- a/app/views/notify/project_was_not_exported_email.text.erb +++ /dev/null @@ -1,6 +0,0 @@ -Project <%= @project.name %> couldn't be exported. - -The errors we encountered were: - -- @errors.each do |error| -<%= error %>
\ No newline at end of file diff --git a/app/views/notify/project_was_not_exported_email.text.haml b/app/views/notify/project_was_not_exported_email.text.haml new file mode 100644 index 00000000000..b27cb620b9e --- /dev/null +++ b/app/views/notify/project_was_not_exported_email.text.haml @@ -0,0 +1,6 @@ += "Project #{@project.name} couldn't be exported." + += "The errors we encountered were:" + +- @errors.each do |error| + #{error}
\ No newline at end of file diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 5afd83a522e..f77738f97f5 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -28,7 +28,7 @@ = label_tag :global_notification_level, "Global notification level", class: "label-light" %br .clearfix - .form-group.pull-left + .form-group.pull-left.global-notification-setting = render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true .clearfix diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 28a28282fd3..ca6714ef42b 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -14,8 +14,17 @@ %span This is a confidential issue. Your comment will not be visible to the public. %li.pull-right - %button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 } - Go full screen + .toolbar-group + = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" }) + = markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" }) + = markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" }) + = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`" }, title: "Insert code" }) + = markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" }) + = markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) + = markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) + .toolbar-group + %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } } + =icon("arrows-alt fw") .md-write-holder = yield @@ -24,7 +33,7 @@ - if defined?(referenced_users) && referenced_users %div.referenced-users.hide %span - = icon('exclamation-triangle') + = icon("exclamation-triangle") You are about to add %strong %span.js-referenced-users-count 0 diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index da522b53417..771a2e0df7d 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -8,4 +8,4 @@ %strong Only allow merge requests to be merged if the build succeeds .help-block Builds need to be configured to enable this feature. - = link_to icon('question-circle'), help_page_path('workflow', 'merge_requests#only-allow-merge-requests-to-be-merged-if-the-build-succeeds') + = link_to icon('question-circle'), help_page_path('workflow', 'merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml index ee63bc55a30..ac80951dd4f 100644 --- a/app/views/projects/badges/index.html.haml +++ b/app/views/projects/badges/index.html.haml @@ -7,7 +7,7 @@ %b Builds badge · = @build_badge.to_html .pull-right - = render 'shared/ref_switcher', destination: 'badges' + = render 'shared/ref_switcher', destination: 'badges', align_right: true .panel-body .row .col-md-2.text-center diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index ae89637df60..29c7d45074a 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -17,6 +17,8 @@ = dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) .gitignore-selector.js-gitignore-selector-wrap.hidden = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) + .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden + = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) .encoding-selector = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index b1769759dce..58524418a67 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -16,4 +16,4 @@ .file-content.code .nothing-here-block Empty file - else - = render 'shared/file_highlight', blob: blob + = render 'shared/file_highlight', blob: blob, repository: @repository diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index b8d8758fd2b..e38d1ff5ff0 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -24,8 +24,8 @@ %span.label.label-warning stuck %p.commit-title - - if commit_data = pipeline.commit_data - = link_to_gfm truncate(commit_data.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message" + - if commit = pipeline.commit + = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message" - else Cant find HEAD commit for this branch diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index a959b34a539..929496f81d8 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -10,29 +10,30 @@ = cache(cache_key) do %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } = commit_author_avatar(commit, size: 36) - .commit-row-title - %span.item-title - = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" - %span.commit-row-message.visible-xs-inline - · - = commit.short_id - - if commit.status - = render_commit_status(commit, cssclass: 'visible-xs-inline') - - if commit.description? - %a.text-expander.hidden-xs.js-toggle-button ... + .commit-info-block + .commit-row-title + %span.item-title + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" + %span.commit-row-message.visible-xs-inline + · + = commit.short_id + - if commit.status + = render_commit_status(commit, cssclass: 'visible-xs-inline') + - if commit.description? + %a.text-expander.hidden-xs.js-toggle-button ... - .commit-actions.hidden-xs - - if commit.status - = render_commit_status(commit, cssclass: 'btn btn-transparent') - = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent') - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent" - = link_to_browse_code(project, commit) + .commit-actions.hidden-xs + - if commit.status + = render_commit_status(commit, cssclass: 'btn btn-transparent') + = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent') + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent" + = link_to_browse_code(project, commit) - - if commit.description? - %pre.commit-row-description.js-toggle-content - = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author)) + - if commit.description? + %pre.commit-row-description.js-toggle-content + = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author)) - .commit-row-info - = commit_author_link(commit, avatar: false, size: 24) - authored - #{time_ago_with_tooltip(commit.committed_date)} + .commit-row-info + = commit_author_link(commit, avatar: false, size: 24) + authored + #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index c8aa849c217..b11b6c24ccd 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,7 +1,10 @@ -.scrolling-tabs-container +.scrolling-tabs-container.sub-nav-scroll + .fade-left + = icon('arrow-left') + .fade-right + = icon('arrow-right') .nav-links.sub-nav.scrolling-tabs %ul{ class: (container_class) } - .fade-left = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do = link_to project_files_path(@project) do Files @@ -25,4 +28,3 @@ = nav_link(controller: [:tags, :releases]) do = link_to namespace_project_tags_path(@project.namespace, @project) do Tags - .fade-right diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index c322942aeba..b22285c11e0 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -3,7 +3,7 @@ = render "projects/commits/head" %div{ class: (container_class) } - .row-content-block.second-block.content-component-block + .sub-header-block Compare branches, tags or commit ranges. %br Fill input field with commit id like diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index cdc34f51d6d..f4ec7b767f6 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,24 +1,24 @@ +- @no_container = true - page_title "#{params[:from]}...#{params[:to]}" = render "projects/commits/head" +%div{ class: (container_class) } + .sub-header-block.no-bottom-space + = render "form" -.row-content-block - = render "form" - -- if @commits.present? - .prepend-top-default + - if @commits.present? = render "projects/commits/commit_list" = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs -- else - .light-well.prepend-top-default - .center - %h4 - There isn't anything to compare. - %p.slead - - if params[:to] == params[:from] - %span.label-branch #{params[:from]} - and - %span.label-branch #{params[:to]} - are the same. - - else - You'll need to use different branch names to get a valid comparison. + - else + .light-well + .center + %h4 + There isn't anything to compare. + %p.slead + - if params[:to] == params[:from] + %span.label-branch #{params[:from]} + and + %span.label-branch #{params[:to]} + are the same. + - else + You'll need to use different branch names to get a valid comparison. diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml index f35faa6afb5..10822b6184c 100644 --- a/app/views/projects/container_registry/_tag.html.haml +++ b/app/views/projects/container_registry/_tag.html.haml @@ -3,9 +3,9 @@ = escape_once(tag.name) = clipboard_button(clipboard_text: "docker pull #{tag.path}") %td - - if layer = tag.layers.first - %span.has-tooltip{ title: "#{layer.revision}" } - = layer.short_revision + - if tag.revision + %span.has-tooltip{ title: "#{tag.revision}" } + = tag.short_revision - else \- %td diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index ae9e77e7d89..a03f117291f 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -3,16 +3,24 @@ = render "projects/pipelines/head" %div{ class: (container_class) } - - if can?(current_user, :create_environment, @project) + - if can?(current_user, :create_environment, @project) && !@environments.blank? .top-area .nav-controls = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment - if @environments.blank? - %ul.content-list.environments - %li.nothing-here-block - No environments to show + .blank-state.blank-state-no-icon + %h2.blank-state-title + You don't have any environments right now. + %p.blank-state-text + Environments are places where code gets deployed, such as staging or production. + %br + = succeed "." do + = link_to "Read more about environments", help_page_path("ci", "environments") + - if can?(current_user, :create_environment, @project) + = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do + New environment - else .table-holder %table.table.environments diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index 54465828ba9..da325efecd2 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -4,6 +4,9 @@ .col-lg-3 %h4.prepend-top-0 New Environment - %p Environments allow you to track deployments of your application + %p + Environments allow you to track deployments of your application + = succeed "." do + = link_to "Read more about environments", help_page_path("ci", "environments") = render 'form' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 069b77b5adf..4c15e2759d6 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -13,10 +13,14 @@ = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? - %ul.content-list.environments - %li.nothing-here-block - No deployments for - %strong= @environment.name + .blank-state.blank-state-no-icon + %h2.blank-state-title + You don't have any deployments right now. + %p.blank-state-text + Define environments in the deploy stage(s) in + %code .gitlab-ci.yml + to track deployments here. + = link_to "Read more", help_page_path("ci", "environments"), class: "btn btn-success" - else .table-holder %table.table.environments diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index 4bcf2d9d533..dbe9ddfde2f 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -40,9 +40,3 @@ = render 'projects', projects: @forks - -- if @private_forks_count > 0 - .private-forks-notice - = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon') - %strong= pluralize(@private_forks_count, 'private fork') - %span you have no access to. diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index 8becaea246f..ca347406dfe 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -1,12 +1,16 @@ -- page_specific_javascripts asset_path("graphs/application.js") -%ul.nav-links - = nav_link(action: :show) do - = link_to 'Contributors', namespace_project_graph_path - = nav_link(action: :commits) do - = link_to 'Commits', commits_namespace_project_graph_path - = nav_link(action: :languages) do - = link_to 'Languages', languages_namespace_project_graph_path - - if @project.builds_enabled? - = nav_link(action: :ci) do - = link_to ci_namespace_project_graph_path do - Continuous Integration +.nav-links.sub-nav + %ul{ class: (container_class) } + + - content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/chart.js') + = page_specific_javascript_tag('graphs/application.js') + = nav_link(action: :show) do + = link_to 'Contributors', namespace_project_graph_path + = nav_link(action: :commits) do + = link_to 'Commits', commits_namespace_project_graph_path + = nav_link(action: :languages) do + = link_to 'Languages', languages_namespace_project_graph_path + - if @project.builds_enabled? + = nav_link(action: :ci) do + = link_to ci_namespace_project_graph_path do + Continuous Integration diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml index 19ccc125ea8..e695d3ae369 100644 --- a/app/views/projects/graphs/ci.html.haml +++ b/app/views/projects/graphs/ci.html.haml @@ -1,15 +1,18 @@ +- @no_container = true - page_title "Continuous Integration", "Graphs" = render 'head' -.row-content-block.append-bottom-default - .oneline - A collection of graphs for Continuous Integration -#charts.ci-charts - .row - .col-md-6 - = render 'projects/graphs/ci/overall' - .col-md-6 - = render 'projects/graphs/ci/build_times' +%div{ class: (container_class) } + .sub-header-block + .oneline + A collection of graphs for Continuous Integration - %hr - = render 'projects/graphs/ci/builds' + #charts.ci-charts + .row + .col-md-6 + = render 'projects/graphs/ci/overall' + .col-md-6 + = render 'projects/graphs/ci/build_times' + + %hr + = render 'projects/graphs/ci/builds' diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index d9b2fb6c065..0daffe68f6f 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -1,52 +1,54 @@ +- @no_container = true - page_title "Commits", "Graphs" = render 'head' -.row-content-block.append-bottom-default - .tree-ref-holder - = render 'shared/ref_switcher', destination: 'graphs_commits' - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs +%div{ class: (container_class) } + .sub-header-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'graphs_commits' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs -%p.lead - Commit statistics for - %strong #{@ref} - #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} + %p.lead + Commit statistics for + %strong #{@ref} + #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} -.row - .col-md-6 - %ul - %li - %p.lead - %strong #{@commits_graph.commits.size} - commits during - %strong #{@commits_graph.duration} - days - %li - %p.lead - Average - %strong #{@commits_graph.commit_per_day} - commits per day - %li - %p.lead - Contributed by - %strong #{@commits_graph.authors} - authors - .col-md-6 - %div - %p.slead - Commits per day of month - %canvas#month-chart -.row - .col-md-6 - %div - %p.slead - Commits per day hour (UTC) - %canvas#hour-chart - .col-md-6 - %div - %p.slead - Commits per weekday - %canvas#weekday-chart + .row + .col-md-6 + %ul + %li + %p.lead + %strong #{@commits_graph.commits.size} + commits during + %strong #{@commits_graph.duration} + days + %li + %p.lead + Average + %strong #{@commits_graph.commit_per_day} + commits per day + %li + %p.lead + Contributed by + %strong #{@commits_graph.authors} + authors + .col-md-6 + %div + %p.slead + Commits per day of month + %canvas#month-chart + .row + .col-md-6 + %div + %p.slead + Commits per day hour (UTC) + %canvas#hour-chart + .col-md-6 + %div + %p.slead + Commits per weekday + %canvas#weekday-chart :javascript var responsiveChart = function (selector, data) { diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml index 249c16f4709..6d97f552a8e 100644 --- a/app/views/projects/graphs/languages.html.haml +++ b/app/views/projects/graphs/languages.html.haml @@ -1,24 +1,26 @@ +- @no_container = true - page_title "Languages", "Graphs" = render 'head' -.row-content-block.append-bottom-default - .oneline - Programming languages used in this repository +%div{ class: (container_class) } + .sub-header-block + .oneline + Programming languages used in this repository -.row - .col-md-8 - %canvas#languages-chart{ height: 400 } - .col-md-4 - %ul.bordered-list - - @languages.each do |language| - %li - %span{ style: "color: #{language[:color]}" } - = icon('circle') - - = language[:label] - .pull-right - = language[:value] - \% + .row + .col-md-8 + %canvas#languages-chart{ height: 400 } + .col-md-4 + %ul.bordered-list + - @languages.each do |language| + %li + %span{ style: "color: #{language[:color]}" } + = icon('circle') + + = language[:label] + .pull-right + = language[:value] + \% :javascript var data = #{@languages.to_json}; diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 33970e7b909..9f7e2a361ff 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,29 +1,31 @@ +- @no_container = true - page_title "Contributors", "Graphs" = render 'head' -.row-content-block.append-bottom-default - .tree-ref-holder - = render 'shared/ref_switcher', destination: 'graphs' - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs - -.loading-graph - .center - %h3.page-title - %i.fa.fa-spinner.fa-spin - Building repository graph. - %p.slead Please wait a moment, this page will automatically refresh when ready. - -.stat-graph.hide - .header.clearfix - %h3#date_header.page-title - %p.light - Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits. - %input#brush_change{:type => "hidden"} - .graphs - #contributors-master - #contributors.clearfix - %ol.contributors-list.clearfix +%div{ class: (container_class) } + .sub-header-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'graphs' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs + + .loading-graph + .center + %h3.page-title + %i.fa.fa-spinner.fa-spin + Building repository graph. + %p.slead Please wait a moment, this page will automatically refresh when ready. + + .stat-graph.hide + .header.clearfix + %h3#date_header.page-title + %p.light + Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits. + %input#brush_change{:type => "hidden"} + .graphs.row + #contributors-master + #contributors.clearfix + %ol.contributors-list.clearfix diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 393998f15b9..53dd300c35c 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -1,8 +1,8 @@ - content_for :note_actions do - if can?(current_user, :update_merge_request, @merge_request) - if @merge_request.open? - = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"} + = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"} - if @merge_request.closed? - = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"} + = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"} #notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index e4ab064eda8..3ca30b4ba6b 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,5 +1,7 @@ - page_title "Network", @ref -- page_specific_javascripts asset_path("network/application.js") +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/raphael.js') + = page_specific_javascript_tag('network/application.js') = render "projects/commits/head" = render "head" %div{ class: (container_class) } @@ -15,5 +17,5 @@ = check_box_tag :filter_ref, 1, @options[:filter_ref] %span Begin with the selected commit - .network-graph{ data: { url: '#{escape_javascript(@url)}', commit_url: '#{escape_javascript(@commit_url)}', ref: '#{escape_javascript(@ref)}', commit_id: '#{escape_javascript(@commit.id)}' } } + .network-graph{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } } = spinner nil, true diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml index 0b002043408..7d1cbc62e86 100644 --- a/app/views/projects/notes/_hints.html.haml +++ b/app/views/projects/notes/_hints.html.haml @@ -5,4 +5,4 @@ is supported %button.toolbar-button.markdown-selector{ type: 'button', tabindex: '-1' } = icon('file-image-o', class: 'toolbar-button-icon') - Attach a file + Attach a file
\ No newline at end of file diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index bcdbff08011..a5e163b91e9 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -18,9 +18,9 @@ = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') .note-actions - access = note.project.team.human_max_access(note.author.id) - - if access + - if access and not note.system %span.note-role.hidden-xs= access - - if current_user + - if current_user and not note.system = link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do = icon('spinner spin') = icon('smile-o') @@ -32,7 +32,7 @@ .note-body{class: note_editable ? 'js-task-list-container' : ''} .note-text = preserve do - = markdown(note.note, pipeline: :note, cache_key: [note, "note"], author: note.author) + = note.note_html = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) - if note_editable = render 'projects/notes/edit_form', note: note diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index cb6136c215a..e783d8c72c5 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -2,8 +2,7 @@ .panel-heading %strong #{@group.name} group members - %small - (#{members.count}) + %span.badge= members.size - if can?(current_user, :admin_group_member, @group) .controls = link_to 'Manage group members', diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml index 952844acefc..840b57c2e63 100644 --- a/app/views/projects/project_members/_shared_group_members.html.haml +++ b/app/views/projects/project_members/_shared_group_members.html.haml @@ -1,6 +1,7 @@ - @project_group_links.each do |group_links| - shared_group = group_links.group - - shared_group_users_count = group_links.group.group_members.count + - shared_group_members = shared_group.members.non_request + - shared_group_users_count = shared_group_members.size .panel.panel-default .panel-heading Shared with @@ -15,7 +16,7 @@ Edit group members %ul.content-list = render partial: 'shared/members/member', - collection: shared_group.group_members.order(access_level: :desc).limit(20), + collection: shared_group_members.order(access_level: :desc).limit(20), as: :member, locals: { show_controls: false, show_roles: false } - if shared_group_users_count > 20 diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 03207614258..b0bfdd235f7 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,8 +2,7 @@ .panel-heading %strong #{@project.name} project members - %small - (#{members.count}) + %span.badge= members.size .controls = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do .form-group diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 357ccccaf1d..a2026c41d01 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -18,7 +18,7 @@ = render 'team', members: @project_members.non_request - if @group - = render "group_members", members: @group_members + = render "group_members", members: @group_members.non_request - if @project_group_links.any? && @project.allowed_to_share_with_group? = render "shared_group_members" diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index d62f5c8f131..c45a9d4f81f 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -13,6 +13,12 @@ = f.check_box :run_untagged %span.light Indicates whether this runner can pick jobs without tags .form-group + = label :locked, 'Lock to current projects', class: 'control-label' + .col-sm-10 + .checkbox + = f.check_box :locked + %span.light When a runner is locked, it cannot be assigned to other projects + .form-group = label_tag :token, class: 'control-label' do Token .col-sm-10 diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 96e2aac451f..85225857758 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -2,8 +2,10 @@ %h4 = runner_status_icon(runner) %span.monospace - - if @runners.include?(runner) + - if @project_runners.include?(runner) = link_to runner.short_sha, runner_path(runner) + - if runner.locked? + = icon('lock', class: 'has-tooltip', title: 'Locked to current projects') %small = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do %i.fa.fa-edit.btn @@ -11,7 +13,7 @@ = runner.short_sha .pull-right - - if @runners.include?(runner) + - if @project_runners.include?(runner) - if runner.belongs_to_one_project? = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' - else diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index 8ae9f0d95f7..d469dda5b81 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -17,13 +17,13 @@ Start runner! -- if @runners.any? +- if @project_runners.any? %h4.underlined-title Runners activated for this project %ul.bordered-list.activated-specific-runners - = render partial: 'runner', collection: @runners, as: :runner + = render partial: 'runner', collection: @project_runners, as: :runner -- if @specific_runners.any? +- if @assignable_runners.any? %h4.underlined-title Available specific runners %ul.bordered-list.available-specific-runners - = render partial: 'runner', collection: @specific_runners, as: :runner - = paginate @specific_runners + = render partial: 'runner', collection: @assignable_runners, as: :runner + = paginate @assignable_runners diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml index f24e1b9144e..61b99f35d74 100644 --- a/app/views/projects/runners/show.html.haml +++ b/app/views/projects/runners/show.html.haml @@ -23,6 +23,9 @@ %td Can run untagged jobs %td= @runner.run_untagged? ? 'Yes' : 'No' %tr + %td Locked to this project + %td= @runner.locked? ? 'Yes' : 'No' + %tr %td Tags %td - @runner.tag_list.each do |tag| diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index e9ca46a74bf..15f0d85194b 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -57,6 +57,10 @@ %li.missing = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do Add Contribution guide + - unless @repository.gitlab_ci_yml + %li.missing + = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do + Set Up CI - if @repository.commit .content-block.second-block.white diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 4faa547769b..4ea75dbbf0c 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,4 +1,7 @@ - if (@page && @page.persisted?) + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do + New Page = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do Page History - if can?(current_user, :create_wiki, @project) diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 988fe024e28..f8ea479e0b1 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,5 +1,5 @@ -.top-area - %ul.nav-links +.nav-links.sub-nav + %ul{ class: (container_class) } = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) @@ -10,9 +10,4 @@ = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do Git Access - .nav-controls - - if can?(current_user, :create_wiki, @project) - = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - New Page - -= render 'projects/wikis/new' + = render 'projects/wikis/new' diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index 919daf0a7b2..4f8abcdc8e1 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -1,14 +1,17 @@ -%div#modal-new-wiki.modal - .modal-dialog - .modal-content - .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × - %h3.page-title New Wiki Page - .modal-body - %form.new-wiki-page - .form-group - = label_tag :new_wiki_path do - %span Page slug - = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true - .form-actions - = button_tag 'Create Page', class: 'build-new-wiki btn btn-create' +- @no_container = true + +%div{ class: (container_class) } + %div#modal-new-wiki.modal + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3.page-title New Wiki Page + .modal-body + %form.new-wiki-page + .form-group + = label_tag :new_wiki_path do + %span Page slug + = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true + .form-actions + = button_tag 'Create Page', class: 'build-new-wiki btn btn-create' diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index cbd69ee1a73..817bf9b3f69 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,19 +1,25 @@ +- @no_container = true - page_title "Edit", @page.title.capitalize, "Wiki" = render 'nav' -.top-area - .nav-text.wiki-page - %strong - - if @page.persisted? - = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) - - else - = @page.title.capitalize - %span.light - · - Edit Page +%div{ class: (container_class) } + .top-area + .nav-text + %strong + - if @page.persisted? + = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) + - else + = @page.title.capitalize + %span.light + · + Edit Page - .nav-controls - = render 'main_links' + .nav-controls + - if !(@page && @page.persisted?) + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do + New Page + = render 'main_links' -= render 'form' + = render 'form' diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index ccceab6155e..6caf7230f35 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -1,32 +1,34 @@ +- @no_container = true - page_title "Git Access", "Wiki" = render 'nav' -.row-content-block - %span.oneline - Git access for - %strong= @project_wiki.path_with_namespace +%div{ class: (container_class) } + .sub-header-block + %span.oneline + Git access for + %strong= @project_wiki.path_with_namespace - .pull-right - = render "shared/clone_panel", project: @project_wiki + .pull-right + = render "shared/clone_panel", project: @project_wiki -.git-empty.prepend-top-default - %fieldset - %legend Install Gollum: - %pre.dark - :preserve - gem install gollum + .prepend-top-default + %fieldset + %legend Install Gollum: + %pre.dark + :preserve + gem install gollum - %legend Clone Your Wiki: - %pre.dark - :preserve - git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')} - cd #{h @project_wiki.path} + %legend Clone Your Wiki: + %pre.dark + :preserve + git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')} + cd #{h @project_wiki.path} - %legend Start Gollum And Edit Locally: - %pre.dark - :preserve - gollum - == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin - >> Thin web server (v1.5.0 codename Knife) - >> Maximum connections set to 1024 - >> Listening on 0.0.0.0:4567, CTRL+C to stop + %legend Start Gollum And Edit Locally: + %pre.dark + :preserve + gollum + == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin + >> Thin web server (v1.5.0 codename Knife) + >> Maximum connections set to 1024 + >> Listening on 0.0.0.0:4567, CTRL+C to stop diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 45460ed9f41..630ee35b70b 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -1,37 +1,37 @@ - page_title "History", @page.title.capitalize, "Wiki" = render 'nav' +%div{ class: (container_class) } + .top-area + .nav-text + %strong + = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) + %span.light + · + History -.top-area - .nav-text - %strong - = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) - %span.light - · - History - -.table-holder - %table.table - %thead - %tr - %th Page version - %th Author - %th Commit Message - %th Last updated - %th Format - %tbody - - @page.versions.each_with_index do |version, index| - - commit = version + .table-holder + %table.table + %thead %tr - %td - = link_to project_wiki_path_with_version(@project, @page, - commit.id, index == 0) do - = truncate_sha(commit.id) - %td - = commit.author.name - %td - = commit.message - %td - #{time_ago_with_tooltip(version.authored_date)} - %td - %strong - = @page.page.wiki.page(@page.page.name, commit.id).try(:format) + %th Page version + %th Author + %th Commit Message + %th Last updated + %th Format + %tbody + - @page.versions.each_with_index do |version, index| + - commit = version + %tr + %td + = link_to project_wiki_path_with_version(@project, @page, + commit.id, index == 0) do + = truncate_sha(commit.id) + %td + = commit.author.name + %td + = commit.message + %td + #{time_ago_with_tooltip(version.authored_date)} + %td + %strong + = @page.page.wiki.page(@page.page.name, commit.id).try(:format) diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 2f6162fa3c5..81d9f391c1c 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -1,12 +1,14 @@ +- @no_container = true - page_title "Pages", "Wiki" = render 'nav' -%ul.content-list - - @wiki_pages.each do |wiki_page| - %li - = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page) - %small (#{wiki_page.format}) - .pull-right - %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)} -= paginate @wiki_pages, theme: 'gitlab' +%div{ class: (container_class) } + %ul.content-list + - @wiki_pages.each do |wiki_page| + %li + = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page) + %small (#{wiki_page.format}) + .pull-right + %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)} + = paginate @wiki_pages, theme: 'gitlab' diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 9166c0edb3b..76f9b1ecd76 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,24 +1,26 @@ +- @no_container = true - page_title @page.title.capitalize, "Wiki" = render 'nav' -.top-area - .nav-text - %strong= @page.title.capitalize +%div{ class: (container_class) } + .top-area + .nav-text + %strong= @page.title.capitalize - %span.wiki-last-edit-by - · - last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} + %span.wiki-last-edit-by + · + last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} - .nav-controls - = render 'main_links' + .nav-controls + = render 'main_links' -- if @page.historical? - .warning_message - This is an old version of this page. - You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. + - if @page.historical? + .warning_message + This is an old version of this page. + You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. -.wiki-holder.prepend-top-default.append-bottom-default - .wiki - = preserve do - = render_wiki_content(@page) + .wiki-holder.prepend-top-default.append-bottom-default + .wiki + = preserve do + = render_wiki_content(@page) diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index 0fe8a3b490a..290743feb4a 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -2,9 +2,10 @@ .blob-result .file-holder .file-title - = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do + - blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename)) + = link_to blob_link do %i.fa.fa-file %strong = blob.filename .file-content.code.term - = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline + = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 30055002213..8824bcc158e 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,7 +1,5 @@ %ul.nav-links.event-filter.scrolling-tabs - .fade-left = event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.team, 'Team' - .fade-right diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index 37dcf39c062..e26693bf5b9 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -1,13 +1,16 @@ +- repository = nil unless local_assigns.key?(:repository) + .file-content.code.js-syntax-highlight .line-numbers - if blob.data.present? - link_icon = icon('link') + - link = blob_link if defined?(blob_link) - blob.data.each_line.each_with_index do |_, index| - offset = defined?(first_line_number) ? first_line_number : 1 - i = index + offset -# We're not using `link_to` because it is too slow once we get to thousands of lines. - %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} + %a.diff-line-num{href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i} = link_icon = i .blob-content{data: {blob_id: blob.id}} - = highlight(blob.name, blob.data, plain: blob.no_highlighting?) + = highlight(blob.path, blob.data, repository: repository, plain: blob.no_highlighting?) diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index eb2e1919e19..ea7162d4d63 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -1,7 +1,14 @@ +- dropdown_toggle_text = @ref || @project.default_branch = form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do - = select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm" = hidden_field_tag :destination, destination - if defined?(path) = hidden_field_tag :path, path - @options && @options.each do |key, value| = hidden_field_tag key, value, id: nil + .dropdown + = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project) }, { toggle_class: "js-project-refs-dropdown" } + .dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } + = dropdown_title "Switch branch/tag" + = dropdown_filter "Search branches and tags" + = dropdown_content + = dropdown_loading diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml index b5963876034..e4bd2bdc265 100644 --- a/app/views/shared/members/_requests.html.haml +++ b/app/views/shared/members/_requests.html.haml @@ -3,6 +3,6 @@ .panel-heading %strong= membership_source.name access requests - %small= "(#{members.size})" + %span.badge= members.size %ul.content-list = render partial: 'shared/members/member', collection: members, as: :member diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index 47b66d44e43..3c03c220ddd 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -21,7 +21,8 @@ = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do - render_colored_label(label) - - if assignee - = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }), - class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do - - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '') + %span{ class: "assignee-icon" } + - if assignee + = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }), + class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do + - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '') diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index 2e08bb2ac08..3a9dd37dc7d 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -16,6 +16,12 @@ = render "shared/projects/project", project: project, skip_namespace: skip_namespace, avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, forks: forks, show_last_commit_as_description: show_last_commit_as_description + + - if @private_forks_count && @private_forks_count > 0 + %li.project-row.private-forks-notice + = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon') + %strong= pluralize(@private_forks_count, 'private fork') + %span you have no access to. = paginate(projects, remote: remote, theme: "gitlab") if projects.respond_to? :total_pages - else .nothing-here-block No projects found diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 92305594a81..68665858c3e 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,6 +1,8 @@ - page_title @user.name - page_description @user.bio -- page_specific_javascripts asset_path("users/application.js") +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/d3.js') + = page_specific_javascript_tag('users/application.js') - header_title @user.name, user_path(@user) - @no_container = true diff --git a/config/application.rb b/config/application.rb index 05fec995ed3..2b0595ede2b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -84,6 +84,8 @@ module Gitlab config.assets.precompile << "graphs/application.js" config.assets.precompile << "users/application.js" config.assets.precompile << "network/application.js" + config.assets.precompile << "lib/utils/*.js" + config.assets.precompile << "lib/*.js" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 09ffc319065..c6dc1e4ab38 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -215,7 +215,7 @@ Settings.gitlab.default_projects_features['container_registry'] = true if Settin Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil? Settings.gitlab['restricted_signup_domains'] ||= [] -Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'] +Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project] Settings.gitlab['trusted_proxies'] ||= [] diff --git a/config/initializers/haml.rb b/config/initializers/haml.rb deleted file mode 100644 index 1516476815a..00000000000 --- a/config/initializers/haml.rb +++ /dev/null @@ -1,7 +0,0 @@ -Haml::Template.options[:ugly] = true - -# Remove the `:coffee` and `:coffeescript` filters -# -# See https://git.io/vztMu and http://stackoverflow.com/a/17571242/223897 -Haml::Filters.remove_filter('coffee') -Haml::Filters.remove_filter('coffeescript') diff --git a/config/initializers/hamlit.rb b/config/initializers/hamlit.rb new file mode 100644 index 00000000000..7b545d8c06c --- /dev/null +++ b/config/initializers/hamlit.rb @@ -0,0 +1,18 @@ +module Hamlit + class TemplateHandler + def call(template) + Engine.new( + generator: Temple::Generators::RailsOutputBuffer, + attr_quote: '"', + ).call(template.source) + end + end +end + +ActionView::Template.register_template_handler( + :haml, + Hamlit::TemplateHandler.new, +) + +Hamlit::Filters.remove_filter('coffee') +Hamlit::Filters.remove_filter('coffeescript') diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb index 79e2d23ab2e..6796407d4e6 100644 --- a/config/initializers/health_check.rb +++ b/config/initializers/health_check.rb @@ -1,3 +1,17 @@ +# Email forcibly included in the standard checks, but the email health check +# doesn't support the full range of SMTP options, which can result in failures +# for valid SMTP configurations. +# Overwrite the HealthCheck's detection of whether email is configured +# in order to avoid the email check during standard checks +module HealthCheck + class Utils + def self.mailer_configured? + false + end + end +end + HealthCheck.setup do |config| config.standard_checks = ['database', 'migrations', 'cache'] + config.full_checks = ['database', 'migrations', 'cache'] end diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index d159f4eded2..75f89d524e7 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -113,6 +113,10 @@ if Gitlab::Metrics.enabled? config.instrument_methods(Banzai::Renderer) config.instrument_methods(Banzai::Querying) + config.instrument_instance_methods(Banzai::ObjectRenderer) + config.instrument_instance_methods(Banzai::Redactor) + config.instrument_methods(Banzai::NoteRenderer) + [Issuable, Mentionable, Participable].each do |klass| config.instrument_instance_methods(klass) config.instrument_instance_methods(klass::ClassMethods) diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index f1eec674888..7a2b9a7f6c1 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -23,6 +23,10 @@ Sidekiq.configure_server do |config| config['pool'] = Sidekiq.options[:concurrency] + 2 ActiveRecord::Base.establish_connection(config) Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}") + + # Avoid autoload issue such as 'Mail::Parsers::AddressStruct' + # https://github.com/mikel/mail/issues/912#issuecomment-214850355 + Mail.eager_autoload! end Sidekiq.configure_client do |config| diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample index 2287a76fca7..bd37080b1c8 100644 --- a/config/initializers/smtp_settings.rb.sample +++ b/config/initializers/smtp_settings.rb.sample @@ -10,6 +10,7 @@ if Rails.env.production? Rails.application.config.action_mailer.delivery_method = :smtp + ActionMailer::Base.delivery_method = :smtp ActionMailer::Base.smtp_settings = { address: "email.server.com", port: 465, diff --git a/config/routes.rb b/config/routes.rb index de6094fa0ed..e45293cdf7f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -295,7 +295,7 @@ Rails.application.routes.draw do post :repository_check end - resources :runner_projects + resources :runner_projects, only: [:create, :destroy] end end @@ -479,6 +479,7 @@ Rails.application.routes.draw do get :download_export get :autocomplete_sources get :activity + get :refs end scope module: :projects do diff --git a/db/migrate/20160509091049_add_locked_to_ci_runner.rb b/db/migrate/20160509091049_add_locked_to_ci_runner.rb new file mode 100644 index 00000000000..3fbaef3b7f0 --- /dev/null +++ b/db/migrate/20160509091049_add_locked_to_ci_runner.rb @@ -0,0 +1,13 @@ +class AddLockedToCiRunner < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + def up + add_column_with_default(:ci_runners, :locked, :boolean, + default: false, allow_null: false) + end + + def down + remove_column(:ci_runners, :locked) + end +end diff --git a/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb new file mode 100644 index 00000000000..bd0463886bc --- /dev/null +++ b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb @@ -0,0 +1,9 @@ +class SetMissingStageOnCiBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def up + update_column_in_batches(:ci_builds, :stage, :test) do |table, query| + query.where(table[:stage].eq(nil)) + end + end +end diff --git a/db/migrate/20160616102642_remove_duplicated_keys.rb b/db/migrate/20160616102642_remove_duplicated_keys.rb new file mode 100644 index 00000000000..00a45d7fe73 --- /dev/null +++ b/db/migrate/20160616102642_remove_duplicated_keys.rb @@ -0,0 +1,19 @@ +# rubocop:disable all +class RemoveDuplicatedKeys < ActiveRecord::Migration + def up + select_all("SELECT fingerprint FROM #{quote_table_name(:keys)} GROUP BY fingerprint HAVING COUNT(*) > 1").each do |row| + fingerprint = connection.quote(row['fingerprint']) + execute(%Q{ + DELETE FROM keys + WHERE fingerprint = #{fingerprint} + AND id != ( + SELECT id FROM ( + SELECT max(id) AS id + FROM keys + WHERE fingerprint = #{fingerprint} + ) max_ids + ) + }) + end + end +end diff --git a/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb b/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb new file mode 100644 index 00000000000..4bb4204cebd --- /dev/null +++ b/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb @@ -0,0 +1,21 @@ +class RemoveKeysFingerprintIndexIfExists < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + # https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/250 + # That MR was added on gitlab-ee so we need to check if the index + # already exists because we want to do is create an unique index instead. + + def up + if index_exists?(:keys, :fingerprint) + remove_index :keys, :fingerprint + end + end + + def down + unless index_exists?(:keys, :fingerprint) + add_concurrent_index :keys, :fingerprint + end + end +end diff --git a/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb b/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb new file mode 100644 index 00000000000..e35af38aac3 --- /dev/null +++ b/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb @@ -0,0 +1,13 @@ +class AddUniqueIndexToKeysFingerprint < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + def up + add_concurrent_index :keys, :fingerprint, unique: true + end + + def down + remove_index :keys, :fingerprint + end +end diff --git a/db/migrate/20160620115026_add_index_on_runners_locked.rb b/db/migrate/20160620115026_add_index_on_runners_locked.rb new file mode 100644 index 00000000000..dfa5110dea4 --- /dev/null +++ b/db/migrate/20160620115026_add_index_on_runners_locked.rb @@ -0,0 +1,12 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddIndexOnRunnersLocked < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + def change + add_concurrent_index :ci_runners, :locked + end +end diff --git a/db/schema.rb b/db/schema.rb index 5a27e9d5cdc..7a8377f687c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160616084004) do +ActiveRecord::Schema.define(version: 20160620115026) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -287,9 +287,11 @@ ActiveRecord::Schema.define(version: 20160616084004) do t.string "platform" t.string "architecture" t.boolean "run_untagged", default: true, null: false + t.boolean "locked", default: false, null: false end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} + add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} @@ -507,6 +509,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do end add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree + add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree create_table "label_links", force: :cascade do |t| @@ -707,6 +710,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do t.integer "level", default: 0, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "events" end add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree diff --git a/doc/README.md b/doc/README.md index 5d89d0c9821..be0d17084c7 100644 --- a/doc/README.md +++ b/doc/README.md @@ -3,17 +3,18 @@ ## User documentation - [API](api/README.md) Automate GitLab via a simple and powerful API. -- [CI](ci/README.md) GitLab Continuous Integration (CI) getting started, `.gitlab-ci.yml` options, and examples. +- [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. +- [Container Registry](container_registry/README.md) Learn how to use GitLab Container Registry. - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). +- [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Markdown](markdown/markdown.md) GitLab's advanced formatting system. -- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab +- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab. - [Permissions](permissions/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do. - [Profile Settings](profile/README.md) - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat. - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects. -- [Container Registry](container_registry/README.md) Learn how to use GitLab Container Registry. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. @@ -24,15 +25,15 @@ external authentication with LDAP, SAML, CAS and additional Omniauth providers. - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough. - [Install](install/README.md) Requirements, directory structures and installation from source. -- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components +- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Log system](administration/logs.md) Log system. - [Environment Variables](administration/environment_variables.md) to configure GitLab. -- [Operations](operations/README.md) Keeping GitLab up and running +- [Operations](operations/README.md) Keeping GitLab up and running. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. -- [Repository checks](administration/repository_checks.md) Periodic Git repository checks +- [Repository checks](administration/repository_checks.md) Periodic Git repository checks. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. @@ -41,11 +42,12 @@ - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. -- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics -- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint -- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs -- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability -- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab +- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. +- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint. +- [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong +- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. +- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. +- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab. ## Contributor documentation diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index 7870669fa77..d5d43303454 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -22,6 +22,7 @@ You can read more about Docker Registry at https://docs.docker.com/registry/intr - [Disable Container Registry per project](#disable-container-registry-per-project) - [Disable Container Registry for new projects site-wide](#disable-container-registry-for-new-projects-site-wide) - [Container Registry storage path](#container-registry-storage-path) +- [Container Registry storage driver](#container-registry-storage-driver) - [Storage limitations](#storage-limitations) - [Changelog](#changelog) @@ -84,6 +85,17 @@ GitLab does not ship with a Registry init file. Hence, [restarting GitLab][resta will not restart the Registry should you modify its settings. Read the upstream documentation on how to achieve that. +The Docker Registry configuration will need `container_registry` as the service and `https://gitlab.example.com/jwt/auth` as the realm: + +``` +auth: + token: + realm: https://gitlab.example.com/jwt/auth + service: container_registry + issuer: gitlab-issuer + rootcertbundle: /root/certs/certbundle +``` + ## Container Registry domain configuration There are two ways you can configure the Registry's external domain. @@ -306,8 +318,12 @@ the Container Registry by themselves, follow the steps below. ## Container Registry storage path -To change the storage path where Docker images will be stored, follow the -steps below. +>**Note:** +For configuring storage in the cloud instead of the filesystem, see the +[storage driver configuration](#container-registry-storage-driver). + +If you want to store your images on the filesystem, you can change the storage +path for the Container Registry, follow the steps below. This path is accessible to: @@ -349,6 +365,72 @@ The default location where images are stored in source installations, is 1. Save the file and [restart GitLab][] for the changes to take effect. +## Container Registry storage driver + +You can configure the Container Registry to use a different storage backend by +configuring a different storage driver. By default the GitLab Container Registry +is configured to use the filesystem driver, which makes use of [storage path](#container-registry-storage-path) +configuration. + +The different supported drivers are: + +| Driver | Description | +|------------|-------------------------------------| +| filesystem | Uses a path on the local filesystem | +| azure | Microsoft Azure Blob Storage | +| gcs | Google Cloud Storage | +| s3 | Amazon Simple Storage Service | +| swift | OpenStack Swift Object Storage | +| oss | Aliyun OSS | + +Read more about the individual driver's config options in the +[Docker Registry docs][storage-config]. + +> **Warning** GitLab will not backup Docker images that are not stored on the +filesystem. Remember to enable backups with your object storage provider if +desired. + +--- + +**Omnibus GitLab installations** + +To configure the storage driver in Omnibus: + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + registry['storage'] = { + 's3' => { + 'accesskey' => 's3-access-key', + 'secretkey' => 's3-secret-key-for-access-key', + 'bucket' => 'your-s3-bucket' + } + } + ``` + +1. Save the file and [reconfigure GitLab][] for the changes to take effect. + +--- + +**Installations from source** + +Configuring the storage driver is done in your registry config YML file created +when you [deployed your docker registry][registry-deploy]. + +Example: + +``` +storage: + s3: + accesskey: 'AKIAKIAKI' + secretkey: 'secret123' + bucket: 'gitlab-registry-bucket-AKIAKIAKI' + cache: + blobdescriptor: inmemory + delete: + enabled: true +``` + ## Storage limitations Currently, there is no storage limitation, which means a user can upload an diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md new file mode 100644 index 00000000000..c212059b9d5 --- /dev/null +++ b/doc/administration/raketasks/project_import_export.md @@ -0,0 +1,33 @@ +# Project import/export + +>**Note:** + - This feature was [introduced][ce-3050] in GitLab 8.9 + - Importing will not be possible if the import instance version is lower + than that of the exporter. + - For existing installations, the project import option has to be enabled in + application settings (`/admin/application_settings`) under 'Import sources'. + - The exports are stored in a temporary [shared directory][tmp] and are deleted + every 24 hours by a specific worker. + +The GitLab Import/Export version can be checked by using: + +```bash +# Omnibus installations +sudo gitlab-rake gitlab:import_export:version + +# Installations from source +bundle exec rake gitlab:import_export:version RAILS_ENV=production +``` + +The current list of DB tables that will get exported can be listed by using: + +```bash +# Omnibus installations +sudo gitlab-rake gitlab:import_export:data + +# Installations from source +bundle exec rake gitlab:import_export:data RAILS_ENV=production +``` + +[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050 +[tmp]: ../../development/shared_files.md diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md new file mode 100644 index 00000000000..e5701b86cf3 --- /dev/null +++ b/doc/administration/troubleshooting/debug.md @@ -0,0 +1,120 @@ +# Debugging Tips + +Sometimes things don't work the way they should. Here are some tips on debugging issues out +in production. + +## The GNU Project Debugger (gdb) + +`gdb` is a must-have tool for debugging issues. To install on Ubuntu/Debian: + +``` +sudo apt-get install gdb +``` + +On CentOS: + +``` +sudo yum install gdb +``` + +## Common Problems + +Many of the tips to diagnose issues below apply to many different situations. We'll use one +concrete example to illustrate what you can do to learn what is going wrong. + +### 502 Gateway Timeout after unicorn spins at 100% CPU + +This error occurs when the Web server times out (default: 60 s) after not +hearing back from the unicorn worker. If the CPU spins to 100% while this in +progress, there may be something taking longer than it should. + +To fix this issue, we first need to figure out what is happening. The +following tips are only recommended if you do NOT mind users being affected by +downtime. Otherwise skip to the next section. + +1. Load the problematic URL +1. Run `sudo gdb -p <PID>` to attach to the unicorn process. +1. In the gdb window, type: + + ``` + call (void) rb_backtrace() + ``` + +1. This forces the process to generate a Ruby backtrace. Check + `/var/log/gitlab/unicorn/unicorn_stderr.log` for the backtace. For example, you may see: + + ```ruby + from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start' + from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `loop' + from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:36:in `block (2 levels) in start' + from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:44:in `sample' + from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `sample_objects' + from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each_with_object' + from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each' + from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `block in sample_objects' + from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `name' + ``` + +1. To see the current threads, run: + + ``` + apply all thread bt + ``` + +1. Once you're done debugging with `gdb`, be sure to detach from the process and exit: + + ``` + detach + exit + ``` + +Note that if the unicorn process terminates before you are able to run these +commands, gdb will report an error. To buy more time, you can always raise the +Unicorn timeout. For omnibus users, you can edit `/etc/gitlab/gitlab.rb` and +increase it from 60 seconds to 300: + +```ruby +unicorn['worker_timeout'] = 300 +``` + +For source installations, edit `config/unicorn.rb`. + +[Reconfigure] GitLab for the changes to take effect. + +[Reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure + +#### Troubleshooting without affecting other users + +The previous section attached to a running unicorn process, and this may have +undesirable effects for users trying to access GitLab during this time. If you +are concerned about affecting others during a production system, you can run a +separate Rails process to debug the issue: + +1. Log in to your GitLab account. +1. Copy the URL that is causing problems (e.g. https://gitlab.com/ABC). +1. Obtain the private token for your user (Profile Settings -> Account). +1. Bring up the GitLab Rails console. For omnibus users, run: + + ```` + sudo gitlab-rails console + ``` + +1. At the Rails console, run: + + ```ruby + [1] pry(main)> app.get '<URL FROM STEP 1>/private_token?<TOKEN FROM STEP 2>' + ``` + + For example: + + ```ruby + [1] pry(main)> app.get 'https://gitlab.com/gitlab-org/gitlab-ce/issues/1?private_token=123456' + ``` + +1. In a new window, run `top`. It should show this ruby process using 100% CPU. Write down the PID. +1. Follow step 2 from the previous section on using gdb. + +# More information + +* [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/) +* [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt) diff --git a/doc/administration/troubleshooting/gdb-stuck-ruby.txt b/doc/administration/troubleshooting/gdb-stuck-ruby.txt new file mode 100644 index 00000000000..13d5dfcffa4 --- /dev/null +++ b/doc/administration/troubleshooting/gdb-stuck-ruby.txt @@ -0,0 +1,142 @@ +# Here's the script I'll use to demonstrate - it just loops forever: + +$ cat test.rb +#!/usr/bin/env ruby + +loop do + sleep 1 +end + +# Now, I'll start the script in the background, and redirect stdout and stderr +# to /dev/null: + +$ ruby ./test.rb >/dev/null 2>/dev/null & +[1] 1343 + +# Next, I'll grab the PID of the script (1343): + +$ ps aux | grep test.rb +vagrant 1343 0.0 0.4 3884 1652 pts/0 S 14:42 0:00 ruby ./test.rb +vagrant 1345 0.0 0.2 4624 852 pts/0 S+ 14:42 0:00 grep --color=auto test.rb + +# Now I start gdb. Note that I'm using sudo here. This may or may not be +# necessary in your setup. I'd try without sudo first, and fall back to adding +# it if the next step fails: + +$ sudo gdb +GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04 +Copyright (C) 2012 Free Software Foundation, Inc. +License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. Type "show copying" +and "show warranty" for details. +This GDB was configured as "i686-linux-gnu". +For bug reporting instructions, please see: +<http://bugs.launchpad.net/gdb-linaro/>. + +# OK, now I'm in gdb, and I want to instruct it to attach to our Ruby process. +# I can do that using the 'attach' command, which takes a PID (the one we +# gathered above): + +(gdb) attach 1343 +Attaching to process 1343 +Reading symbols from /opt/vagrant_ruby/bin/ruby...done. +Reading symbols from /lib/i386-linux-gnu/librt.so.1...(no debugging symbols found)...done. +Loaded symbols for /lib/i386-linux-gnu/librt.so.1 +Reading symbols from /lib/i386-linux-gnu/libdl.so.2...(no debugging symbols found)...done. +Loaded symbols for /lib/i386-linux-gnu/libdl.so.2 +Reading symbols from /lib/i386-linux-gnu/libcrypt.so.1...(no debugging symbols found)...done. +Loaded symbols for /lib/i386-linux-gnu/libcrypt.so.1 +Reading symbols from /lib/i386-linux-gnu/libm.so.6...(no debugging symbols found)...done. +Loaded symbols for /lib/i386-linux-gnu/libm.so.6 +Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done. +Loaded symbols for /lib/i386-linux-gnu/libc.so.6 +Reading symbols from /lib/i386-linux-gnu/libpthread.so.0...(no debugging symbols found)...done. +[Thread debugging using libthread_db enabled] +Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1". +Loaded symbols for /lib/i386-linux-gnu/libpthread.so.0 +Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done. +Loaded symbols for /lib/ld-linux.so.2 +0xb770c424 in __kernel_vsyscall () + +# Great, now gdb is attached to the target process. If the step above fails, try +# going back and running gdb under sudo. The next thing I want to do is gather +# C-level backtraces from all threads in the process. The following command +# stands for 'thread apply all backtrace': + +(gdb) t a a bt + +Thread 1 (Thread 0xb74d76c0 (LWP 1343)): +#0 0xb770c424 in __kernel_vsyscall () +#1 0xb75d7abd in select () from /lib/i386-linux-gnu/libc.so.6 +#2 0x08069c56 in rb_thread_wait_for (time=...) at eval.c:11376 +#3 0x080a20fd in rb_f_sleep (argc=1, argv=0xbf85f490) at process.c:1633 +#4 0x0805e0e2 in call_cfunc (argv=0xbf85f490, argc=1, len=-1, recv=3075299660, func=0x80a20b0 <rb_f_sleep>) + at eval.c:5778 +#5 rb_call0 (klass=3075304600, recv=3075299660, id=9393, oid=9393, argc=1, argv=0xbf85f490, body=0xb74c85a8, flags=2) + at eval.c:5928 +#6 0x0805e35d in rb_call (klass=3075304600, recv=3075299660, mid=9393, argc=1, argv=0xbf85f490, scope=1, + self=<optimized out>) at eval.c:6176 +#7 0x080651ec in rb_eval (self=3075299660, n=0xb74c4e1c) at eval.c:3521 +#8 0x0805c31c in rb_yield_0 (val=6, self=3075299660, klass=<optimized out>, flags=0, avalue=0) at eval.c:5095 +#9 0x0806a1e5 in loop_i () at eval.c:5227 +#10 0x08058dbd in rb_rescue2 (b_proc=0x806a1c0 <loop_i>, data1=0, r_proc=0, data2=0) at eval.c:5491 +#11 0x08058f28 in rb_f_loop () at eval.c:5252 +#12 0x0805e0c1 in call_cfunc (argv=0x0, argc=0, len=0, recv=3075299660, func=0x8058ef0 <rb_f_loop>) at eval.c:5781 +#13 rb_call0 (klass=3075304600, recv=3075299660, id=4121, oid=4121, argc=0, argv=0x0, body=0xb74d4dbc, flags=2) + at eval.c:5928 +#14 0x0805e35d in rb_call (klass=3075304600, recv=3075299660, mid=4121, argc=0, argv=0x0, scope=1, self=<optimized out>) + at eval.c:6176 +#15 0x080651ec in rb_eval (self=3075299660, n=0xb74c4dcc) at eval.c:3521 +#16 0x080662c6 in rb_eval (self=3075299660, n=0xb74c4de0) at eval.c:3236 +#17 0x08068ee4 in ruby_exec_internal () at eval.c:1654 +#18 0x08068f24 in ruby_exec () at eval.c:1674 +#19 0x0806b2cd in ruby_run () at eval.c:1684 +#20 0x08053771 in main (argc=2, argv=0xbf860204, envp=0xbf860210) at main.c:48 + +# C backtraces are sometimes sufficient, but often Ruby backtraces are necessary +# for debugging as well. Ruby has a built-in function called rb_backtrace() that +# we can use to dump out a Ruby backtrace, but it prints to stdout or stderr +# (depending on your Ruby version), which might have been redirected to a file +# or to /dev/null (as in our example) when the process started up. +# +# To get aroundt this, we'll do a little trick and redirect the target process's +# stdout and stderr to the current TTY, so that any output from the process +# will appear directly on our screen. + +# First, let's close the existing file descriptors for stdout and stderr +# (FD 1 and 2, respectively): +(gdb) call (void) close(1) +(gdb) call (void) close(2) + +# Next, we need to figure out the device name for the current TTY: +(gdb) shell tty +/dev/pts/0 + +# OK, now we can pass the device name obtained above to open() and attach +# file descriptors 1 and 2 back to the current TTY with these calls: + +(gdb) call (int) open("/dev/pts/0", 2, 0) +$1 = 1 +(gdb) call (int) open("/dev/pts/0", 2, 0) +$2 = 2 + +# Finally, we call rb_backtrace() in order to dump the Ruby backtrace: + +(gdb) call (void) rb_backtrace() + from ./test.rb:4:in `sleep' + from ./test.rb:4 + from ./test.rb:3:in `loop' + from ./test.rb:3 + +# And here's how we get out of gdb. Once you've quit, you'll probably want to +# clean up the stuck process by killing it. + +(gdb) quit +A debugging session is active. + + Inferior 1 [process 1343] will be detached. + +Quit anyway? (y or n) y +Detaching from program: /opt/vagrant_ruby/bin/ruby, process 1343 +$ diff --git a/doc/api/README.md b/doc/api/README.md index 73f44603688..288f7f9ee69 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -32,6 +32,7 @@ following locations: - [Services](services.md) - [Session](session.md) - [Settings](settings.md) +- [Sidekiq metrics](sidekiq_metrics.md) - [System Hooks](system_hooks.md) - [Tags](tags.md) - [Users](users.md) diff --git a/doc/api/issues.md b/doc/api/issues.md index 0bc82ef9edb..708fc691f67 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -28,7 +28,7 @@ GET /issues?labels=foo,bar&state=opened | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `state` | string | no | Return all issues or just those that are `opened` or `closed`| -| `labels` | string | no | Comma-separated list of label names | +| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned | | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | @@ -83,6 +83,82 @@ Example response: ] ``` +## List group issues + +Get a list of a group's issues. + +``` +GET /groups/:id/issues +GET /groups/:id/issues?state=opened +GET /groups/:id/issues?state=closed +GET /groups/:id/issues?labels=foo +GET /groups/:id/issues?labels=foo,bar +GET /groups/:id/issues?labels=foo,bar&state=opened +GET /groups/:id/issues?milestone=1.0.0 +GET /groups/:id/issues?milestone=1.0.0&state=opened +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a group | +| `state` | string | no | Return all issues or just those that are `opened` or `closed`| +| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned | +| `milestone` | string| no | The milestone title | +| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | +| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | + + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4/issues +``` + +Example response: + +```json +[ + { + "project_id" : 4, + "milestone" : { + "due_date" : null, + "project_id" : 4, + "state" : "closed", + "description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.", + "iid" : 3, + "id" : 11, + "title" : "v3.0", + "created_at" : "2016-01-04T15:31:39.788Z", + "updated_at" : "2016-01-04T15:31:39.788Z" + }, + "author" : { + "state" : "active", + "web_url" : "https://gitlab.example.com/u/root", + "avatar_url" : null, + "username" : "root", + "id" : 1, + "name" : "Administrator" + }, + "description" : "Omnis vero earum sunt corporis dolor et placeat.", + "state" : "closed", + "iid" : 1, + "assignee" : { + "avatar_url" : null, + "web_url" : "https://gitlab.example.com/u/lennie", + "state" : "active", + "username" : "lennie", + "id" : 9, + "name" : "Dr. Luella Kovacek" + }, + "labels" : [], + "id" : 41, + "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.", + "updated_at" : "2016-01-04T15:31:46.176Z", + "created_at" : "2016-01-04T15:31:46.176Z", + "subscribed" : false, + "user_notes_count": 1 + } +] +``` + ## List project issues Get a list of a project's issues. @@ -104,7 +180,7 @@ GET /projects/:id/issues?iid=42 | `id` | integer | yes | The ID of a project | | `iid` | integer | no | Return the issue having the given `iid` | | `state` | string | no | Return all issues or just those that are `opened` or `closed`| -| `labels` | string | no | Comma-separated list of label names | +| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned | | `milestone` | string| no | The milestone title | | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md index d416a826f79..31902e145f6 100644 --- a/doc/api/oauth2.md +++ b/doc/api/oauth2.md @@ -65,6 +65,13 @@ curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user ## Resource Owner Password Credentials +## Deprecation Notice + +1. Starting in GitLab 9.0, the Resource Owner Password Credentials will be *disabled* for users with two-factor authentication turned on. +2. These users can access the API using [personal access tokens] instead. + +--- + In this flow, a token is requested in exchange for the resource owner credentials (username and password). The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the client is part of the device operating system or a highly privileged application), and when other authorization grant types are not @@ -100,3 +107,5 @@ client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http access_token = client.password.get_token('user@example.com', 'sekret') puts access_token.token ``` + +[personal access tokens]: ./README.md#personal-access-tokens diff --git a/doc/api/services.md b/doc/api/services.md index ccfc0fccb7f..32d6e2dea78 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -374,40 +374,6 @@ Get Gemnasium service settings for a project. GET /projects/:id/services/gemnasium ``` -## GitLab CI - -Continuous integration server from GitLab - -### Create/Edit GitLab CI service - -Set GitLab CI service for a project. - -``` -PUT /projects/:id/services/gitlab-ci -``` - -Parameters: - -- `token` (**required**) - GitLab CI project specific token -- `project_url` (**required**) - http://ci.gitlabhq.com/projects/3 -- `enable_ssl_verification` (optional) - Enable SSL verification - -### Delete GitLab CI service - -Delete GitLab CI service for a project. - -``` -DELETE /projects/:id/services/gitlab-ci -``` - -### Get GitLab CI service settings - -Get GitLab CI service settings for a project. - -``` -GET /projects/:id/services/gitlab-ci -``` - ## HipChat Private group chat and IM diff --git a/doc/api/session.md b/doc/api/session.md index 71e93d0bb0a..066a055702d 100644 --- a/doc/api/session.md +++ b/doc/api/session.md @@ -1,5 +1,12 @@ # Session +## Deprecation Notice + +1. Starting in GitLab 9.0, this feature will be *disabled* for users with two-factor authentication turned on. +2. These users can access the API using [personal access tokens] instead. + +--- + You can login with both GitLab and LDAP credentials in order to obtain the private token. @@ -45,3 +52,5 @@ Example response: "private_token": "9koXpg98eAheJpvBs5tK" } ``` + +[personal access tokens]: ./README.md#personal-access-tokens diff --git a/doc/ci/README.md b/doc/ci/README.md index ef72df97ce6..3dd4e2bc230 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -5,6 +5,8 @@ - [Get started with GitLab CI](quick_start/README.md) - [CI examples for various languages](examples/README.md) - [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md) +- [Pipelines and builds](pipelines.md) +- [Environments and deployments](environments.md) - [Learn how `.gitlab-ci.yml` works](yaml/README.md) - [Configure a Runner, the application that runs your builds](runners/README.md) - [Use Docker images with GitLab Runner](docker/using_docker_images.md) diff --git a/doc/ci/environments.md b/doc/ci/environments.md new file mode 100644 index 00000000000..d85b8a34ced --- /dev/null +++ b/doc/ci/environments.md @@ -0,0 +1,58 @@ +# Introduction to environments and deployments + +>**Note:** +Introduced in GitLab 8.9. + +## Environments + +Environments are places where code gets deployed, such as staging or production. +CI/CD [Pipelines] usually have one or more [jobs] that deploy to an environment. +Defining environments in a project's `.gitlab-ci.yml` lets developers track +[deployments] to these environments. + +## Deployments + +Deployments are created when [jobs] deploy versions of code to [environments]. + +## Defining environments + +You can create and delete environments manually in the web interface, but we +recommend that you define your environments in `.gitlab-ci.yml` first, which +will automatically create environments for you after the first deploy. + +The `environment` is just a hint for GitLab that this job actually deploys to +this environment. Each time the job succeeds, a deployment is recorded, +remembering the git SHA and environment. + +Add something like this to your `.gitlab-ci.yml`: +``` +production: + stage: deploy + script: dpl... + environment: production +``` + +See full [documentation](yaml/README.md#environment). + +## Seeing environment status + +You can find the environment list under **Pipelines > Environments** for your +project. You'll see the git SHA and date of the last deployment to each +environment defined. + +>**Note:** +Only deploys that happen after your `.gitlab-ci.yml` is properly configured will +show up in the environments and deployments lists. + +## Seeing deployment history + +Clicking on an environment will show the history of deployments. + +>**Note:** +Only deploys that happen after your `.gitlab-ci.yml` is properly configured will +show up in the environments and deployments lists. + +[Pipelines]: pipelines.md +[jobs]: yaml/README.md#jobs +[environments]: #environments +[deployments]: #deployments diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md new file mode 100644 index 00000000000..48a9f994759 --- /dev/null +++ b/doc/ci/pipelines.md @@ -0,0 +1,38 @@ +# Introduction to pipelines and builds + +>**Note:** +Introduced in GitLab 8.8. + +## Pipelines + +A pipeline is a group of [builds] that get executed in [stages] (batches). All +of the builds in a stage are executed in parallel (if there are enough +concurrent [runners]), and if they all succeed, the pipeline moves on to the +next stage. If one of the builds fails, the next stage is not (usually) +executed. + +## Builds + +Builds are individual runs of [jobs]. Not to be confused with a `build` job or +`build` stage. + +## Defining pipelines + +Pipelines are defined in `.gitlab-ci.yml` by specifying [jobs] that run in +[stages]. + +See full [documentation](yaml/README.md#jobs). + +## Seeing pipeline status + +You can find the current and historical pipeline runs under **Pipelines** for your +project. + +## Seeing build status + +Clicking on a pipeline will show the builds that were run for that pipeline. + +[builds]: #builds +[jobs]: yaml/README.md#jobs +[stages]: yaml/README.md#stages +[runners]: runners/README.md diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 386b8e29fcf..7fa1a478f34 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -4,41 +4,41 @@ is fully integrated into GitLab itself and is [enabled] by default on all projects. -The TL;DR version of how GitLab CI works is the following. - ---- - GitLab offers a [continuous integration][ci] service. If you [add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository, and configure your GitLab project to use a [Runner], then each merge request or -push triggers a build. +push triggers your CI [pipeline]. -The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it -runs three [stages]: `build`, `test`, and `deploy`. +The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it runs +a pipeline with three [stages]: `build`, `test`, and `deploy`. You don't need to +use all three stages; stages with no jobs are simply ignored. If everything runs OK (no non-zero return values), you'll get a nice green checkmark associated with the pushed commit or merge request. This makes it -easy to see whether a merge request will cause any of the tests to fail before +easy to see whether a merge request caused any of the tests to fail before you even look at the code. -Most projects only use GitLab's CI service to run the test suite so that +Most projects use GitLab's CI service to run the test suite so that developers get immediate feedback if they broke something. +There's a growing trend to use continuous delivery and continuous deployment to +automatically deploy tested code to staging and production environments. + So in brief, the steps needed to have a working CI can be summed up to: 1. Add `.gitlab-ci.yml` to the root directory of your repository 1. Configure a Runner -From there on, on every push to your Git repository, the build will be -automagically started by the Runner and will appear under the project's -`/builds` page. +From there on, on every push to your Git repository, the Runner will +automagically start the pipeline and the pipeline will appear under the +project's `/pipelines` page. --- This guide assumes that you: - have a working GitLab instance of version 8.0 or higher or are using - [GitLab.com](https://gitlab.com/users/sign_in) + [GitLab.com](https://gitlab.com) - have a project in GitLab that you would like to use CI for Let's break it down to pieces and work on solving the GitLab CI puzzle. @@ -57,15 +57,14 @@ On any push to your repository, GitLab will look for the `.gitlab-ci.yml` file and start builds on _Runners_ according to the contents of the file, for that commit. -Because `.gitlab-ci.yml` is in the repository, it is version controlled, -old versions still build successfully, forks can easily make use of CI, -branches can have separate builds and you have a single source of truth for CI. -You can read more about the reasons why we are using `.gitlab-ci.yml` -[in our blog about it][blog-ci]. +Because `.gitlab-ci.yml` is in the repository and is version controlled, old +versions still build successfully, forks can easily make use of CI, branches can +have different pipelines and jobs, and you have a single source of truth for CI. +You can read more about the reasons why we are using `.gitlab-ci.yml` [in our +blog about it][blog-ci]. **Note:** `.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file -so you have to pay extra attention to the indentation. Always use spaces, not -tabs. +so you have to pay extra attention to indentation. Always use spaces, not tabs. ### Creating a simple `.gitlab-ci.yml` file @@ -108,7 +107,7 @@ If you want to check whether your `.gitlab-ci.yml` file is valid, there is a Lint tool under the page `/ci/lint` of your GitLab instance. You can also find the link under **Settings > CI settings** in your project. -For more information and a complete `.gitlab-ci.yml` syntax, please check +For more information and a complete `.gitlab-ci.yml` syntax, please read [the documentation on .gitlab-ci.yml](../yaml/README.md). ### Push `.gitlab-ci.yml` to GitLab @@ -122,7 +121,8 @@ git commit -m "Add .gitlab-ci.yml" git push origin master ``` -Now if you go to the **Builds** page you will see that the builds are pending. +Now if you go to the **Pipelines** page you will see that the pipeline is +pending. You can also go to the **Commits** page and notice the little clock icon next to the commit SHA. @@ -138,15 +138,14 @@ Notice that there are two jobs pending which are named after what we wrote in `.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured yet for these builds. -The next step is to configure a Runner so that it picks the pending jobs. +The next step is to configure a Runner so that it picks the pending builds. ## Configuring a Runner -In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`. -A Runner can be a virtual machine, a VPS, a bare-metal machine, a docker -container or even a cluster of containers. GitLab and the Runners communicate -through an API, so the only needed requirement is that the machine on which the -Runner is configured to have Internet access. +In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`. A Runner +can be a virtual machine, a VPS, a bare-metal machine, a docker container or +even a cluster of containers. GitLab and the Runners communicate through an API, +so the only requirement is that the Runner's machine has Internet access. A Runner can be specific to a certain project or serve multiple projects in GitLab. If it serves all projects it's called a _Shared Runner_. @@ -188,12 +187,16 @@ To enable **Shared Runners** you have to go to your project's [Read more on Shared Runners](../runners/README.md). -## Seeing the status of your build +## Seeing the status of your pipeline and builds After configuring the Runner successfully, you should see the status of your last commit change from _pending_ to either _running_, _success_ or _failed_. -You can view all builds, by going to the **Builds** page in your project. +You can view all pipelines by going to the **Pipelines** page in your project. + + + +Or you can view all builds, by going to the **Pipelines > Builds** page.  @@ -238,3 +241,4 @@ CI with various languages. [runner]: ../runners/README.md [enabled]: ../enable_or_disable_ci.md [stages]: ../yaml/README.md#stages +[pipeline]: ../pipelines.md diff --git a/doc/ci/quick_start/img/pipelines_status.png b/doc/ci/quick_start/img/pipelines_status.png Binary files differnew file mode 100644 index 00000000000..6bc97bb739c --- /dev/null +++ b/doc/ci/quick_start/img/pipelines_status.png diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index 400784da617..ddebd987650 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -96,6 +96,12 @@ To register the runner, run the command below and follow instructions: sudo gitlab-ci-multi-runner register ``` +### Lock a specific runner from being enabled for other projects + +You can configure a runner to assign it exclusively to a project. When a +runner is locked this way, it can no longer be enabled for other projects. +This setting is available on each runner in *Project Settings* > *Runners*. + ### Making an existing Shared Runner Specific If you are an admin on your GitLab instance, @@ -128,7 +134,7 @@ the appropriate dependencies to run Rails test suites. ### Prevent runner with tags from picking jobs without tags You can configure a runner to prevent it from picking jobs with tags when -the runnner does not have tags assigned. This setting is available on each +the runner does not have tags assigned. This setting is available on each runner in *Project Settings* > *Runners*. ### Be careful with sensitive information diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 9c98f9c98c6..d2d1b04f893 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -13,32 +13,34 @@ If you want a quick introduction to GitLab CI, follow our **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [.gitlab-ci.yml](#gitlab-ci-yml) - - [image and services](#image-and-services) - - [before_script](#before_script) - - [after_script](#after_script) - - [stages](#stages) - - [types](#types) - - [variables](#variables) - - [cache](#cache) - - [cache:key](#cache-key) + - [image and services](#image-and-services) + - [before_script](#before_script) + - [after_script](#after_script) + - [stages](#stages) + - [types](#types) + - [variables](#variables) + - [cache](#cache) + - [cache:key](#cache-key) - [Jobs](#jobs) - - [script](#script) - - [stage](#stage) - - [job variables](#job-variables) - - [only and except](#only-and-except) - - [tags](#tags) - - [when](#when) - - [environment](#environment) - - [artifacts](#artifacts) - - [artifacts:name](#artifacts-name) - - [artifacts:when](#artifacts-when) - - [artifacts:expire_in](#artifacts-expire_in) - - [dependencies](#dependencies) - - [before_script and after_script](#before_script-and-after_script) + - [script](#script) + - [stage](#stage) + - [only and except](#only-and-except) + - [job variables](#job-variables) + - [tags](#tags) + - [when](#when) + - [environment](#environment) + - [artifacts](#artifacts) + - [artifacts:name](#artifactsname) + - [artifacts:when](#artifactswhen) + - [artifacts:expire_in](#artifactsexpire_in) + - [dependencies](#dependencies) + - [before_script and after_script](#before_script-and-after_script) +- [Git Strategy](#git-strategy) +- [Shallow cloning](#shallow-cloning) - [Hidden jobs](#hidden-jobs) - [Special YAML features](#special-yaml-features) - - [Anchors](#anchors) -- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml) + - [Anchors](#anchors) +- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ciyml) - [Skipping builds](#skipping-builds) - [Examples](#examples) @@ -54,7 +56,7 @@ of your repository and contains definitions of how your project should be built. The YAML file defines a set of jobs with constraints stating when they should be run. The jobs are defined as top-level elements with a name and always have -to contain the `script` clause: +to contain at least the `script` clause: ```yaml job1: @@ -165,9 +167,9 @@ stages: There are also two edge cases worth mentioning: -1. If no `stages` is defined in `.gitlab-ci.yml`, then by default the `build`, +1. If no `stages` are defined in `.gitlab-ci.yml`, then by default the `build`, `test` and `deploy` are allowed to be used as job's stage by default. -2. If a job doesn't specify `stage`, the job is assigned the `test` stage. +2. If a job doesn't specify a `stage`, the job is assigned the `test` stage. ### types @@ -178,9 +180,9 @@ Alias for [stages](#stages). >**Note:** Introduced in GitLab Runner v0.5.0. -GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build -environment. The variables are stored in the git repository and are meant to -store non-sensitive project configuration, for example: +GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the +build environment. The variables are stored in the git repository and are meant +to store non-sensitive project configuration, for example: ```yaml variables: @@ -253,8 +255,8 @@ rspec: - binaries/ ``` -The cache is provided on best effort basis, so don't expect that cache will be -always present. For implementation details please check GitLab Runner. +The cache is provided on a best-effort basis, so don't expect that the cache +will be always present. For implementation details, please check GitLab Runner. #### cache:key @@ -479,10 +481,10 @@ failure. `when` can be set to one of the following values: 1. `on_success` - execute build only when all builds from prior stages - succeeded. This is the default. + succeed. This is the default. 1. `on_failure` - execute build only when at least one build from prior stages - failed. -1. `always` - execute build despite the status of builds from prior stages. + fails. +1. `always` - execute build regardless of the status of builds from prior stages. For example: @@ -530,14 +532,18 @@ The above script will: ### environment >**Note:** -Introduced in GitLab v8.9.0. +Introduced in GitLab 8.9. -`environment` is used to define that job does deployment to specific environment. -This allows to easily track all deployments to your environments straight from GitLab. +`environment` is used to define that a job deploys to a specific environment. +This allows easy tracking of all deployments to your environments straight from +GitLab. -If `environment` is specified and no environment under that name does exist a new one will be created automatically. +If `environment` is specified and no environment under that name exists, a new +one will be created automatically. -The `environment` name must contain only letters, digits, '-' and '_'. +The `environment` name must contain only letters, digits, '-' and '_'. Common +names are `qa`, `staging`, and `production`, but you can use whatever name works +with your workflow. --- @@ -550,7 +556,8 @@ deploy to production: environment: production ``` -The `deploy to production` job will be marked as doing deployment to `production` environment. +The `deploy to production` job will be marked as doing deployment to +`production` environment. ### artifacts @@ -559,10 +566,10 @@ The `deploy to production` job will be marked as doing deployment to `production > - Introduced in GitLab Runner v0.7.0 for non-Windows platforms. > - Windows support was added in GitLab Runner v.1.0.0. > - Currently not all executors are supported. -> - Build artifacts are only collected for successful builds. +> - Build artifacts are only collected for successful builds by default. -`artifacts` is used to specify list of files and directories which should be -attached to build after success. To pass artifacts between different builds, +`artifacts` is used to specify a list of files and directories which should be +attached to the build after success. To pass artifacts between different builds, see [dependencies](#dependencies). Below are some examples. @@ -690,9 +697,9 @@ failure. `artifacts:when` can be set to one of the following values: -1. `on_success` - upload artifacts only when build succeeds. This is the default -1. `on_failure` - upload artifacts only when build fails -1. `always` - upload artifacts despite the build status +1. `on_success` - upload artifacts only when the build succeeds. This is the default. +1. `on_failure` - upload artifacts only when the build fails. +1. `always` - upload artifacts regardless of the build status. --- @@ -711,16 +718,18 @@ job: >**Note:** Introduced in GitLab 8.9 and GitLab Runner v1.3.0. -`artifacts:expire_in` is used to remove uploaded artifacts after specified time. -By default artifacts are stored on GitLab forver. -`expire_in` allows to specify after what time the artifacts should be removed. -The artifacts will expire counting from the moment when they are uploaded and stored on GitLab. +`artifacts:expire_in` is used to delete uploaded artifacts after the specified +time. By default, artifacts are stored on GitLab forever. `expire_in` allows you +to specify how long artifacts should live before they expire, counting from the +time they are uploaded and stored on GitLab. -After artifacts uploading you can use the **Keep** button on build page to keep the artifacts forever. +You can use the **Keep** button on the build page to override expiration and +keep artifacts forever. -Artifacts are removed every hour, but they are not accessible after expire date. +After expiry, artifacts are actually deleted hourly by default (via a cron job), +but they are not accessible after expiry. -The value of `expire_in` is a elapsed time. The example of parsable values: +The value of `expire_in` is an elapsed time. Examples of parseable values: - '3 mins 4 sec' - '2 hrs 20 min' - '2h20min' @@ -732,7 +741,7 @@ The value of `expire_in` is a elapsed time. The example of parsable values: **Example configurations** -To expire artifacts after 1 week from the moment that they are uploaded: +To expire artifacts 1 week after being uploaded: ```yaml job: @@ -814,6 +823,61 @@ job: - execute this after my script ``` +## Git Strategy + +>**Note:** +Introduced in GitLab 8.9 as an experimental feature. May change in future +releases or be removed completely. + +You can set the `GIT_STRATEGY` used for getting recent application code. `clone` +is slower, but makes sure you have a clean directory before every build. `fetch` +is faster. `GIT_STRATEGY` can be specified in the global `variables` section or +in the `variables` section for individual jobs. If it's not specified, then the +default from project settings will be used. + +``` +variables: + GIT_STRATEGY: clone +``` + +or + +``` +variables: + GIT_STRATEGY: fetch +``` + +## Shallow cloning + +>**Note:** +Introduced in GitLab 8.9 as an experimental feature. May change in future +releases or be removed completely. + +You can specify the depth of fetching and cloning using `GIT_DEPTH`. This allows +shallow cloning of the repository which can significantly speed up cloning for +repositories with a large number of commits or old, large binaries. The value is +passed to `git fetch` and `git clone`. + +>**Note:** +If you use a depth of 1 and have a queue of builds or retry +builds, jobs may fail. + +Since Git fetching and cloning is based on a ref, such as a branch name, runners +can't clone a specific commit SHA. If there are multiple builds in the queue, or +you are retrying an old build, the commit to be tested needs to be within the +git history that is cloned. Setting too small a value for `GIT_DEPTH` can make +it impossible to run these old commits. You will see `unresolved reference` in +build logs. You should then reconsider changing `GIT_DEPTH` to a higher value. + +Builds that rely on `git describe` may not work correctly when `GIT_DEPTH` is +set since only part of the git history is present. + +To fetch or clone only the last 3 commits: +``` +variables: + GIT_DEPTH: "3" +``` + ## Hidden jobs >**Note:** @@ -970,8 +1034,8 @@ You can find the link under `/ci/lint` of your gitlab instance. ## Skipping builds -If your commit message contains `[ci skip]`, the commit will be created but the -builds will be skipped. +If your commit message contains `[ci skip]` or `[skip ci]`, using any +capitalization, the commit will be created but the builds will be skipped. ## Examples diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md index 194b8e00299..4620bb2dcde 100644 --- a/doc/customization/issue_closing.md +++ b/doc/customization/issue_closing.md @@ -8,7 +8,7 @@ the matched text will be closed. This happens when the commit is pushed to a pro When not specified, the default `issue_closing_pattern` as shown below will be used: ```bash -((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+) +((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+) ``` Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that matches a reference to a local issue (`#123`), cross-project issue (`group/project#123`) or a link to an issue (`https://gitlab.example.com/group/project/issues/123`). diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 12e33406cb6..33fd50f4c11 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -52,7 +52,9 @@ To serve repositories over SSH there's an add-on application called gitlab-shell ### Components - + + +_[edit diagram (for GitLab team members only)](https://docs.google.com/drawings/d/1fBzAyklyveF-i-2q-OHUIqDkYfjjxC4mq5shwKSZHLs/edit)_ A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs. diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index f5d97179f8a..975bb82c37d 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -183,6 +183,62 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to (`workflow/lfs/lfs_administration.md`). +## Configuration documentation for source and Omnibus installations + +GitLab currently officially supports two installation methods: installations +from source and Omnibus packages installations. + +Whenever there is a setting that is configurable for both installation methods, +prefer to document it in the CE docs to avoid duplication. + +Configuration settings include: + +- settings that touch configuration files in `config/` +- NGINX settings and settings in `lib/support/` in general + +When there is a list of steps to perform, usually that entails editing the +configuration file and reconfiguring/restarting GitLab. In such case, follow +the style below as a guide: + +```` +**For Omnibus installations** + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + external_url "https://gitlab.example.com" + ``` + +1. Save the file and [reconfigure] GitLab for the changes to take effect. + +--- + +**For installations from source** + +1. Edit `config/gitlab.yml`: + + ```yaml + gitlab: + host: "gitlab.example.com" + ``` + +1. Save the file and [restart] GitLab for the changes to take effect. + + +[reconfigure]: path/to/administration/gitlab_restart.md#omnibus-gitlab-reconfigure +[restart]: path/to/administration/gitlab_restart.md#installations-from-source +```` + +In this case: + +- before each step list the installation method is declared in bold +- three dashes (`---`) are used to create an horizontal line and separate the + two methods +- the code blocks are indented one or more spaces under the list item to render + correctly +- different highlighting languages are used for each config in the code block +- the [references](#references) guide is used for reconfigure/restart + ## API Here is a list of must-have items. Use them in the exact order that appears diff --git a/doc/development/gitlab_architecture_diagram.png b/doc/development/gitlab_architecture_diagram.png Binary files differnew file mode 100644 index 00000000000..9ab7ffd3c7b --- /dev/null +++ b/doc/development/gitlab_architecture_diagram.png diff --git a/doc/development/gitlab_diagram_overview.png b/doc/development/gitlab_diagram_overview.png Binary files differdeleted file mode 100644 index d9b9eed3d8f..00000000000 --- a/doc/development/gitlab_diagram_overview.png +++ /dev/null diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md index 21078c8d6f9..9d7fe7440d2 100644 --- a/doc/development/gotchas.md +++ b/doc/development/gotchas.md @@ -46,7 +46,7 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9 Using the inline `:coffee` or `:coffeescript` Haml filters comes with a performance overhead. -_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-5-stable/config/initializers/haml.rb) +_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/hamlit.rb) in an initializer._ ### Further reading diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 8a7547e5322..e2ca46504e7 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -37,7 +37,6 @@ First, you need to provide information on whether the migration can be applied: For example: ``` -# rubocop:disable all # Migration type: online without errors (works on previous version and new one) class MyMigration < ActiveRecord::Migration ... diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 6d04b9590e6..41685c7ee41 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -33,3 +33,23 @@ bundle exec rake gitlab:generate_docs ``` bundle exec rake services:doc ``` + +## Updating Emoji Digests + +To update the Emoji digests file (used for Emoji autocomplete) you must run the +following: + +``` +bundle exec rake gemojione:digests +``` + +This will update the file `fixtures/emojis/digests.json` based on the currently +available Emoji. + +## Emoji Sprites + +Generating a sprite file containing all the Emoji can be done by running: + +``` +bundle exec rake gemojione:sprite +``` diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md index 3625c4191b8..a6d22e5a04a 100644 --- a/doc/downgrade_ee_to_ce/README.md +++ b/doc/downgrade_ee_to_ce/README.md @@ -44,13 +44,13 @@ to avoid getting this error, you need to remove all instances of the **Omnibus Installation** ``` -$ sudo gitlab-rails runner "Service.where(type: 'JenkinsService').delete_all" +$ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" ``` **Source Installation** ``` -$ bundle exec rails runner "Service.where(type: 'JenkinsService').delete_all" production +$ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" production ``` ## Downgrade to CE diff --git a/doc/install/installation.md b/doc/install/installation.md index d9290b1fa76..dc8d9c65535 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -391,6 +391,10 @@ GitLab Shell is an SSH access and repository management software developed speci ### Install gitlab-workhorse +GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). +If you are not using Linux you may have to run `gmake` instead of +`make` below. + cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 09c6211b3ab..a65ac8a5f79 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -52,7 +52,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim ### CPU -- 1 core works supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core +- 1 core supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core - **2 cores** is the **recommended** number of cores and supports up to 500 users - 4 cores supports up to 2,000 users - 8 cores supports up to 5,000 users diff --git a/doc/intro/README.md b/doc/intro/README.md index 382d10aaf40..1850031eb26 100644 --- a/doc/intro/README.md +++ b/doc/intro/README.md @@ -12,7 +12,7 @@ Create projects and groups. Create issues, labels, milestones, cast your vote, and review issues. - [Create a new issue](../gitlab-basics/create-issue.md) -- [Assign labels to issues](../workflow/labels.md) +- [Assign labels to issues](../user/project/labels.md) - [Use milestones as an overview of your project's tracker](../workflow/milestones.md) - [Use voting to express your like/dislike to issues and merge requests](../workflow/award_emoji.md) diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 168bd85c26a..7947b0fedc4 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -44,70 +44,32 @@ on a separate server) ## Apply retention policies and create continuous queries -If you intend to import the GitLab provided Grafana dashboards, you will need -to copy and run a set of queries against InfluxDB to create the needed data -sets. +If you intend to import the GitLab provided Grafana dashboards, you will need to +set up the right retention policies and continuous queries. The easiest way of +doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management) +repository. -On the InfluxDB server, run the following command, substituting your InfluxDB -user and password: +To use this repository you must first clone it: -```bash -influxdb --username admin -password super_secret +``` +git clone https://gitlab.com/gitlab-org/influxdb-management.git +cd influxdb-management ``` -This will drop you in to an InfluxDB interactive session. Copy the entire -contents below and paste it in to the interactive session: +Next you must install the required dependencies: ``` -CREATE RETENTION POLICY default ON gitlab DURATION 1h REPLICATION 1 DEFAULT -CREATE RETENTION POLICY downsampled ON gitlab DURATION 7d REPLICATION 1 -CREATE CONTINUOUS QUERY grape_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY grape_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY grape_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY grape_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY grape_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method, action END; -CREATE CONTINUOUS QUERY grape_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method END; -CREATE CONTINUOUS QUERY grape_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY grape_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY grape_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY grape_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY rails_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.rails_file_descriptor_counts FROM gitlab."default".rails_file_descriptors GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY rails_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.rails_gc_counts FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY rails_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.rails_gc_timings FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY rails_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY rails_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY rails_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY rails_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY rails_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.rails_memory_usage_overall FROM gitlab."default".rails_memory_usage GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY rails_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method, action END; -CREATE CONTINUOUS QUERY rails_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method END; -CREATE CONTINUOUS QUERY rails_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_overall FROM gitlab."default".rails_object_counts GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY rails_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_per_type FROM gitlab."default".rails_object_counts GROUP BY time(1m), type END; -CREATE CONTINUOUS QUERY rails_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY rails_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY rails_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY rails_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY rails_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_view_timings_per_action_and_view FROM gitlab."default".rails_views WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action, view END; -CREATE CONTINUOUS QUERY sidekiq_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.sidekiq_file_descriptor_counts FROM gitlab."default".sidekiq_file_descriptors GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY sidekiq_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.sidekiq_gc_counts FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY sidekiq_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.sidekiq_gc_timings FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY sidekiq_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_git_timings_per_action FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_overall FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_per_action FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY sidekiq_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_markdown_timings_overall FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^Banzai/ GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY sidekiq_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.sidekiq_memory_usage_overall FROM gitlab."default".sidekiq_memory_usage GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_action_and_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method, action END; -CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method END; -CREATE CONTINUOUS QUERY sidekiq_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_overall FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY sidekiq_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_per_type FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m), type END; -CREATE CONTINUOUS QUERY sidekiq_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY sidekiq_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY sidekiq_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END; -CREATE CONTINUOUS QUERY sidekiq_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END; -CREATE CONTINUOUS QUERY sidekiq_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_view_timings_per_action_and_view FROM gitlab."default".sidekiq_views GROUP BY time(1m), action, view END; -CREATE CONTINUOUS QUERY web_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.web_transaction_counts_overall FROM gitlab."default".rails_transactions GROUP BY time(1m) END; +gem install bundler +bundle install ``` +Now you must configure the repository by first copying `.env.example` to `.env` +and then editing the `.env` file to contain the correct InfluxDB settings. Once +configured you can simply run `bundle exec rake` and the InfluxDB database will +be configured for you. + +For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md). + ## Import Dashboards You can now import a set of default dashboards that will give you a good diff --git a/doc/project_services/emails_on_push.md b/doc/project_services/emails_on_push.md new file mode 100644 index 00000000000..2f9f36f962e --- /dev/null +++ b/doc/project_services/emails_on_push.md @@ -0,0 +1,17 @@ +## Enabling emails on push + +To receive email notifications for every change that is pushed to the project, visit +your project's **Settings > Services > Emails on push** and activate the service. + +In the _Recipients_ area, provide a list of emails separated by commas. + +You can configure any of the following settings depending on your preference. + ++ **Push events** - Email will be triggered when a push event is recieved ++ **Tag push events** - Email will be triggered when a tag is created and pushed ++ **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`). ++ **Disable code diffs** - Don't include possibly sensitive code diffs in notification body. + +--- + + diff --git a/doc/project_services/img/emails_on_push_service.png b/doc/project_services/img/emails_on_push_service.png Binary files differnew file mode 100644 index 00000000000..cd6f79ad1eb --- /dev/null +++ b/doc/project_services/img/emails_on_push_service.png diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md index a5af620d9be..f81a035f70b 100644 --- a/doc/project_services/project_services.md +++ b/doc/project_services/project_services.md @@ -33,7 +33,7 @@ further configuration instructions and details. Contributions are welcome. | Campfire | Simple web-based real-time group chat | | Custom Issue Tracker | Custom issue tracker | | Drone CI | Continuous Integration platform built on Docker, written in Go | -| Emails on push | Email the commits and diff of each push to a list of recipients | +| [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients | | External Wiki | Replaces the link to the internal wiki with a link to an external wiki | | Flowdock | Flowdock is a collaboration web app for technical teams | | Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities | diff --git a/doc/update/2.6-to-3.0.md b/doc/update/2.6-to-3.0.md index 4827ef9501a..fb70eaacbc9 100644 --- a/doc/update/2.6-to-3.0.md +++ b/doc/update/2.6-to-3.0.md @@ -13,6 +13,10 @@ git fetch origin git checkout v3.0.3 +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u gitlab -H vim Gemfile # Install libs sudo -u gitlab bundle install --without development test postgres diff --git a/doc/update/2.9-to-3.0.md b/doc/update/2.9-to-3.0.md index f4a997a8c5e..ce46b57c09a 100644 --- a/doc/update/2.9-to-3.0.md +++ b/doc/update/2.9-to-3.0.md @@ -13,6 +13,11 @@ sudo -u gitlab -H git fetch origin sudo -u gitlab -H git checkout v3.0.3 +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u gitlab -H vim Gemfile + # Install gems sudo -u gitlab -H bundle install --without development test postgres diff --git a/doc/update/3.0-to-3.1.md b/doc/update/3.0-to-3.1.md index a30485c42f7..6ac83f3b60d 100644 --- a/doc/update/3.0-to-3.1.md +++ b/doc/update/3.0-to-3.1.md @@ -25,6 +25,11 @@ sudo -u gitlab -H git checkout v3.1.0 # Install new charlock_holmes sudo gem install charlock_holmes --version '0.6.9' +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u gitlab -H vim Gemfile + # Install gems for MySQL sudo -u gitlab -H bundle install --without development test postgres sqlite diff --git a/doc/update/3.1-to-4.0.md b/doc/update/3.1-to-4.0.md index f1ef4df4744..df53ed6de83 100644 --- a/doc/update/3.1-to-4.0.md +++ b/doc/update/3.1-to-4.0.md @@ -26,6 +26,11 @@ I wrote a bash script which will do it automatically for you. Just make sure all sudo -u gitlab -H git fetch sudo -u gitlab -H git checkout 4-0-stable +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u gitlab -H vim Gemfile + # Install gems for MySQL sudo -u gitlab -H bundle install --without development test postgres diff --git a/doc/update/4.0-to-4.1.md b/doc/update/4.0-to-4.1.md index d89d5235917..c163bfd348d 100644 --- a/doc/update/4.0-to-4.1.md +++ b/doc/update/4.0-to-4.1.md @@ -22,6 +22,11 @@ cd /home/gitlab/gitlab/ sudo -u gitlab -H git fetch sudo -u gitlab -H git checkout 4-1-stable +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u gitlab -H vim Gemfile + # Install gems for MySQL sudo -u gitlab -H bundle install --without development test postgres diff --git a/doc/update/4.1-to-4.2.md b/doc/update/4.1-to-4.2.md index 6fe4412ff90..97367c5f347 100644 --- a/doc/update/4.1-to-4.2.md +++ b/doc/update/4.1-to-4.2.md @@ -17,7 +17,15 @@ sudo -u gitlab -H git fetch sudo -u gitlab -H git checkout 4-2-stable -# Install libs +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u gitlab -H vim Gemfile + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u gitlab -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u gitlab -H bundle install --without development test postgres --deployment # update db diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md index f9faf65f952..ee6de51c923 100644 --- a/doc/update/4.2-to-5.0.md +++ b/doc/update/4.2-to-5.0.md @@ -85,8 +85,17 @@ sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml # edit it sudo -u git -H vim config/gitlab.yml +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment + sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:shell:build_missing_projects RAILS_ENV=production diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md index 9fbd1f88515..f0fddcf83af 100644 --- a/doc/update/5.0-to-5.1.md +++ b/doc/update/5.0-to-5.1.md @@ -42,7 +42,17 @@ cd /home/git/gitlab sudo rm tmp/sockets/gitlab.socket sudo -u git -H cp config/puma.rb.example config/puma.rb +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment + sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake migrate_merge_requests RAILS_ENV=production sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md index cf9c4e4f770..625fcc33852 100644 --- a/doc/update/5.1-to-5.2.md +++ b/doc/update/5.1-to-5.2.md @@ -40,12 +40,28 @@ sudo -u git -H git checkout v1.4.0 ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -#PostgreSQL +# PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md index 97a98ede070..547d453914c 100644 --- a/doc/update/5.1-to-5.4.md +++ b/doc/update/5.1-to-5.4.md @@ -37,12 +37,28 @@ sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulner ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -#PostgreSQL +# PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md index a3fdd92bd2f..c992c69678e 100644 --- a/doc/update/5.1-to-6.0.md +++ b/doc/update/5.1-to-6.0.md @@ -137,12 +137,28 @@ sudo apt-get install python-docutils ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -#PostgreSQL +# PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake migrate_groups RAILS_ENV=production sudo -u git -H bundle exec rake migrate_global_projects RAILS_ENV=production diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md index 27613aeda07..c5254f6fb0c 100644 --- a/doc/update/5.2-to-5.3.md +++ b/doc/update/5.2-to-5.3.md @@ -31,12 +31,28 @@ sudo -u git -H git checkout 5-3-stable ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -#PostgreSQL +# PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md index 577b9a585ff..c4a6146dcda 100644 --- a/doc/update/5.3-to-5.4.md +++ b/doc/update/5.3-to-5.4.md @@ -35,12 +35,28 @@ sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulner ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -#PostgreSQL +# PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md index d9c6d9bfb91..f0fee634322 100644 --- a/doc/update/5.4-to-6.0.md +++ b/doc/update/5.4-to-6.0.md @@ -73,12 +73,28 @@ sudo apt-get install python-docutils ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment # PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake migrate_groups RAILS_ENV=production sudo -u git -H bundle exec rake migrate_global_projects RAILS_ENV=production diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md index c5eba1c01c4..409faf30902 100644 --- a/doc/update/6.0-to-6.1.md +++ b/doc/update/6.0-to-6.1.md @@ -50,13 +50,28 @@ sudo -u git -H git checkout v1.7.9 ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -#PostgreSQL -sudo -u git -H bundle install --without development test mysql --deployment +# PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment +# Install libs (with deployment this time) +sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md index a534528108a..150c7ae1c83 100644 --- a/doc/update/6.1-to-6.2.md +++ b/doc/update/6.1-to-6.2.md @@ -45,13 +45,28 @@ sudo apt-get install logrotate ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -#PostgreSQL -sudo -u git -H bundle install --without development test mysql --deployment +# PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment +# Install libs (with deployment this time) +sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md index b08ebde0808..b96dfb8add7 100644 --- a/doc/update/6.2-to-6.3.md +++ b/doc/update/6.2-to-6.3.md @@ -40,13 +40,28 @@ The gitlab-shell config changed recently, so check for config file changes and m ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment # PostgreSQL -sudo -u git -H bundle install --without development test mysql --deployment +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) +sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production diff --git a/doc/update/6.3-to-6.4.md b/doc/update/6.3-to-6.4.md index 951d92dfeb5..37028be055f 100644 --- a/doc/update/6.3-to-6.4.md +++ b/doc/update/6.3-to-6.4.md @@ -36,13 +36,28 @@ sudo -u git -H git checkout v1.8.0 ```bash cd /home/git/gitlab +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + # MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment # PostgreSQL -sudo -u git -H bundle install --without development test mysql --deployment +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) +sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production diff --git a/doc/update/6.4-to-6.5.md b/doc/update/6.4-to-6.5.md index 0dae9a9fe59..982381a4db0 100644 --- a/doc/update/6.4-to-6.5.md +++ b/doc/update/6.4-to-6.5.md @@ -46,13 +46,28 @@ sudo -u git -H git checkout v1.8.0 ```bash cd /home/git/gitlab -# MySQL installations (note: the line below states '--without ... postgres') +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + +# MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -# PostgreSQL installations (note: the line below states '--without ... mysql') -sudo -u git -H bundle install --without development test mysql --deployment +# PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment +# Install libs (with deployment this time) +sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production diff --git a/doc/update/6.5-to-6.6.md b/doc/update/6.5-to-6.6.md index c24e83eb006..bbed2b30215 100644 --- a/doc/update/6.5-to-6.6.md +++ b/doc/update/6.5-to-6.6.md @@ -46,12 +46,28 @@ sudo -u git -H git checkout v1.8.0 ```bash cd /home/git/gitlab -# MySQL installations (note: the line below states '--without ... postgres') +# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0 +# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to +# `gem "modernizr-rails", "2.7.1"`` +sudo -u git -H vim Gemfile + +# MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -# PostgreSQL installations (note: the line below states '--without ... mysql') +# PostgreSQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production diff --git a/doc/update/6.6-to-6.7.md b/doc/update/6.6-to-6.7.md index b4298c93429..8e82942a1a0 100644 --- a/doc/update/6.6-to-6.7.md +++ b/doc/update/6.6-to-6.7.md @@ -46,13 +46,23 @@ sudo -u git -H git checkout v1.9.1 ```bash cd /home/git/gitlab -# MySQL installations (note: the line below states '--without ... postgres') +# MySQL + +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test postgres --no-deployment + +# Install libs (with deployment this time) sudo -u git -H bundle install --without development test postgres --deployment -# PostgreSQL installations (note: the line below states '--without ... mysql') -sudo -u git -H bundle install --without development test mysql --deployment +# PostgreSQL +# Run a bundle install without deployment to generate the new Gemfile +sudo -u git -H bundle install --without development test mysql --no-deployment + +# Install libs (with deployment this time) +sudo -u git -H bundle install --without development test mysql --deployment +# Both MySQL and PostgreSQL # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md index c45fc9340ea..f170a0021b7 100644 --- a/doc/update/6.x-or-7.x-to-7.14.md +++ b/doc/update/6.x-or-7.x-to-7.14.md @@ -147,12 +147,15 @@ sudo -u git -H bundle install --without development test postgres --deployment # PostgreSQL installations (note: the line below states '--without ... mysql') sudo -u git -H bundle install --without development test mysql --deployment -# Run database migrations -sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production +# Run database migrations from 6.0 to 6.1 +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production VERSION=20130909132950 # Enable internal issue IDs (introduced in GitLab 6.1) sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production +# Run left database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + # Clean up assets and cache sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md index bb463d43a7c..cb66ef920bb 100644 --- a/doc/update/8.6-to-8.7.md +++ b/doc/update/8.6-to-8.7.md @@ -45,7 +45,7 @@ sudo -u git -H git checkout 8-7-stable-ee ```bash cd /home/git/gitlab-shell -sudo -u git -H git fetch --all --tags +sudo -u git -H git fetch --tags sudo -u git -H git checkout v2.7.2 ``` diff --git a/doc/user/project/highlighting.md b/doc/user/project/highlighting.md new file mode 100644 index 00000000000..73a2d176b54 --- /dev/null +++ b/doc/user/project/highlighting.md @@ -0,0 +1,31 @@ +[Rouge]: https://rubygems.org/gems/rouge + +# Syntax Highlighting + +GitLab provides syntax highlighting on all files and snippets through the [Rouge][] rubygem. It will try to guess what language to use based on the file extension, which most of the time is sufficient. + +If GitLab is guessing wrong, you can override its choice of language using the `gitlab-language` attribute in `.gitattributes`. For example, if you are working in a Prolog project and using the `.pl` file extension (which would normally be highlighted as Perl), you can add the following to your `.gitattributes` file: + +``` conf +*.pl gitlab-language=prolog +``` + +When you check in and push that change, all `*.pl` files in your project will be highlighted as Prolog. + +The paths here are simply git's builtin [`.gitattributes` interface](https://git-scm.com/docs/gitattributes). So, if you were to invent a file format called a `Nicefile` at the root of your project that used ruby syntax, all you need is: + +``` conf +/Nicefile gitlab-language=ruby +``` + +To disable highlighting entirely, use `gitlab-language=text`. Lots more fun shenanigans are available through CGI options, such as: + +``` conf +# json with erb in it +/my-cool-file gitlab-language=erb?parent=json + +# an entire file of highlighting errors! +/other-file gitlab-language=text?token=Error +``` + +Please note that these configurations will only take effect when the `.gitattributes` file is in your default branch (usually `master`). diff --git a/doc/user/project/img/labels_assign_label_in_new_issue.png b/doc/user/project/img/labels_assign_label_in_new_issue.png Binary files differnew file mode 100644 index 00000000000..e32a35f7cda --- /dev/null +++ b/doc/user/project/img/labels_assign_label_in_new_issue.png diff --git a/doc/user/project/img/labels_assign_label_sidebar.png b/doc/user/project/img/labels_assign_label_sidebar.png Binary files differnew file mode 100644 index 00000000000..799443af889 --- /dev/null +++ b/doc/user/project/img/labels_assign_label_sidebar.png diff --git a/doc/user/project/img/labels_assign_label_sidebar_saved.png b/doc/user/project/img/labels_assign_label_sidebar_saved.png Binary files differnew file mode 100644 index 00000000000..e7d8d69e60e --- /dev/null +++ b/doc/user/project/img/labels_assign_label_sidebar_saved.png diff --git a/doc/user/project/img/labels_default.png b/doc/user/project/img/labels_default.png Binary files differnew file mode 100644 index 00000000000..ee0c9f889ad --- /dev/null +++ b/doc/user/project/img/labels_default.png diff --git a/doc/user/project/img/labels_description_tooltip.png b/doc/user/project/img/labels_description_tooltip.png Binary files differnew file mode 100644 index 00000000000..0d1e3e091fb --- /dev/null +++ b/doc/user/project/img/labels_description_tooltip.png diff --git a/doc/user/project/img/labels_filter.png b/doc/user/project/img/labels_filter.png Binary files differnew file mode 100644 index 00000000000..ed622be2d93 --- /dev/null +++ b/doc/user/project/img/labels_filter.png diff --git a/doc/user/project/img/labels_filter_by_priority.png b/doc/user/project/img/labels_filter_by_priority.png Binary files differnew file mode 100644 index 00000000000..c5a9e20919b --- /dev/null +++ b/doc/user/project/img/labels_filter_by_priority.png diff --git a/doc/user/project/img/labels_generate.png b/doc/user/project/img/labels_generate.png Binary files differnew file mode 100644 index 00000000000..9579be4e231 --- /dev/null +++ b/doc/user/project/img/labels_generate.png diff --git a/doc/user/project/img/labels_new_label.png b/doc/user/project/img/labels_new_label.png Binary files differnew file mode 100644 index 00000000000..a916d3dceb5 --- /dev/null +++ b/doc/user/project/img/labels_new_label.png diff --git a/doc/user/project/img/labels_new_label_on_the_fly.png b/doc/user/project/img/labels_new_label_on_the_fly.png Binary files differnew file mode 100644 index 00000000000..80cc434239e --- /dev/null +++ b/doc/user/project/img/labels_new_label_on_the_fly.png diff --git a/doc/user/project/img/labels_new_label_on_the_fly_create.png b/doc/user/project/img/labels_new_label_on_the_fly_create.png Binary files differnew file mode 100644 index 00000000000..c41090945eb --- /dev/null +++ b/doc/user/project/img/labels_new_label_on_the_fly_create.png diff --git a/doc/user/project/img/labels_prioritize.png b/doc/user/project/img/labels_prioritize.png Binary files differnew file mode 100644 index 00000000000..8dfe72cf826 --- /dev/null +++ b/doc/user/project/img/labels_prioritize.png diff --git a/doc/user/project/img/labels_subscribe.png b/doc/user/project/img/labels_subscribe.png Binary files differnew file mode 100644 index 00000000000..ea3db2bc0cf --- /dev/null +++ b/doc/user/project/img/labels_subscribe.png diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md new file mode 100644 index 00000000000..4258185b7d0 --- /dev/null +++ b/doc/user/project/labels.md @@ -0,0 +1,147 @@ +# Labels + +Labels provide an easy way to categorize the issues or merge requests based on +descriptive titles like `bug`, `documentation` or any other text you feel like +it. They can have different colors, a description, and are visible throughout +the issue tracker or inside each issue individually. + +With labels, you can navigate the issue tracker and filter any bloated +information to visualize only the issues you are interested in. Let's see how +that works. + +## Create new labels + +>**Note:** +A permission level of `Developer` or higher is required in order to manage +labels. + +Head over a single project and navigate to **Issues > Labels**. + +The first time you visit this page, you'll notice that there are no labels +created yet. + + + +--- + +You can skip that and create a new label or click that link and GitLab will +generate a set of predefined labels for you. There 8 default generated labels +in total and you can see them in the screenshot below. + + + +--- + +You can see that from the labels page you can have an overview of the number of +issues and merge requests assigned to each label. + +Creating a new label from scratch is as easy as pressing the **New label** +button. From there on you can choose the name, give it an optional description, +a color and you are set. + +When you are ready press the **Create label** button to create the new label. + + + +## Prioritize labels + +>**Notes:** + - This feature was introduced in GitLab 8.9. + - Priority sorting is based on the highest priority label only. This might + change in the future, follow the discussion in + https://gitlab.com/gitlab-org/gitlab-ce/issues/18554. + +Prioritized labels are like any other label, but sorted by priority. This allows +you to sort issues and merge requests by priority. + +To prioritize labels, navigate to your project's **Issues > Labels** and click +on the star icon next to them to put them in the priority list. Click on the +star icon again to remove them from the list. + +From there, you can drag them around to set the desired priority. Priority is +set from high to low with an ascending order. Labels with no priority, count as +having their priority set to null. + + + +Now that you have labels prioritized, you can use the 'Priority' filter in the +issues or merge requests tracker. Those with the highest priority label, will +appear on top. + + + +## Subscribe to labels + +If you don’t want to miss issues or merge requests that are important to you, +simply subscribe to a label. You’ll get notified whenever the label gets added +to an issue or merge request, making sure you don’t miss a thing. + +Go to your project's **Issues > Labels** area, find the label(s) you want to +subscribe to and click on the eye icon. Click again to unsubscribe. + + + +If you work on a large or popular project, try subscribing only to the labels +that are relevant to you. You’ll notice it’ll be much easier to focus on what’s +important. + +## Create a new label right from the issue tracker + +>**Note:** +This feature was introduced in GitLab 8.6. + +There are times when you are already in the issue tracker searching for a +label, only to realize it doesn't exist. Instead of going to the **Labels** +page and being distracted from your original purpose, you can create new +labels on the fly. + +Select **Create new** from the labels dropdown list, provide a name, pick a +color and hit **Create**. + + + + +## Assigning labels to issues and merge requests + +There are generally two ways to assign a label to an issue or merge request. + +You can assign a label when you first create or edit an issue or merge request. + + + +--- + +The second way is by using the right sidebar when inside an issue or merge +request. Expand it and hit **Edit** in the labels area. Start typing the name +of the label you are looking for to narrow down the list, and select it. You +can add more than one labels at once. When done, click outside the sidebar area +for the changes to take effect. + + + + +--- + +To remove labels, expand the left sidebar and unmark them from the labels list. +Simple as that. + +## Use labels to filter issues + +Once you start adding labels to your issues, you'll see the benefit of it. +Labels can have several uses, one of them being the quick filtering of issues +or merge requests. + +Pick an existing label from the dropdown _Label_ menu or click on an existing +label from the issue tracker. In the latter case, you also get to see the +label description like shown below. + + + +--- + +And if you added a description to your label, you can see it by hovering your +mouse over the label in the issue tracker or wherever else the label is +rendered. + + + diff --git a/doc/user/project/settings/img/import_export_download_export.png b/doc/user/project/settings/img/import_export_download_export.png Binary files differnew file mode 100644 index 00000000000..a2f7f0085c1 --- /dev/null +++ b/doc/user/project/settings/img/import_export_download_export.png diff --git a/doc/user/project/settings/img/import_export_export_button.png b/doc/user/project/settings/img/import_export_export_button.png Binary files differnew file mode 100644 index 00000000000..1f7bdd21b0d --- /dev/null +++ b/doc/user/project/settings/img/import_export_export_button.png diff --git a/doc/user/project/settings/img/import_export_mail_link.png b/doc/user/project/settings/img/import_export_mail_link.png Binary files differnew file mode 100644 index 00000000000..c123f83eb8e --- /dev/null +++ b/doc/user/project/settings/img/import_export_mail_link.png diff --git a/doc/user/project/settings/img/import_export_new_project.png b/doc/user/project/settings/img/import_export_new_project.png Binary files differnew file mode 100644 index 00000000000..b3a7f201018 --- /dev/null +++ b/doc/user/project/settings/img/import_export_new_project.png diff --git a/doc/user/project/settings/img/import_export_select_file.png b/doc/user/project/settings/img/import_export_select_file.png Binary files differnew file mode 100644 index 00000000000..f31832af3e1 --- /dev/null +++ b/doc/user/project/settings/img/import_export_select_file.png diff --git a/doc/user/project/settings/img/settings_edit_button.png b/doc/user/project/settings/img/settings_edit_button.png Binary files differnew file mode 100644 index 00000000000..3c0cee536de --- /dev/null +++ b/doc/user/project/settings/img/settings_edit_button.png diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md new file mode 100644 index 00000000000..38e9786123d --- /dev/null +++ b/doc/user/project/settings/import_export.md @@ -0,0 +1,73 @@ +# Project import/export + +>**Notes:** + - This feature was [introduced][ce-3050] in GitLab 8.9 + - Importing will not be possible if the import instance version is lower + than that of the exporter. + - For existing installations, the project import option has to be enabled in + application settings (`/admin/application_settings`) under 'Import sources'. + Ask your administrator if you don't see the **GitLab export** button when + creating a new project. + - You can find some useful raketasks if you are an administrator in the + [import_export](../../../administration/raketasks/project_import_export.md) + raketask. + - The exports are stored in a temporary [shared directory][tmp] and are deleted + every 24 hours by a specific worker. + +Existing projects running on any GitLab instance or GitLab.com can be exported +with all their related data and be moved into a new GitLab instance. + +## Exported contents + +The following items will be exported: + +- Project and wiki repositories +- Project uploads +- Project configuration including web hooks and services +- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, + and other project entities + +The following items will NOT be exported: + +- Build traces and artifacts +- LFS objects + +## Exporting a project and its data + +1. Go to the project settings page by clicking on **Edit Project**: + +  + +1. Scroll down to find the **Export project** button: + +  + +1. Once the export is generated, you should receive an e-mail with a link to + download the file: + +  + +1. Alternatively, you can come back to the project settings and download the + file from there, or generate a new export. Once the file available, the page + should show the **Download export** button: + +  + +## Importing the project + +1. The new GitLab project import feature is at the far right of the import + options when creating a New Project. Make sure you are in the right namespace + and you have entered a project name. Click on **GitLab export**: + +  + +1. You can see where the project will be imported to. You can now select file + exported previously: + +  + +1. Click on **Import project** to begin importing. Your newly imported project + page will appear soon. + +[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050 +[tmp]: ../../../development/shared_files.md diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 9efe41308dc..ddb2f7281b1 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -7,7 +7,7 @@ - [Groups](groups.md) - [Keyboard shortcuts](shortcuts.md) - [File finder](file_finder.md) -- [Labels](labels.md) +- [Labels](../user/project/labels.md) - [Notification emails](notifications.md) - [Project Features](project_features.md) - [Project forking workflow](forking_workflow.md) diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md index fffa0aba57f..4b551130255 100644 --- a/doc/workflow/add-user/add-user.md +++ b/doc/workflow/add-user/add-user.md @@ -8,7 +8,7 @@ You should have `master` or `owner` permissions to add or import a new user to your project. The first step to add or import a user, go to your project and click on -**Members** on the left side of your screen. +**Members** in the drop-down menu on the right side of your screen.  @@ -87,3 +87,25 @@ invitation, change their access level or even delete them. Once the user accepts the invitation, they will be prompted to create a new GitLab account using the same e-mail address the invitation was sent to. + +## Request access to a project + +As a user, you can request to be a member of a project. Go to the project you'd +like to be a member of, and click the **Request Access** button on the right +side of your screen. + + + +--- + +Project owners & masters will be notified of your request and will be able to approve or +decline it on the members page. + + + +--- + +If you change your mind before your request is approved, just click the +**Withdraw Access Request** button. + + diff --git a/doc/workflow/add-user/img/access_requests_management.png b/doc/workflow/add-user/img/access_requests_management.png Binary files differnew file mode 100644 index 00000000000..e9641cb4f85 --- /dev/null +++ b/doc/workflow/add-user/img/access_requests_management.png diff --git a/doc/workflow/add-user/img/add_user_email_accept.png b/doc/workflow/add-user/img/add_user_email_accept.png Binary files differindex 910affc9659..18aabf93d50 100644 --- a/doc/workflow/add-user/img/add_user_email_accept.png +++ b/doc/workflow/add-user/img/add_user_email_accept.png diff --git a/doc/workflow/add-user/img/add_user_email_ready.png b/doc/workflow/add-user/img/add_user_email_ready.png Binary files differindex 5f02ce89b3e..385d64330c0 100644 --- a/doc/workflow/add-user/img/add_user_email_ready.png +++ b/doc/workflow/add-user/img/add_user_email_ready.png diff --git a/doc/workflow/add-user/img/add_user_email_search.png b/doc/workflow/add-user/img/add_user_email_search.png Binary files differindex 140979fbe13..84741edbca4 100644 --- a/doc/workflow/add-user/img/add_user_email_search.png +++ b/doc/workflow/add-user/img/add_user_email_search.png diff --git a/doc/workflow/add-user/img/add_user_give_permissions.png b/doc/workflow/add-user/img/add_user_give_permissions.png Binary files differindex 8ef9156c8d5..7e580384e54 100644 --- a/doc/workflow/add-user/img/add_user_give_permissions.png +++ b/doc/workflow/add-user/img/add_user_give_permissions.png diff --git a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png Binary files differindex 5770d5cf0c4..8dbd73a5bc8 100644 --- a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png +++ b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png diff --git a/doc/workflow/add-user/img/add_user_imported_members.png b/doc/workflow/add-user/img/add_user_imported_members.png Binary files differindex dea4b3f40ad..abac1f59c02 100644 --- a/doc/workflow/add-user/img/add_user_imported_members.png +++ b/doc/workflow/add-user/img/add_user_imported_members.png diff --git a/doc/workflow/add-user/img/add_user_list_members.png b/doc/workflow/add-user/img/add_user_list_members.png Binary files differindex 7daa6ca7d9e..e17d88c6f5f 100644 --- a/doc/workflow/add-user/img/add_user_list_members.png +++ b/doc/workflow/add-user/img/add_user_list_members.png diff --git a/doc/workflow/add-user/img/add_user_members_menu.png b/doc/workflow/add-user/img/add_user_members_menu.png Binary files differindex f1797b95f67..ec5d39f402d 100644 --- a/doc/workflow/add-user/img/add_user_members_menu.png +++ b/doc/workflow/add-user/img/add_user_members_menu.png diff --git a/doc/workflow/add-user/img/add_user_search_people.png b/doc/workflow/add-user/img/add_user_search_people.png Binary files differindex 5ac10ce80d4..eaa062376f4 100644 --- a/doc/workflow/add-user/img/add_user_search_people.png +++ b/doc/workflow/add-user/img/add_user_search_people.png diff --git a/doc/workflow/add-user/img/request_access_button.png b/doc/workflow/add-user/img/request_access_button.png Binary files differnew file mode 100644 index 00000000000..984d640b0f0 --- /dev/null +++ b/doc/workflow/add-user/img/request_access_button.png diff --git a/doc/workflow/add-user/img/withdraw_access_request_button.png b/doc/workflow/add-user/img/withdraw_access_request_button.png Binary files differnew file mode 100644 index 00000000000..ff54a0e4384 --- /dev/null +++ b/doc/workflow/add-user/img/withdraw_access_request_button.png diff --git a/doc/workflow/award_emoji.md b/doc/workflow/award_emoji.md index 70b35c58be6..e6f8b792707 100644 --- a/doc/workflow/award_emoji.md +++ b/doc/workflow/award_emoji.md @@ -1,28 +1,26 @@ -# Award emojis +# Award emoji >**Note:** This feature was [introduced][1825] in GitLab 8.2. When you're collaborating online, you get fewer opportunities for high-fives -and thumbs-ups. In order to make virtual celebrations easier, you can now vote -on issues and merge requests using emoji! +and thumbs-ups. Emoji can be awarded to issues and merge requests, making +virtual celebrations easier.  -This makes it much easier to give and receive feedback, without a long comment -thread. Any comment that contains only the thumbs up or down emojis is -converted to a vote and depicted in the emoji area. - -You can then use that functionality to sort issues and merge requests based on -popularity. +Award emoji make it much easier to give and receive feedback without a long +comment thread. Comments that are only emoji will automatically become +award emoji. ## Sort issues and merge requests on vote count >**Note:** This feature was [introduced][2871] in GitLab 8.5. -You can quickly sort the issues or merge requests by the number of votes they -have received. The sort option can be found in the right dropdown menu. +You can quickly sort issues and merge requests by the number of votes they +have received. The sort options can be found in the dropdown menu as "Most +popular" and "Least popular".  @@ -40,9 +38,28 @@ Sort by least popular issues/merge requests. --- -The number of upvotes and downvotes is not summed up. That means that an issue -with 18 upvotes and 5 downvotes is considered more popular than an issue with -17 upvotes and no downvotes. +The total number of votes is not summed up. An issue with 18 upvotes and 5 +downvotes is considered more popular than an issue with 17 upvotes and no +downvotes. + +## Award emoji for comments + +>**Note:** +This feature was [introduced][4291] in GitLab 8.9. + +Award emoji can also be applied to individual comments when you want to +celebrate an accomplishment or agree with an opinion. + +To add an award emoji, click the smile in the top right of the comment and pick +an emoji from the dropdown. + + + + + +If you want to remove an award emoji, just click the emoji again and the vote +will be removed. [2871]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781 [1825]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825 +[4291]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4291 diff --git a/doc/workflow/award_emoji.png b/doc/workflow/award_emoji.png Binary files differindex fb26ee04393..3408ed95841 100644 --- a/doc/workflow/award_emoji.png +++ b/doc/workflow/award_emoji.png diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md index 34ada1774d8..1a316e80976 100644 --- a/doc/workflow/groups.md +++ b/doc/workflow/groups.md @@ -51,6 +51,28 @@ If necessary, you can increase the access level of an individual user for a spec  +## Request access to a group + +As a user, you can request to be a member of a group. Go to the group you'd +like to be a member of, and click the **Request Access** button on the right +side of your screen. + + + +--- + +Group owners & masters will be notified of your request and will be able to approve or +decline it on the members page. + + + +--- + +If you change your mind before your request is approved, just click the +**Withdraw Access Request** button. + + + ## Managing group memberships via LDAP In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups. diff --git a/doc/workflow/groups/access_requests_management.png b/doc/workflow/groups/access_requests_management.png Binary files differnew file mode 100644 index 00000000000..ffede8e9bd6 --- /dev/null +++ b/doc/workflow/groups/access_requests_management.png diff --git a/doc/workflow/groups/request_access_button.png b/doc/workflow/groups/request_access_button.png Binary files differnew file mode 100644 index 00000000000..ff0ac8747a7 --- /dev/null +++ b/doc/workflow/groups/request_access_button.png diff --git a/doc/workflow/groups/withdraw_access_request_button.png b/doc/workflow/groups/withdraw_access_request_button.png Binary files differnew file mode 100644 index 00000000000..99d7a326ed8 --- /dev/null +++ b/doc/workflow/groups/withdraw_access_request_button.png diff --git a/doc/workflow/img/award_emoji_comment_awarded.png b/doc/workflow/img/award_emoji_comment_awarded.png Binary files differnew file mode 100644 index 00000000000..67697831869 --- /dev/null +++ b/doc/workflow/img/award_emoji_comment_awarded.png diff --git a/doc/workflow/img/award_emoji_comment_picker.png b/doc/workflow/img/award_emoji_comment_picker.png Binary files differnew file mode 100644 index 00000000000..d9c3faecdca --- /dev/null +++ b/doc/workflow/img/award_emoji_comment_picker.png diff --git a/doc/workflow/img/todo_list_item.png b/doc/workflow/img/todo_list_item.png Binary files differnew file mode 100644 index 00000000000..884ba1d22a3 --- /dev/null +++ b/doc/workflow/img/todo_list_item.png diff --git a/doc/workflow/img/todos_add_todo_sidebar.png b/doc/workflow/img/todos_add_todo_sidebar.png Binary files differnew file mode 100644 index 00000000000..126ecc2c82f --- /dev/null +++ b/doc/workflow/img/todos_add_todo_sidebar.png diff --git a/doc/workflow/img/todos_icon.png b/doc/workflow/img/todos_icon.png Binary files differindex 879b3b51c21..a63bad0c258 100644 --- a/doc/workflow/img/todos_icon.png +++ b/doc/workflow/img/todos_icon.png diff --git a/doc/workflow/img/todos_mark_done_sidebar.png b/doc/workflow/img/todos_mark_done_sidebar.png Binary files differnew file mode 100644 index 00000000000..f449f977dd6 --- /dev/null +++ b/doc/workflow/img/todos_mark_done_sidebar.png diff --git a/doc/workflow/labels.md b/doc/workflow/labels.md index 6e4840ca5ae..5c09891dfdd 100644 --- a/doc/workflow/labels.md +++ b/doc/workflow/labels.md @@ -1,18 +1,3 @@ # Labels -In GitLab, you can easily tag issues and Merge Requests. If you have permission level `Developer` or higher, you can manage labels. To create, edit or delete a label, go to a project and then to `Issues` and then `Labels`. - -Here you can create a new label. - - - -You can choose to set a color. - - - -If you want to change an existing label, press edit next to the listed label. -You will be presented with the same form as when creating a new label. - - - -You can add labels to Merge Requests when you create or edit them. +This document was moved to [user/project/labels.md](../user/project/labels.md). diff --git a/doc/workflow/labels/label1.png b/doc/workflow/labels/label1.png Binary files differdeleted file mode 100644 index cac661a34c8..00000000000 --- a/doc/workflow/labels/label1.png +++ /dev/null diff --git a/doc/workflow/labels/label2.png b/doc/workflow/labels/label2.png Binary files differdeleted file mode 100644 index 44d9fef86d4..00000000000 --- a/doc/workflow/labels/label2.png +++ /dev/null diff --git a/doc/workflow/labels/label3.png b/doc/workflow/labels/label3.png Binary files differdeleted file mode 100644 index e2fce11b7a4..00000000000 --- a/doc/workflow/labels/label3.png +++ /dev/null diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png Binary files differindex beb6c53ec77..16be0413b64 100644 --- a/doc/workflow/shortcuts.png +++ b/doc/workflow/shortcuts.png diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index 5f440fdafdd..9524ffd5420 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -1,4 +1,4 @@ -# GitLab ToDos +# GitLab Todos >**Note:** This feature was [introduced][ce-2817] in GitLab 8.5. @@ -14,8 +14,9 @@ in a simple dashboard. --- -You can access quickly your Todos dashboard by clicking the round gray icon -next to the search bar in the upper right corner. +You can quickly access the Todos dashboard using the bell icon next to the +search bar in the upper right corner. The number in blue is the number of Todos +you still have open.  @@ -29,45 +30,61 @@ A Todo appears in your Todos dashboard when: >**Note:** Commenting on a commit will _not_ trigger a Todo. -## How a Todo is marked as Done +### Manually creating a Todo + +You can also add an issue or merge request to your Todos dashboard by clicking +the "Add Todo" button in the issue or merge request sidebar. + + + +## Marking a Todo as done Any action to the corresponding issue or merge request will mark your Todo as -**Done**. This action can include: +**Done**. Actions that dismiss Todos include: - changing the assignee - changing the milestone - adding/removing a label - commenting on the issue -In case where you think no action is needed, you can manually mark the todo as -done by clicking the corresponding **Done** button, and it will disappear from -your Todos list. If you want to mark all your Todos as done, just click on the -**Mark all as done** button. - --- -In order for a Todo to be marked as done, the action must be coming from you. -So, if you close the related issue or merge the merge request yourself, and you -had a Todo for that, it will automatically get marked as done. On the other -hand, if someone else closes, merges or takes action on the issue or merge -request, your Todo will remain pending. This makes sense because you may need -to give attention to an issue even if it has been resolved. +Todos are personal, and they're only marked as done if the action is coming from +you. If you close the issue or merge request, your Todo will automatically +be marked as done. + +If someone else closes, merges, or takes action on the issue or merge +request, your Todo will remain pending. This prevents other users from closing issues without you being notified. There is just one Todo per issue or merge request, so mentioning a user a hundred times in an issue will only trigger one Todo. +--- + +If no action is needed, you can manually mark the Todo as done by clicking the +corresponding **Done** button, and it will disappear from your Todo list. + + + +A Todo can also be marked as done from the issue or merge request sidebar using +the "Mark Done" button. + + + +You can mark all your Todos as done at once by clicking on the **Mark all as +done** button. + ## Filtering your Todos -In general, there are four kinds of filters you can use on your Todos -dashboard: +There are four kinds of filters you can use on your Todos dashboard. -| Filter | Description | -| ------ | ----------- | +| Filter | Description | +| ------- | ----------- | | Project | Filter by project | | Author | Filter by the author that triggered the Todo | | Type | Filter by issue or merge request | | Action | Filter by the action that triggered the Todo (Assigned or Mentioned)| -You can choose more than one filters at the same time. +You can also filter by more than one of these at the same time. [ce-2817]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2817 diff --git a/features/admin/groups.feature b/features/admin/groups.feature index ab7de7ac315..657e847cf4a 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -27,13 +27,6 @@ Feature: Admin Groups Then I should see project shared with group @javascript - Scenario: Remove user from group - Given we have user "John Doe" in group - When I visit admin group page - And I remove user "John Doe" from group - Then I should not see "John Doe" in team list - - @javascript Scenario: Invite user to a group by e-mail When I visit admin group page When I select user "johndoe@gitlab.com" from user list as "Reporter" diff --git a/features/dashboard/group.feature b/features/dashboard/group.feature index e3c01db2ebb..3ae2c679dc1 100644 --- a/features/dashboard/group.feature +++ b/features/dashboard/group.feature @@ -5,53 +5,9 @@ Feature: Dashboard Group And "John Doe" is owner of group "Owned" And "John Doe" is guest of group "Guest" - # Leave groups - - @javascript - Scenario: Owner should be able to leave from group if he is not the last owner - Given "Mary Jane" is owner of group "Owned" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should see group "Guest" in group list - When I click on the "Leave" button for group "Owned" - And I visit dashboard groups page - Then I should not see group "Owned" in group list - Then I should see group "Guest" in group list - - @javascript - Scenario: Owner should not be able to leave from group if he is the last owner - Given "Mary Jane" is guest of group "Owned" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should see group "Guest" in group list - When I click on the "Leave" button for group "Owned" - Then I should see the "Can not leave message" - - @javascript - Scenario: Guest should be able to leave from group - Given "Mary Jane" is guest of group "Guest" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should see group "Guest" in group list - When I click on the "Leave" button for group "Guest" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should not see group "Guest" in group list - - @javascript - Scenario: Guest should be able to leave from group even if he is the only user in the group - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should see group "Guest" in group list - When I click on the "Leave" button for group "Guest" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should not see group "Guest" in group list - Scenario: Create a group from dasboard And I visit dashboard groups page And I click new group link And submit form with new group "Samurai" info Then I should be redirected to group "Samurai" page And I should see newly created group "Samurai" - diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature index c4f987a7923..57dda9c2234 100644 --- a/features/project/active_tab.feature +++ b/features/project/active_tab.feature @@ -10,9 +10,9 @@ Feature: Project Active Tab Then the active main tab should be Home And no other main tabs should be active - Scenario: On Project Code + Scenario: On Project Repository Given I visit my project's files page - Then the active main tab should be Code + Then the active main tab should be Repository And no other main tabs should be active Scenario: On Project Issues @@ -59,46 +59,46 @@ Feature: Project Active Tab And no other sub navs should be active And the active main tab should be Settings - # Sub Tabs: Code + # Sub Tabs: Repository - Scenario: On Project Code/Files + Scenario: On Project Repository/Files Given I visit my project's files page Then the active sub tab should be Files And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Commits + Scenario: On Project Repository/Commits Given I visit my project's commits page Then the active sub tab should be Commits And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Network + Scenario: On Project Repository/Network Given I visit my project's network page Then the active sub tab should be Network And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Compare + Scenario: On Project Repository/Compare Given I visit my project's commits page And I click the "Compare" tab Then the active sub tab should be Compare And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Branches + Scenario: On Project Repository/Branches Given I visit my project's commits page And I click the "Branches" tab Then the active sub tab should be Branches And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Tags + Scenario: On Project Repository/Tags Given I visit my project's commits page And I click the "Tags" tab Then the active sub tab should be Tags And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository Scenario: On Project Issues/Browse Given I visit my project's issues page diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature index c73d0b32337..f71f69ef060 100644 --- a/features/project/shortcuts.feature +++ b/features/project/shortcuts.feature @@ -8,21 +8,21 @@ Feature: Project Shortcuts @javascript Scenario: Navigate to files tab Given I press "g" and "f" - Then the active main tab should be Code + Then the active main tab should be Repository Then the active sub tab should be Files @javascript Scenario: Navigate to commits tab Given I visit my project's files page Given I press "g" and "c" - Then the active main tab should be Code + Then the active main tab should be Repository Then the active sub tab should be Commits @javascript Scenario: Navigate to network tab Given I press "g" and "n" Then the active sub tab should be Network - And the active main tab should be Code + And the active main tab should be Repository @javascript Scenario: Navigate to graphs tab diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index e1f1db2872f..8613dc537cc 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -62,7 +62,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps step 'I should see "johndoe@gitlab.com" in team list in every project as "Reporter"' do page.within ".group-users-list" do - expect(page).to have_content "johndoe@gitlab.com (invited)" + expect(page).to have_content "johndoe@gitlab.com – Invited by" expect(page).to have_content "Reporter" end end @@ -92,12 +92,6 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps current_group.add_reporter(user_john) end - step 'I remove user "John Doe" from group' do - page.within "#user_#{user_john.id}" do - click_link 'Remove user from group' - end - end - step 'I should not see "John Doe" in team list' do page.within ".group-users-list" do expect(page).not_to have_content "John Doe" diff --git a/features/steps/dashboard/group.rb b/features/steps/dashboard/group.rb index 9b79a3be49b..cf679fea530 100644 --- a/features/steps/dashboard/group.rb +++ b/features/steps/dashboard/group.rb @@ -4,44 +4,6 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps include SharedPaths include SharedUser - # Leave - - step 'I click on the "Leave" button for group "Owned"' do - find(:css, 'li', text: "Owner").find(:css, 'i.fa.fa-sign-out').click - # poltergeist always confirms popups. - end - - step 'I click on the "Leave" button for group "Guest"' do - find(:css, 'li', text: "Guest").find(:css, 'i.fa.fa-sign-out').click - # poltergeist always confirms popups. - end - - step 'I should not see the "Leave" button for group "Owned"' do - expect(find(:css, 'li', text: "Owner")).not_to have_selector(:css, 'i.fa.fa-sign-out') - # poltergeist always confirms popups. - end - - step 'I should not see the "Leave" button for groupr "Guest"' do - expect(find(:css, 'li', text: "Guest")).not_to have_selector(:css, 'i.fa.fa-sign-out') - # poltergeist always confirms popups. - end - - step 'I should see group "Owned" in group list' do - expect(page).to have_content("Owned") - end - - step 'I should not see group "Owned" in group list' do - expect(page).not_to have_content("Owned") - end - - step 'I should see group "Guest" in group list' do - expect(page).to have_content("Guest") - end - - step 'I should not see group "Guest" in group list' do - expect(page).not_to have_content("Guest") - end - step 'I click new group link' do click_link "New Group" end @@ -60,8 +22,4 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps expect(page).to have_content "Samurai" expect(page).to have_content "Tokugawa Shogunate" end - - step 'I should see the "Can not leave message"' do - expect(page).to have_content "You can not leave the \"Owned\" group." - end end diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb index 979f4692d5a..7e339443b75 100644 --- a/features/steps/profile/notifications.rb +++ b/features/steps/profile/notifications.rb @@ -15,8 +15,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps end step 'I should see Notification saved message' do - page.within '.flash-container' do - expect(page).to have_content 'Notification settings saved' - end + expect(page).to have_content 'On mention' end end diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 1b14659b4df..1498f899cf5 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -81,9 +81,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I search "hand"' do - page.within('.emoji-menu-content') do - fill_in 'emoji_search', with: 'hand' - end + fill_in 'emoji_search', with: 'hand' end step 'I see search result for "hand"' do diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb index 9b59b682676..019b3124a86 100644 --- a/features/steps/project/network_graph.rb +++ b/features/steps/project/network_graph.rb @@ -20,11 +20,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps end step 'page should select "master" in select box' do - expect(page).to have_selector '.select2-chosen', text: "master" + expect(page).to have_selector '.dropdown-menu-toggle', text: "master" end step 'page should select "v1.0.0" in select box' do - expect(page).to have_selector '.select2-chosen', text: "v1.0.0" + expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0" end step 'page should have "master" on graph' do @@ -40,11 +40,19 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps end When 'I switch ref to "feature"' do - select 'feature', from: 'ref' + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'feature' + end end When 'I switch ref to "v1.0.0"' do - select 'v1.0.0', from: 'ref' + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'v1.0.0' + end end When 'click "Show only selected branch" checkbox' do @@ -68,11 +76,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps end step 'page should select "feature" in select box' do - expect(page).to have_selector '.select2-chosen', text: "feature" + expect(page).to have_selector '.dropdown-menu-toggle', text: "feature" end step 'page should select "v1.0.0" in select box' do - expect(page).to have_selector '.select2-chosen', text: "v1.0.0" + expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0" end step 'page should have "feature" on graph' do diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 98b57e5cbfb..76fefee9254 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -134,8 +134,8 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I should see Notification saved message' do - page.within '.flash-container' do - expect(page).to have_content 'Notification settings saved' + page.within '#notifications-button' do + expect(page).to have_content 'On mention' end end diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb index 47de4b91df1..90771847909 100644 --- a/features/steps/project/project_find_file.rb +++ b/features/steps/project/project_find_file.rb @@ -13,12 +13,12 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps end step 'I should see "find file" page' do - ensure_active_main_tab('Code') + ensure_active_main_tab('Repository') expect(page).to have_selector('.file-finder-holder', count: 1) end step 'I fill in Find by path with "git"' do - ensure_active_main_tab('Code') + ensure_active_main_tab('Repository') expect(page).to have_selector('.file-finder-holder', count: 1) end diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 79a3ed8197e..0fe046dcbf6 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -290,15 +290,23 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step "I switch ref to 'test'" do - select "'test'", from: 'ref' + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'test' + end end step "I switch ref to fix" do - select "fix", from: 'ref' + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'fix' + end end step "I see the ref 'test' has been selected" do - expect(page).to have_selector '.select2-chosen', text: "'test'" + expect(page).to have_selector '.dropdown-toggle-text', text: "'test'" end step "I visit the 'test' tree" do diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb index bfee8793301..d6024212601 100644 --- a/features/steps/shared/project_tab.rb +++ b/features/steps/shared/project_tab.rb @@ -8,8 +8,8 @@ module SharedProjectTab ensure_active_main_tab('Project') end - step 'the active main tab should be Code' do - ensure_active_main_tab('Code') + step 'the active main tab should be Repository' do + ensure_active_main_tab('Repository') end step 'the active main tab should be Graphs' do diff --git a/lib/api/api.rb b/lib/api/api.rb index 0e7a1cc2623..c3fff8b2f8f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -33,14 +33,13 @@ module API mount ::API::Commits mount ::API::DeployKeys mount ::API::Files - mount ::API::Gitignores mount ::API::GroupMembers mount ::API::Groups mount ::API::Internal mount ::API::Issues mount ::API::Keys mount ::API::Labels - mount ::API::Licenses + mount ::API::LicenseTemplates mount ::API::MergeRequests mount ::API::Milestones mount ::API::Namespaces @@ -58,6 +57,7 @@ module API mount ::API::Subscriptions mount ::API::SystemHooks mount ::API::Tags + mount ::API::Templates mount ::API::Triggers mount ::API::Users mount ::API::Variables diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 2e397643ed1..5a23a18fe9c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -423,6 +423,7 @@ module API class RunnerDetails < Runner expose :tag_list expose :run_untagged + expose :locked expose :version, :revision, :platform, :architecture expose :contacted_at expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? } @@ -444,11 +445,7 @@ module API expose :created_at, :started_at, :finished_at expose :user, with: User expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? } - expose :commit, with: RepoCommit do |repo_obj, _options| - if repo_obj.respond_to?(:commit) - repo_obj.commit.commit_data - end - end + expose :commit, with: RepoCommit expose :runner, with: Runner end @@ -472,11 +469,11 @@ module API expose :content end - class GitignoresList < Grape::Entity + class TemplatesList < Grape::Entity expose :name end - class Gitignore < Grape::Entity + class Template < Grape::Entity expose :name, :content end end diff --git a/lib/api/gitignores.rb b/lib/api/gitignores.rb deleted file mode 100644 index 270c9501dd2..00000000000 --- a/lib/api/gitignores.rb +++ /dev/null @@ -1,29 +0,0 @@ -module API - class Gitignores < Grape::API - - # Get the list of the available gitignore templates - # - # Example Request: - # GET /gitignores - get 'gitignores' do - present Gitlab::Gitignore.all, with: Entities::GitignoresList - end - - # Get the text for a specific gitignore - # - # Parameters: - # name (required) - The name of a license - # - # Example Request: - # GET /gitignores/Elixir - # - get 'gitignores/:name' do - required_attributes! [:name] - - gitignore = Gitlab::Gitignore.find(params[:name]) - not_found!('.gitignore') unless gitignore - - present gitignore, with: Entities::Gitignore - end - end -end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 3ac7b50c4ce..1d361569d59 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -23,8 +23,6 @@ module API end post "/allowed" do - Gitlab::Metrics.action = 'Grape#/internal/allowed' - status 200 actor = diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4c43257c48a..8a03a41e9c5 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -59,6 +59,41 @@ module API end end + resource :groups do + # Get a list of group issues + # + # Parameters: + # id (required) - The ID of a group + # state (optional) - Return "opened" or "closed" issues + # labels (optional) - Comma-separated list of label names + # milestone (optional) - Milestone title + # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` + # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` + # + # Example Requests: + # GET /groups/:id/issues + # GET /groups/:id/issues?state=opened + # GET /groups/:id/issues?state=closed + # GET /groups/:id/issues?labels=foo + # GET /groups/:id/issues?labels=foo,bar + # GET /groups/:id/issues?labels=foo,bar&state=opened + # GET /groups/:id/issues?milestone=1.0.0 + # GET /groups/:id/issues?milestone=1.0.0&state=closed + get ":id/issues" do + group = find_group(params[:id]) + + params[:state] ||= 'opened' + params[:group_id] = group.id + params[:milestone_title] = params.delete(:milestone) + params[:label_name] = params.delete(:labels) + params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort] + + issues = IssuesFinder.new(current_user, params).execute + + present paginate(issues), with: Entities::Issue, current_user: current_user + end + end + resource :projects do # Get a list of project issues # diff --git a/lib/api/licenses.rb b/lib/api/license_templates.rb index be0e113fbcb..d0552299ed0 100644 --- a/lib/api/licenses.rb +++ b/lib/api/license_templates.rb @@ -1,6 +1,6 @@ module API - # Licenses API - class Licenses < Grape::API + # License Templates API + class LicenseTemplates < Grape::API PROJECT_TEMPLATE_REGEX = /[\<\{\[] (project|description| diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 4faba9dc87b..ecc8f2fc5a2 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -49,7 +49,7 @@ module API runner = get_runner(params[:id]) authenticate_update_runner!(runner) - attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged] + attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged, :locked] if runner.update(attrs) present runner, with: Entities::RunnerDetails, current_user: current_user else @@ -96,9 +96,14 @@ module API runner = get_runner(params[:runner_id]) authenticate_enable_runner!(runner) - Ci::RunnerProject.create(runner: runner, project: user_project) - present runner, with: Entities::Runner + runner_project = runner.assign_to(user_project) + + if runner_project.persisted? + present runner, with: Entities::Runner + else + conflict!("Runner was already enabled for this project") + end end # Disable project's runner @@ -163,6 +168,7 @@ module API def authenticate_enable_runner!(runner) forbidden!("Runner is shared") if runner.is_shared? + forbidden!("Runner is locked") if runner.locked? return if current_user.is_admin? forbidden!("No access granted") unless user_can_access_runner?(runner) end diff --git a/lib/api/templates.rb b/lib/api/templates.rb new file mode 100644 index 00000000000..18408797756 --- /dev/null +++ b/lib/api/templates.rb @@ -0,0 +1,36 @@ +module API + class Templates < Grape::API + TEMPLATE_TYPES = { + gitignores: Gitlab::Template::Gitignore, + gitlab_ci_ymls: Gitlab::Template::GitlabCiYml + }.freeze + + TEMPLATE_TYPES.each do |template, klass| + # Get the list of the available template + # + # Example Request: + # GET /gitignores + # GET /gitlab_ci_ymls + get template.to_s do + present klass.all, with: Entities::TemplatesList + end + + # Get the text for a specific template + # + # Parameters: + # name (required) - The name of a template + # + # Example Request: + # GET /gitignores/Elixir + # GET /gitlab_ci_ymls/Ruby + get "#{template}/:name" do + required_attributes! [:name] + + new_template = klass.find(params[:name]) + not_found!(template.to_s.singularize) unless new_template + + present new_template, with: Entities::Template + end + end + end +end diff --git a/lib/banzai.rb b/lib/banzai.rb index b467413a7dd..093382261ae 100644 --- a/lib/banzai.rb +++ b/lib/banzai.rb @@ -7,10 +7,6 @@ module Banzai Renderer.render_result(text, context) end - def self.pre_process(text, context) - Renderer.pre_process(text, context) - end - def self.post_process(html, context) Renderer.post_process(html, context) end diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb index ccd106860bd..8aa6f8f124a 100644 --- a/lib/banzai/filter/image_link_filter.rb +++ b/lib/banzai/filter/image_link_filter.rb @@ -9,6 +9,11 @@ module Banzai def call doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img| + div = doc.document.create_element( + 'div', + class: 'image-container' + ) + link = doc.document.create_element( 'a', class: 'no-attachment-icon', @@ -17,7 +22,10 @@ module Banzai ) link.children = img.clone - img.replace(link) + + div.children = link + + img.replace(div) end doc diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 2614261f9eb..5351272f42d 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -31,10 +31,14 @@ module Banzai projects_per_reference.each do |path, project| issue_ids = references_per_project[path] - next unless project.default_issues_tracker? + if project.default_issues_tracker? + issues = project.issues.where(iid: issue_ids.to_a) + else + issues = issue_ids.map { |id| ExternalIssue.new(id, project) } + end - project.issues.where(iid: issue_ids.to_a).each do |issue| - hash[project][issue.iid] = issue + issues.each do |issue| + hash[project][issue.iid.to_i] = issue end end diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb index c753a84a20d..c59a80dd1c7 100644 --- a/lib/banzai/filter/redactor_filter.rb +++ b/lib/banzai/filter/redactor_filter.rb @@ -7,40 +7,13 @@ module Banzai # class RedactorFilter < HTML::Pipeline::Filter def call - nodes = Querying.css(doc, 'a.gfm[data-reference-type]') - visible = nodes_visible_to_user(nodes) - - nodes.each do |node| - unless visible.include?(node) - # The reference should be replaced by the original text, - # which is not always the same as the rendered text. - text = node.attr('data-original') || node.text - node.replace(text) - end - end + Redactor.new(project, current_user).redact([doc]) doc end private - def nodes_visible_to_user(nodes) - per_type = Hash.new { |h, k| h[k] = [] } - visible = Set.new - - nodes.each do |node| - per_type[node.attr('data-reference-type')] << node - end - - per_type.each do |type, nodes| - parser = Banzai::ReferenceParser[type].new(project, current_user) - - visible.merge(parser.nodes_visible_to_user(current_user, nodes)) - end - - visible - end - def current_user context[:current_user] end diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index ea21c7b041c..c78da404607 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -14,6 +14,8 @@ module Banzai def call return doc unless linkable_files? + @uri_types = {} + doc.search('a:not(.gfm)').each do |el| process_link_attr el.attribute('href') end @@ -48,7 +50,7 @@ module Banzai uri.path = [ relative_url_root, context[:project].path_with_namespace, - path_type(file_path), + uri_type(file_path), ref || context[:project].default_branch, # if no ref exists, point to the default branch file_path ].compact.join('/').squeeze('/').chomp('/') @@ -87,7 +89,7 @@ module Banzai return path unless request_path parts = request_path.split('/') - parts.pop if path_type(request_path) != 'tree' + parts.pop if uri_type(request_path) != :tree while path.start_with?('../') parts.pop @@ -98,45 +100,20 @@ module Banzai end def file_exists?(path) - return false if path.nil? - repository.blob_at(current_sha, path).present? || - repository.tree(current_sha, path).entries.any? - end - - # Get the type of the given path - # - # path - String path to check - # - # Examples: - # - # path_type('doc/README.md') # => 'blob' - # path_type('doc/logo.png') # => 'raw' - # path_type('doc/api') # => 'tree' - # - # Returns a String - def path_type(path) - unescaped_path = Addressable::URI.unescape(path) - - if tree?(unescaped_path) - 'tree' - elsif image?(unescaped_path) - 'raw' - else - 'blob' - end + path.present? && !!uri_type(path) end - def tree?(path) - repository.tree(current_sha, path).entries.any? - end + def uri_type(path) + @uri_types[path] ||= begin + unescaped_path = Addressable::URI.unescape(path) - def image?(path) - repository.blob_at(current_sha, path).try(:image?) + current_commit.uri_type(unescaped_path) + end end - def current_sha - context[:commit].try(:id) || - ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha + def current_commit + @current_commit ||= context[:commit] || + ref ? repository.commit(ref) : repository.head_commit end def relative_url_root @@ -148,7 +125,7 @@ module Banzai end def repository - context[:project].try(:repository) + @repository ||= context[:project].try(:repository) end end end diff --git a/lib/banzai/note_renderer.rb b/lib/banzai/note_renderer.rb new file mode 100644 index 00000000000..bab6a9934d1 --- /dev/null +++ b/lib/banzai/note_renderer.rb @@ -0,0 +1,22 @@ +module Banzai + module NoteRenderer + # Renders a collection of Note instances. + # + # notes - The notes to render. + # project - The project to use for rendering/redacting. + # user - The user viewing the notes. + # path - The request path. + # wiki - The project's wiki. + # git_ref - The current Git reference. + def self.render(notes, project, user = nil, path = nil, wiki = nil, git_ref = nil) + renderer = ObjectRenderer.new(project, + user, + requested_path: path, + project_wiki: wiki, + ref: git_ref, + pipeline: :note) + + renderer.render(notes, :note) + end + end +end diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb new file mode 100644 index 00000000000..f0e4f28bf12 --- /dev/null +++ b/lib/banzai/object_renderer.rb @@ -0,0 +1,85 @@ +module Banzai + # Class for rendering multiple objects (e.g. Note instances) in a single pass. + # + # Rendered Markdown is stored in an attribute in every object based on the + # name of the attribute containing the Markdown. For example, when the + # attribute `note` is rendered the HTML is stored in `note_html`. + class ObjectRenderer + attr_reader :project, :user + + # Make sure to set the appropriate pipeline in the `raw_context` attribute + # (e.g. `:note` for Note instances). + # + # project - A Project to use for rendering and redacting Markdown. + # user - The user viewing the Markdown/HTML documents, if any. + # context - A Hash containing extra attributes to use in the rendering + # pipeline. + def initialize(project, user = nil, raw_context = {}) + @project = project + @user = user + @raw_context = raw_context + end + + # Renders and redacts an Array of objects. + # + # objects - The objects to render + # attribute - The attribute containing the raw Markdown to render. + # + # Returns the same input objects. + def render(objects, attribute) + documents = render_objects(objects, attribute) + redacted = redact_documents(documents) + + objects.each_with_index do |object, index| + object.__send__("#{attribute}_html=", redacted.fetch(index)) + end + + objects + end + + # Renders the attribute of every given object. + def render_objects(objects, attribute) + objects.map do |object| + render_attribute(object, attribute) + end + end + + # Redacts the list of documents. + # + # Returns an Array containing the redacted documents. + def redact_documents(documents) + redactor = Redactor.new(project, user) + + redactor.redact(documents).map do |document| + document.to_html.html_safe + end + end + + # Returns a Banzai context for the given object and attribute. + def context_for(object, attribute) + context = base_context.merge(cache_key: [object, attribute]) + + if object.respond_to?(:author) + context[:author] = object.author + end + + context + end + + # Renders the attribute of an object. + # + # Returns a `Nokogiri::HTML::Document`. + def render_attribute(object, attribute) + context = context_for(object, attribute) + + string = object.__send__(attribute) + html = Banzai.render(string, context) + + Banzai::Pipeline[:relative_link].to_document(html, context) + end + + def base_context + @base_context ||= @raw_context.merge(current_user: user, project: project) + end + end +end diff --git a/lib/banzai/pipeline/relative_link_pipeline.rb b/lib/banzai/pipeline/relative_link_pipeline.rb new file mode 100644 index 00000000000..270990e7ab4 --- /dev/null +++ b/lib/banzai/pipeline/relative_link_pipeline.rb @@ -0,0 +1,11 @@ +module Banzai + module Pipeline + class RelativeLinkPipeline < BasePipeline + def self.filters + FilterArray[ + Filter::RelativeLinkFilter + ] + end + end + end +end diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb new file mode 100644 index 00000000000..ffd267d5e9a --- /dev/null +++ b/lib/banzai/redactor.rb @@ -0,0 +1,69 @@ +module Banzai + # Class for removing Markdown references a certain user is not allowed to + # view. + class Redactor + attr_reader :user, :project + + # project - A Project to use for redacting links. + # user - The currently logged in user (if any). + def initialize(project, user = nil) + @project = project + @user = user + end + + # Redacts the references in the given Array of documents. + # + # This method modifies the given documents in-place. + # + # documents - A list of HTML documents containing references to redact. + # + # Returns the documents passed as the first argument. + def redact(documents) + nodes = documents.flat_map do |document| + Querying.css(document, 'a.gfm[data-reference-type]') + end + + redact_nodes(nodes) + + documents + end + + # Redacts the given nodes + # + # nodes - An Array of HTML nodes to redact. + def redact_nodes(nodes) + visible = nodes_visible_to_user(nodes) + + nodes.each do |node| + unless visible.include?(node) + # The reference should be replaced by the original text, + # which is not always the same as the rendered text. + text = node.attr('data-original') || node.text + node.replace(text) + end + end + end + + # Returns the nodes visible to the current user. + # + # nodes - The input nodes to check. + # + # Returns a new Array containing the visible nodes. + def nodes_visible_to_user(nodes) + per_type = Hash.new { |h, k| h[k] = [] } + visible = Set.new + + nodes.each do |node| + per_type[node.attr('data-reference-type')] << node + end + + per_type.each do |type, nodes| + parser = Banzai::ReferenceParser[type].new(project, user) + + visible.merge(parser.nodes_visible_to_user(user, nodes)) + end + + visible + end + end +end diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index c14a9c4c722..6718acdef7e 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -30,13 +30,9 @@ module Banzai end def self.render_result(text, context = {}) - Pipeline[context[:pipeline]].call(text, context) - end + text = Pipeline[:pre_process].to_html(text, context) if text - def self.pre_process(text, context) - pipeline = Pipeline[:pre_process] - - pipeline.to_html(text, context) + Pipeline[context[:pipeline]].call(text, context) end # Perform post-processing on an HTML String diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb index 0c41f22c7c5..bcc82969eb3 100644 --- a/lib/ci/api/runners.rb +++ b/lib/ci/api/runners.rb @@ -28,12 +28,9 @@ module Ci post "register" do required_attributes! [:token] - attributes = { description: params[:description], - tag_list: params[:tag_list] } - - unless params[:run_untagged].nil? - attributes[:run_untagged] = params[:run_untagged] - end + attributes = attributes_for_keys( + [:description, :tag_list, :run_untagged, :locked] + ) runner = if runner_registration_token_valid? diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb index 7a0929d774e..708d01b95a1 100644 --- a/lib/container_registry/tag.rb +++ b/lib/container_registry/tag.rb @@ -3,6 +3,7 @@ module ContainerRegistry attr_reader :repository, :name delegate :registry, :client, to: :repository + delegate :revision, :short_revision, to: :config_blob, allow_nil: true def initialize(repository, name) @repository, @name = repository, name diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index 6d0e30e916f..831f1e635ba 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -5,6 +5,8 @@ # module Gitlab module Access + class AccessDeniedError < StandardError; end + GUEST = 10 REPORTER = 20 DEVELOPER = 30 diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 7e3f5abba62..ab7b811c5d8 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -31,7 +31,7 @@ module Grack auth! - lfs_response = Gitlab::Lfs::Router.new(project, @user, @request).try_call + lfs_response = Gitlab::Lfs::Router.new(project, @user, @ci, @request).try_call return lfs_response unless lfs_response.nil? if @user.nil? && !@ci diff --git a/lib/gitlab/blame.rb b/lib/gitlab/blame.rb index 997a22779a0..d62bc50ce78 100644 --- a/lib/gitlab/blame.rb +++ b/lib/gitlab/blame.rb @@ -41,7 +41,8 @@ module Gitlab def highlighted_lines @blob.load_all_data!(repository) - @highlighted_lines ||= Gitlab::Highlight.highlight(@blob.name, @blob.data).lines + @highlighted_lines ||= + Gitlab::Highlight.highlight(@blob.path, @blob.data, repository: repository).lines end def project diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index e2fee6b9f3e..047c77c6fc2 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -37,7 +37,7 @@ module Gitlab end def diffs - @diffs ||= (safe_diff_files(compare.diffs, diff_refs) if compare) + @diffs ||= (safe_diff_files(compare.diffs(max_files: 30), diff_refs) if compare) end def diffs_count diff --git a/lib/gitlab/gitignore.rb b/lib/gitlab/gitignore.rb deleted file mode 100644 index f46b43b61a4..00000000000 --- a/lib/gitlab/gitignore.rb +++ /dev/null @@ -1,56 +0,0 @@ -module Gitlab - class Gitignore - FILTER_REGEX = /\.gitignore\z/.freeze - - def initialize(path) - @path = path - end - - def name - File.basename(@path, '.gitignore') - end - - def content - File.read(@path) - end - - class << self - def all - languages_frameworks + global - end - - def find(key) - file_name = "#{key}.gitignore" - - directory = select_directory(file_name) - directory ? new(File.join(directory, file_name)) : nil - end - - def global - files_for_folder(global_dir).map { |file| new(File.join(global_dir, file)) } - end - - def languages_frameworks - files_for_folder(gitignore_dir).map { |file| new(File.join(gitignore_dir, file)) } - end - - private - - def select_directory(file_name) - [gitignore_dir, global_dir].find { |dir| File.exist?(File.join(dir, file_name)) } - end - - def global_dir - File.join(gitignore_dir, 'Global') - end - - def gitignore_dir - Rails.root.join('vendor/gitignore') - end - - def files_for_folder(dir) - Dir.glob("#{dir.to_s}/*.gitignore").map { |file| file.gsub(FILTER_REGEX, '') } - end - end - end -end diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index 280120b0f9e..41296415e35 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -1,7 +1,7 @@ module Gitlab class Highlight - def self.highlight(blob_name, blob_content, nowrap: true, plain: false) - new(blob_name, blob_content, nowrap: nowrap). + def self.highlight(blob_name, blob_content, repository: nil, nowrap: true, plain: false) + new(blob_name, blob_content, nowrap: nowrap, repository: repository). highlight(blob_content, continue: false, plain: plain) end @@ -10,12 +10,21 @@ module Gitlab return [] unless blob blob.load_all_data!(repository) - highlight(file_name, blob.data).lines.map!(&:html_safe) + highlight(file_name, blob.data, repository: repository).lines.map!(&:html_safe) end - def initialize(blob_name, blob_content, nowrap: true) + attr_reader :lexer + def initialize(blob_name, blob_content, repository: nil, nowrap: true) + @blob_name = blob_name + @blob_content = blob_content + @repository = repository @formatter = rouge_formatter(nowrap: nowrap) - @lexer = Rouge::Lexer.guess(filename: blob_name, source: blob_content).new rescue Rouge::Lexers::PlainText + + @lexer = custom_language || begin + Rouge::Lexer.guess(filename: blob_name, source: blob_content).new + rescue Rouge::Lexer::AmbiguousGuess => e + e.alternatives.sort_by(&:tag).first + end end def highlight(text, continue: true, plain: false) @@ -30,6 +39,14 @@ module Gitlab private + def custom_language + language_name = @repository && @repository.gitattribute(@blob_name, 'gitlab-language') + + return nil unless language_name + + Rouge::Lexer.find_fancy(language_name) + end + def rouge_formatter(options = {}) options = options.reverse_merge( nowrap: true, diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb index 0e70d9282d5..82d1e1805c5 100644 --- a/lib/gitlab/import_export/file_importer.rb +++ b/lib/gitlab/import_export/file_importer.rb @@ -23,7 +23,11 @@ module Gitlab private def decompress_archive - untar_zxf(archive: @archive_file, dir: @shared.export_path) + result = untar_zxf(archive: @archive_file, dir: @shared.export_path) + + raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result + + true end end end diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index d209e04f7be..595b20a09bd 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -10,17 +10,22 @@ module Gitlab end def execute - Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file, - shared: @shared) - if check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore) + if import_file && check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore) project_tree.restored_project else raise Projects::ImportService::Error.new(@shared.errors.join(', ')) end + + remove_import_file end private + def import_file + Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file, + shared: @shared) + end + def check_version! Gitlab::ImportExport::VersionChecker.check!(shared: @shared) end @@ -59,6 +64,10 @@ module Gitlab def wiki_repo_path File.join(@shared.export_path, 'project.wiki.bundle') end + + def remove_import_file + FileUtils.rm_rf(@archive_file) + end end end end diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index b872780f20a..92bf7e0a2fc 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -12,6 +12,8 @@ module Gitlab USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze + BUILD_MODELS = %w[Ci::Build commit_status].freeze + def self.create(*args) new(*args).create end @@ -70,7 +72,7 @@ module Gitlab end def generate_imported_object - if @relation_sym == 'commit_status' # call #trace= method after assigning the other attributes + if BUILD_MODELS.include?(@relation_name) # call #trace= method after assigning the other attributes trace = @relation_hash.delete('trace') imported_object do |object| object.trace = trace diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index 9d9617761b3..e3ed2f6791d 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -2,10 +2,11 @@ module Gitlab module Lfs class Response - def initialize(project, user, request) + def initialize(project, user, ci, request) @origin_project = project @project = storage_project(project) @user = user + @ci = ci @env = request.env @request = request end @@ -189,7 +190,7 @@ module Gitlab return render_not_enabled unless Gitlab.config.lfs.enabled unless @project.public? - return render_unauthorized unless @user + return render_unauthorized unless @user || @ci return render_forbidden unless user_can_fetch? end @@ -210,7 +211,7 @@ module Gitlab def user_can_fetch? # Check user access against the project they used to initiate the pull - @user.can?(:download_code, @origin_project) + @ci || @user.can?(:download_code, @origin_project) end def user_can_push? diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb index 78d02891102..69bd5e62305 100644 --- a/lib/gitlab/lfs/router.rb +++ b/lib/gitlab/lfs/router.rb @@ -1,9 +1,12 @@ module Gitlab module Lfs class Router - def initialize(project, user, request) + attr_reader :project, :user, :ci, :request + + def initialize(project, user, ci, request) @project = project @user = user + @ci = ci @env = request.env @request = request end @@ -80,7 +83,7 @@ module Gitlab def lfs return unless @project - Gitlab::Lfs::Response.new(@project, @user, @request) + Gitlab::Lfs::Response.new(@project, @user, @ci, @request) end def sanitize_tmp_filename(name) diff --git a/lib/gitlab/metrics/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb index fd98aa3412e..a1240fd33ee 100644 --- a/lib/gitlab/metrics/sidekiq_middleware.rb +++ b/lib/gitlab/metrics/sidekiq_middleware.rb @@ -8,6 +8,8 @@ module Gitlab trans = Transaction.new("#{worker.class.name}#perform") begin + # Old gitlad-shell messages don't provide enqueued_at/created_at attributes + trans.set(:sidekiq_queue_duration, Time.now.to_f - (message['enqueued_at'] || message['created_at'] || 0)) trans.run { yield } ensure trans.finish diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 78f3ecb4cb4..7af75a9cc4c 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -74,7 +74,7 @@ module Gitlab if user # Case when a LDAP user already exists in Gitlab. Add the OAuth identity to existing account. log.info "LDAP account found for user #{user.username}. Building new #{auth_hash.provider} identity." - user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider) + user.identities.find_or_initialize_by(extern_uid: auth_hash.uid, provider: auth_hash.provider) else log.info "No existing LDAP account was found in GitLab. Checking for #{auth_hash.provider} account." user = find_by_uid_and_provider diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb index ae85b294d31..4831c46c4be 100644 --- a/lib/gitlab/sidekiq_middleware/memory_killer.rb +++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb @@ -25,7 +25,7 @@ module Gitlab Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\ "#{MAX_RSS}" - Sidekiq.logger.warn "this thread will shut down PID #{Process.pid} "\ + Sidekiq.logger.warn "this thread will shut down PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}"\ "in #{GRACE_TIME} seconds" sleep(GRACE_TIME) @@ -36,7 +36,7 @@ module Gitlab "#{SHUTDOWN_SIGNAL} to PID #{Process.pid}" sleep(SHUTDOWN_WAIT) - Sidekiq.logger.warn "sending #{SHUTDOWN_SIGNAL} to PID #{Process.pid}" + Sidekiq.logger.warn "sending #{SHUTDOWN_SIGNAL} to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}" Process.kill(SHUTDOWN_SIGNAL, Process.pid) end end diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb new file mode 100644 index 00000000000..760ff3e614a --- /dev/null +++ b/lib/gitlab/template/base_template.rb @@ -0,0 +1,67 @@ +module Gitlab + module Template + class BaseTemplate + def initialize(path) + @path = path + end + + def name + File.basename(@path, self.class.extension) + end + + def content + File.read(@path) + end + + class << self + def all + self.categories.keys.flat_map { |cat| by_category(cat) } + end + + def find(key) + file_name = "#{key}#{self.extension}" + + directory = select_directory(file_name) + directory ? new(File.join(category_directory(directory), file_name)) : nil + end + + def categories + raise NotImplementedError + end + + def extension + raise NotImplementedError + end + + def base_dir + raise NotImplementedError + end + + def by_category(category) + templates_for_directory(category_directory(category)) + end + + def category_directory(category) + File.join(base_dir, categories[category]) + end + + private + + def select_directory(file_name) + categories.keys.find do |category| + File.exist?(File.join(category_directory(category), file_name)) + end + end + + def templates_for_directory(dir) + dir << '/' unless dir.end_with?('/') + Dir.glob(File.join(dir, "*#{self.extension}")).select { |f| f =~ filter_regex }.map { |f| new(f) } + end + + def filter_regex + @filter_reges ||= /#{Regexp.escape(extension)}\z/ + end + end + end + end +end diff --git a/lib/gitlab/template/gitignore.rb b/lib/gitlab/template/gitignore.rb new file mode 100644 index 00000000000..964fbfd4de3 --- /dev/null +++ b/lib/gitlab/template/gitignore.rb @@ -0,0 +1,22 @@ +module Gitlab + module Template + class Gitignore < BaseTemplate + class << self + def extension + '.gitignore' + end + + def categories + { + "Languages" => '', + "Global" => 'Global' + } + end + + def base_dir + Rails.root.join('vendor/gitignore') + end + end + end + end +end diff --git a/lib/gitlab/template/gitlab_ci_yml.rb b/lib/gitlab/template/gitlab_ci_yml.rb new file mode 100644 index 00000000000..7f480fe33c0 --- /dev/null +++ b/lib/gitlab/template/gitlab_ci_yml.rb @@ -0,0 +1,27 @@ +module Gitlab + module Template + class GitlabCiYml < BaseTemplate + def content + explanation = "# This file is a template, and might need editing before it works on your project." + [explanation, super].join("\n") + end + + class << self + def extension + '.gitlab-ci.yml' + end + + def categories + { + "General" => '', + "Pages" => 'Pages' + } + end + + def base_dir + Rails.root.join('vendor/gitlab-ci-yml') + end + end + end + end +end diff --git a/lib/tasks/gitlab/import_export.rake b/lib/tasks/gitlab/import_export.rake new file mode 100644 index 00000000000..c2c6031db67 --- /dev/null +++ b/lib/tasks/gitlab/import_export.rake @@ -0,0 +1,13 @@ +namespace :gitlab do + namespace :import_export do + desc "GitLab | Show Import/Export version" + task version: :environment do + puts "Import/Export v#{Gitlab::ImportExport.version}" + end + + desc "GitLab | Display exported DB structure" + task data: :environment do + puts YAML.load_file(Gitlab::ImportExport.config_file)['project_tree'].to_yaml(:SortKeys => true) + end + end +end diff --git a/lib/tasks/gitlab/update_gitignore.rake b/lib/tasks/gitlab/update_gitignore.rake deleted file mode 100644 index 4fd48cccb1d..00000000000 --- a/lib/tasks/gitlab/update_gitignore.rake +++ /dev/null @@ -1,46 +0,0 @@ -namespace :gitlab do - desc "GitLab | Update gitignore" - task :update_gitignore do - unless clone_gitignores - puts "Cloning the gitignores failed".color(:red) - return - end - - remove_unneeded_files(gitignore_directory) - remove_unneeded_files(global_directory) - - puts "Done".color(:green) - end - - def clone_gitignores - FileUtils.rm_rf(gitignore_directory) if Dir.exist?(gitignore_directory) - FileUtils.cd vendor_directory - - system('git clone --depth=1 --branch=master https://github.com/github/gitignore.git') - end - - # Retain only certain files: - # - The LICENSE, because we have to - # - The sub dir global - # - The gitignores themself - # - Dir.entires returns also the entries '.' and '..' - def remove_unneeded_files(path) - Dir.foreach(path) do |file| - FileUtils.rm_rf(File.join(path, file)) unless file =~ /(\.{1,2}|LICENSE|Global|\.gitignore)\z/ - end - end - - private - - def vendor_directory - Rails.root.join('vendor') - end - - def gitignore_directory - File.join(vendor_directory, 'gitignore') - end - - def global_directory - File.join(gitignore_directory, 'Global') - end -end diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake new file mode 100644 index 00000000000..4f76dad7286 --- /dev/null +++ b/lib/tasks/gitlab/update_templates.rake @@ -0,0 +1,54 @@ +namespace :gitlab do + desc "GitLab | Update templates" + task :update_templates do + TEMPLATE_DATA.each { |template| update(template) } + end + + def update(template) + sub_dir = template.repo_url.match(/([a-z-]+)\.git\z/)[1] + dir = File.join(vendor_directory, sub_dir) + + unless clone_repository(template.repo_url, dir) + puts "Cloning the #{sub_dir} templates failed".red + return + end + + remove_unneeded_files(dir, template.cleanup_regex) + puts "Done".green + end + + def clone_repository(url, directory) + FileUtils.rm_rf(directory) if Dir.exist?(directory) + + system("git clone #{url} --depth=1 --branch=master #{directory}") + end + + # Retain only certain files: + # - The LICENSE, because we have to + # - The sub dirs so we can organise the file by category + # - The templates themself + # - Dir.entries returns also the entries '.' and '..' + def remove_unneeded_files(directory, regex) + Dir.foreach(directory) do |file| + FileUtils.rm_rf(File.join(directory, file)) unless file =~ regex + end + end + + private + + Template = Struct.new(:repo_url, :cleanup_regex) + TEMPLATE_DATA = [ + Template.new( + "https://github.com/github/gitignore.git", + /(\.{1,2}|LICENSE|Global|\.gitignore)\z/ + ), + Template.new( + "https://gitlab.com/gitlab-org/gitlab-ci-yml.git", + /(\.{1,2}|LICENSE|Pages|\.gitlab-ci.yml)\z/ + ) + ] + + def vendor_directory + Rails.root.join('vendor') + end +end diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb index eb82476b179..d5f0b289b5b 100644 --- a/spec/controllers/admin/impersonations_controller_spec.rb +++ b/spec/controllers/admin/impersonations_controller_spec.rb @@ -22,7 +22,7 @@ describe Admin::ImpersonationsController do it "responds with status 404" do delete :destroy - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "doesn't sign us in" do @@ -46,7 +46,7 @@ describe Admin::ImpersonationsController do it "responds with status 404" do delete :destroy - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "doesn't sign us in as the impersonator" do @@ -65,7 +65,7 @@ describe Admin::ImpersonationsController do it "responds with status 404" do delete :destroy - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "doesn't sign us in as the impersonator" do diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb index b51b303a714..520a4f6f9c5 100644 --- a/spec/controllers/admin/spam_logs_controller_spec.rb +++ b/spec/controllers/admin/spam_logs_controller_spec.rb @@ -14,7 +14,7 @@ describe Admin::SpamLogsController do it 'lists all spam logs' do get :index - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -22,14 +22,14 @@ describe Admin::SpamLogsController do it 'removes only the spam log when removing log' do expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1) expect(User.find(user.id)).to be_truthy - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'removes user and his spam logs when removing the user' do delete :destroy, id: first_spam.id, remove_user: true expect(flash[:notice]).to eq "User #{user.username} was successfully removed." - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(SpamLog.count).to eq(0) expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 6caf37ddc2c..ab9aa65f7b9 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -17,7 +17,7 @@ describe Admin::UsersController do it 'deletes user' do delete :destroy, id: user.username, format: :json - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect { User.find(user.id) }.to raise_exception(ActiveRecord::RecordNotFound) end end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index ff5b3916273..10824c20c87 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -44,7 +44,7 @@ describe ApplicationController do context "when the 'private_token' param is populated with the private token" do it "logs the user in" do get :index, private_token: user.private_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq("authenticated") end end @@ -54,7 +54,7 @@ describe ApplicationController do it "logs the user in" do @request.headers['PRIVATE-TOKEN'] = user.private_token get :index - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq("authenticated") end end @@ -80,7 +80,7 @@ describe ApplicationController do context "when the 'personal_access_token' param is populated with the personal access token" do it "logs the user in" do get :index, private_token: personal_access_token.token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq('authenticated') end end @@ -89,7 +89,7 @@ describe ApplicationController do it "logs the user in" do @request.headers["PRIVATE-TOKEN"] = personal_access_token.token get :index - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq('authenticated') end end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 28cf804c1b2..60c654f622d 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -29,7 +29,7 @@ describe AutocompleteController do get(:users, project_id: 'unknown') end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end end @@ -58,7 +58,7 @@ describe AutocompleteController do get(:users, group_id: 'unknown') end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end end @@ -114,7 +114,7 @@ describe AutocompleteController do get(:users, project_id: project.id) end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end describe 'GET #users with unknown project' do @@ -122,7 +122,7 @@ describe AutocompleteController do get(:users, project_id: 'unknown') end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end describe 'GET #users with inaccessible group' do @@ -131,7 +131,7 @@ describe AutocompleteController do get(:users, group_id: user.namespace.id) end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end describe 'GET #users with no project' do diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb index cf5c606c723..a3a3309e15e 100644 --- a/spec/controllers/commit_controller_spec.rb +++ b/spec/controllers/commit_controller_spec.rb @@ -155,7 +155,7 @@ describe Projects::CommitController do id: commit.id) expect(response).not_to be_success - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -204,7 +204,7 @@ describe Projects::CommitController do id: master_pickable_commit.id) expect(response).not_to be_success - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 89c2c26a367..ddc54108a7b 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -13,7 +13,7 @@ describe Groups::GroupMembersController do it 'renders index with group members' do get :index, group_id: group - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response).to render_template(:index) end end @@ -26,7 +26,7 @@ describe Groups::GroupMembersController do delete :destroy, group_id: group, id: 42 - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -48,7 +48,7 @@ describe Groups::GroupMembersController do delete :destroy, group_id: group, id: member - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(group.users).to include group_user end end @@ -89,7 +89,7 @@ describe Groups::GroupMembersController do it 'returns 403' do delete :leave, group_id: group - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -118,9 +118,7 @@ describe Groups::GroupMembersController do it 'cannot removes himself from the group' do delete :leave, group_id: group - expect(response).to redirect_to(group_path(group)) - expect(response).to set_flash[:alert].to "You can not leave the \"#{group.name}\" group. Transfer or delete the group." - expect(group.users).to include user + expect(response).to have_http_status(403) end end @@ -134,7 +132,7 @@ describe Groups::GroupMembersController do delete :leave, group_id: group expect(response).to set_flash.to 'Your access request to the group has been withdrawn.' - expect(response).to redirect_to(dashboard_groups_path) + expect(response).to redirect_to(group_path(group)) expect(group.members.request).to be_empty expect(group.users).not_to include user end @@ -168,7 +166,7 @@ describe Groups::GroupMembersController do post :approve_access_request, group_id: group, id: 42 - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -190,7 +188,7 @@ describe Groups::GroupMembersController do post :approve_access_request, group_id: group, id: member - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(group.users).not_to include group_requester end end diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 0d8a68bb51a..56ecf2bb644 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -65,21 +65,21 @@ describe HealthCheckController do it 'supports passing the token in the header' do request.headers['TOKEN'] = token get :index - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'text/plain' expect(response.body).to include('The server is on fire') end it 'supports failure plaintest response' do get :index, token: token - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'text/plain' expect(response.body).to include('The server is on fire') end it 'supports failure json response' do get :index, token: token, format: :json - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false expect(json_response['message']).to include('The server is on fire') @@ -87,7 +87,7 @@ describe HealthCheckController do it 'supports failure xml response' do get :index, token: token, format: :xml - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/xml' expect(xml_response['healthy']).to be false expect(xml_response['message']).to include('The server is on fire') @@ -95,7 +95,7 @@ describe HealthCheckController do it 'supports failure responses for specific checks' do get :index, token: token, checks: 'email', format: :json - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false expect(json_response['message']).to include('Email is on fire') diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index 3c6e54839b5..e478a253b3f 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -15,7 +15,7 @@ describe InvitesController do get :accept, id: token member.reload - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(member.user).to eq(user) expect(flash[:notice]).to include 'You have been granted' end @@ -26,7 +26,7 @@ describe InvitesController do get :decline, id: token expect{member.reload}.to raise_error ActiveRecord::RecordNotFound - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(flash[:notice]).to include 'You have declined the invitation to join' end end diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb index 27e9afe582e..2b334ed1172 100644 --- a/spec/controllers/namespaces_controller_spec.rb +++ b/spec/controllers/namespaces_controller_spec.rb @@ -86,7 +86,7 @@ describe NamespacesController do it "responds with status 404" do get :show, id: group.path - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -102,7 +102,7 @@ describe NamespacesController do it "responds with status 404" do get :show, id: "doesntexist" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index 15d155833b4..6be9489edb2 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -60,7 +60,7 @@ describe NotificationSettingsController do project: { id: private_project.id }, notification_setting: { level: :participating } - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -118,7 +118,7 @@ describe NotificationSettingsController do id: notification_setting, notification_setting: { level: :participating } - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb index af378304893..552899eb36c 100644 --- a/spec/controllers/oauth/applications_controller_spec.rb +++ b/spec/controllers/oauth/applications_controller_spec.rb @@ -12,7 +12,7 @@ describe Oauth::ApplicationsController do it 'shows list of applications' do get :index - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'redirects back to profile page if OAuth applications are disabled' do @@ -21,7 +21,7 @@ describe Oauth::ApplicationsController do get :index - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(response).to redirect_to(profile_path) end end diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb index 4eafc11abaa..2dc9adfd60c 100644 --- a/spec/controllers/profiles/accounts_controller_spec.rb +++ b/spec/controllers/profiles/accounts_controller_spec.rb @@ -13,7 +13,7 @@ describe Profiles::AccountsController do delete :unlink, provider: 'saml' updated_user = User.find(user.id) - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(updated_user.identities.size).to eq(1) expect(updated_user.identities).to include(identity) end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb new file mode 100644 index 00000000000..9444a50b1ce --- /dev/null +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -0,0 +1,40 @@ +require 'rails_helper' + +describe Projects::BlobController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + user = create(:user) + project.team << [user, :master] + + sign_in(user) + end + + describe 'GET diff' do + render_views + + def do_get(opts = {}) + params = { namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: 'master/CHANGELOG' } + get :diff, params.merge(opts) + end + + context 'when essential params are missing' do + it 'renders nothing' do + do_get + + expect(response.body).to be_blank + end + end + + context 'when essential params are present' do + it 'renders the diff content' do + do_get(since: 1, to: 5, offset: 10) + + expect(response.body).to be_present + end + end + end +end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index c4b4a888b4e..f59d4937157 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -103,7 +103,7 @@ describe Projects::BranchesController do namespace_id: project.namespace.to_param, project_id: project.to_param - expect(response.status).to eq(303) + expect(response).to have_http_status(303) end end @@ -121,24 +121,24 @@ describe Projects::BranchesController do context "valid branch name, valid source" do let(:branch) { "feature" } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } end context "valid branch name with unencoded slashes" do let(:branch) { "improve/awesome" } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } end context "valid branch name with encoded slashes" do let(:branch) { "improve%2Fawesome" } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } end context "invalid branch name, valid ref" do let(:branch) { "no-branch" } - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index cbaa3e0b7b2..7cf09fa4a4a 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -14,7 +14,7 @@ describe Projects::IssuesController do it "returns index" do get :index, namespace_id: project.namespace.path, project_id: project.path - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "return 301 if request path doesn't match project path" do @@ -28,7 +28,7 @@ describe Projects::IssuesController do project.save get :index, namespace_id: project.namespace.path, project_id: project.path - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "returns 404 when external issue tracker is enabled" do @@ -36,7 +36,7 @@ describe Projects::IssuesController do allow(project).to receive(:default_issues_tracker?).and_return(false) get :index, namespace_id: project.namespace.path, project_id: project.path - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -248,7 +248,7 @@ describe Projects::IssuesController do before { sign_in(user) } it "rejects a developer to destroy an issue" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -262,7 +262,7 @@ describe Projects::IssuesController do it "deletes the issue" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now end end @@ -280,7 +280,7 @@ describe Projects::IssuesController do project_id: project.path, id: issue.iid, name: "thumbsup") end.to change { issue.award_emoji.count }.by(1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 4b408c03703..1cc35c66c8f 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -264,6 +264,18 @@ describe Projects::MergeRequestsController do merge_when_build_succeeds end + + context 'when project.only_allow_merge_if_build_succeeds? is true' do + before do + project.update_column(:only_allow_merge_if_build_succeeds, true) + end + + it 'returns :merge_when_build_succeeds' do + merge_when_build_succeeds + + expect(assigns(:status)).to eq(:merge_when_build_succeeds) + end + end end end end @@ -272,7 +284,7 @@ describe Projects::MergeRequestsController do it "denies access to users unless they're admin or project owner" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context "when the user is owner" do @@ -285,7 +297,7 @@ describe Projects::MergeRequestsController do it "deletes the merge request" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now end end diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 00bc38b6071..75590c1ed4f 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -18,7 +18,7 @@ describe Projects::NotesController do project_id: project.path, id: note.id, name: "thumbsup") end.to change { note.award_emoji.count }.by(1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "removes the already awarded emoji" do @@ -30,7 +30,7 @@ describe Projects::NotesController do project_id: project.path, id: note.id, name: "thumbsup") end.to change { AwardEmoji.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index fc5f458e795..29aaceb2302 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -58,7 +58,7 @@ describe Projects::ProjectMembersController do get :index, namespace_id: project.namespace, project_id: project end - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } end end @@ -71,7 +71,7 @@ describe Projects::ProjectMembersController do project_id: project, id: 42 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -94,7 +94,7 @@ describe Projects::ProjectMembersController do project_id: project, id: member - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(project.users).to include team_user end end @@ -139,7 +139,7 @@ describe Projects::ProjectMembersController do delete :leave, namespace_id: project.namespace, project_id: project - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -171,11 +171,7 @@ describe Projects::ProjectMembersController do delete :leave, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to( - namespace_project_path(project.namespace, project) - ) - expect(response).to set_flash[:alert].to "You can not leave the \"#{project.human_name}\" project. Transfer or delete the project." - expect(project.users).to include user + expect(response).to have_http_status(403) end end @@ -190,7 +186,7 @@ describe Projects::ProjectMembersController do project_id: project expect(response).to set_flash.to 'Your access request to the project has been withdrawn.' - expect(response).to redirect_to(dashboard_projects_path) + expect(response).to redirect_to(namespace_project_path(project.namespace, project)) expect(project.members.request).to be_empty expect(project.users).not_to include user end @@ -228,7 +224,7 @@ describe Projects::ProjectMembersController do project_id: project, id: 42 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -251,7 +247,7 @@ describe Projects::ProjectMembersController do project_id: project, id: member - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(project.users).not_to include team_requester end end diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 33c35161da3..48f799d8ca1 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::RawController do project_id: public_project.to_param, id: id) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') expect(response.header['Content-Disposition']). to eq("inline") @@ -30,7 +30,7 @@ describe Projects::RawController do project_id: public_project.to_param, id: id) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.header['Content-Type']).to eq('image/jpeg') expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-blob:") end @@ -54,7 +54,7 @@ describe Projects::RawController do project_id: public_project.to_param, id: id) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -65,7 +65,7 @@ describe Projects::RawController do project_id: public_project.to_param, id: id) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index aad62cf20e3..ee905d11fb2 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -36,7 +36,7 @@ describe Projects::RepositoriesController do it "renders Not Found" do get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 0f32a30f18b..b8a28f43707 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::SnippetsController do get :index, namespace_id: project.namespace.path, project_id: project.path expect(assigns(:snippets)).not_to include(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -30,7 +30,7 @@ describe Projects::SnippetsController do get :index, namespace_id: project.namespace.path, project_id: project.path expect(assigns(:snippets)).to include(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -41,7 +41,7 @@ describe Projects::SnippetsController do get :index, namespace_id: project.namespace.path, project_id: project.path expect(assigns(:snippets)).to include(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -56,7 +56,7 @@ describe Projects::SnippetsController do it 'responds with status 404' do get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -67,7 +67,7 @@ describe Projects::SnippetsController do get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param expect(assigns(:snippet)).to eq(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -78,7 +78,7 @@ describe Projects::SnippetsController do get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param expect(assigns(:snippet)).to eq(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -88,7 +88,7 @@ describe Projects::SnippetsController do it 'responds with status 404' do get action, namespace_id: project.namespace.path, project_id: project.path, id: 42 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -98,7 +98,7 @@ describe Projects::SnippetsController do it 'responds with status 404' do get action, namespace_id: project.namespace.path, project_id: project.path, id: 42 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/projects/todo_controller_spec.rb b/spec/controllers/projects/todo_controller_spec.rb index 40a3403b660..5a8bba28594 100644 --- a/spec/controllers/projects/todo_controller_spec.rb +++ b/spec/controllers/projects/todo_controller_spec.rb @@ -22,7 +22,7 @@ describe Projects::TodosController do issuable_type: 'issue') end.to change { user.todos.count }.by(1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -36,7 +36,7 @@ describe Projects::TodosController do issuable_type: 'issue') end.to change { user.todos.count }.by(0) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not create todo for issue when user not logged in' do @@ -47,7 +47,7 @@ describe Projects::TodosController do issuable_type: 'issue') end.to change { user.todos.count }.by(0) - expect(response.status).to eq(302) + expect(response).to have_http_status(302) end end end @@ -69,7 +69,7 @@ describe Projects::TodosController do issuable_type: 'merge_request') end.to change { user.todos.count }.by(1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -83,7 +83,7 @@ describe Projects::TodosController do issuable_type: 'merge_request') end.to change { user.todos.count }.by(0) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not create todo for merge request user has no access to' do @@ -94,7 +94,7 @@ describe Projects::TodosController do issuable_type: 'merge_request') end.to change { user.todos.count }.by(0) - expect(response.status).to eq(302) + expect(response).to have_http_status(302) end end end diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index e74731c9ed8..4e3a2bdb19e 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -64,7 +64,7 @@ describe Projects::TreeController do context "valid SHA commit ID with path" do let(:id) { '6d39438/.gitignore' } - it { expect(response.status).to eq(302) } + it { expect(response).to have_http_status(302) } end end diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 93c4494c660..0893ee89f6a 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -18,7 +18,7 @@ describe Projects::UploadsController do namespace_id: project.namespace.to_param, project_id: project.to_param, format: :json - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -79,7 +79,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -87,7 +87,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -106,7 +106,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -114,7 +114,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -140,7 +140,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -192,7 +192,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -224,7 +224,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -232,7 +232,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -253,7 +253,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -261,7 +261,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -270,7 +270,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index fba545560c7..d60579030c0 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -77,7 +77,7 @@ describe ProjectsController do get :show, namespace_id: public_project.namespace.path, id: public_project.path expect(assigns(:project)).to eq(public_project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -101,7 +101,7 @@ describe ProjectsController do get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase expect(assigns(:project)).to eq(other_project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -146,7 +146,7 @@ describe ProjectsController do expect(project.repository.path).to include(new_path) expect(assigns(:repository).path).to eq(project.repository.path) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -161,7 +161,7 @@ describe ProjectsController do delete :destroy, namespace_id: project.namespace.path, id: project.path expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound) - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(response).to redirect_to(dashboard_projects_path) end end @@ -234,7 +234,27 @@ describe ProjectsController do delete(:remove_fork, namespace_id: project.namespace.to_param, id: project.to_param, format: :js) - expect(response.status).to eq(401) + expect(response).to have_http_status(401) + end + end + + describe "GET refs" do + it "should get a list of branches and tags" do + get :refs, namespace_id: public_project.namespace.path, id: public_project.path + + parsed_body = JSON.parse(response.body) + expect(parsed_body["Branches"]).to include("master") + expect(parsed_body["Tags"]).to include("v1.0.0") + expect(parsed_body["Commits"]).to be_nil + end + + it "should get a list of branches, tags and commits" do + get :refs, namespace_id: public_project.namespace.path, id: public_project.path, ref: "123456" + + parsed_body = JSON.parse(response.body) + expect(parsed_body["Branches"]).to include("master") + expect(parsed_body["Tags"]).to include("v1.0.0") + expect(parsed_body["Commits"]).to include("123456") end end end diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index b3dcb52c500..2a89159c070 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -19,7 +19,7 @@ describe SnippetsController do it 'responds with status 404' do get :show, id: other_personal_snippet.to_param - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -28,7 +28,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -54,7 +54,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -79,7 +79,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -88,7 +88,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -102,7 +102,7 @@ describe SnippetsController do it 'responds with status 404' do get :show, id: 'doesntexist' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -110,7 +110,7 @@ describe SnippetsController do it 'responds with status 404' do get :show, id: 'doesntexist' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -134,7 +134,7 @@ describe SnippetsController do it 'responds with status 404' do get :raw, id: other_personal_snippet.to_param - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -143,7 +143,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -169,7 +169,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -194,7 +194,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -203,7 +203,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -217,7 +217,7 @@ describe SnippetsController do it 'responds with status 404' do get :raw, id: 'doesntexist' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -225,7 +225,7 @@ describe SnippetsController do it 'responds with status 404' do get :raw, id: 'doesntexist' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 73858e6f063..69124ab06bf 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -26,7 +26,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -35,7 +35,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -52,7 +52,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -64,7 +64,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -109,7 +109,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -118,7 +118,7 @@ describe UploadsController do it "responds with status 404" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -133,7 +133,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -145,7 +145,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -181,7 +181,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -190,7 +190,7 @@ describe UploadsController do it "responds with status 404" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -210,7 +210,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -222,7 +222,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -267,7 +267,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -276,7 +276,7 @@ describe UploadsController do it "responds with status 404" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index c61ec174665..8d6f486efdd 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -33,7 +33,7 @@ describe UsersController do it 'renders the show template' do get :show, username: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response).to render_template('show') end end @@ -47,7 +47,7 @@ describe UsersController do context 'when logged out' do it 'renders 404' do get :show, username: user.username - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -56,7 +56,7 @@ describe UsersController do it 'renders show' do get :show, username: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response).to render_template('show') end end @@ -121,7 +121,7 @@ describe UsersController do context 'format html' do it 'renders snippets page' do get :snippets, username: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response).to render_template('show') end end @@ -129,7 +129,7 @@ describe UsersController do context 'format json' do it 'response with snippets json data' do get :snippets, username: user.username, format: :json - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(JSON.parse(response.body)).to have_key('html') end end diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/pipelines.rb index a039bef6f3c..a039bef6f3c 100644 --- a/spec/factories/ci/commits.rb +++ b/spec/factories/ci/pipelines.rb diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 9499cd4e025..2d297776cb0 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -60,6 +60,40 @@ describe "Admin Runners" do it { expect(page).to have_content(@project1.name_with_namespace) } it { expect(page).not_to have_content(@project2.name_with_namespace) } end + + describe 'enable/create' do + before do + @project1.runners << runner + visit admin_runner_path(runner) + end + + it 'enables specific runner for project' do + within '.unassigned-projects' do + click_on 'Enable' + end + + assigned_project = page.find('.assigned-projects') + + expect(assigned_project).to have_content(@project2.path) + end + end + + describe 'disable/destroy' do + before do + @project1.runners << runner + visit admin_runner_path(runner) + end + + it 'enables specific runner for project' do + within '.assigned-projects' do + click_on 'Disable' + end + + new_runner_project = page.find('.unassigned-projects') + + expect(new_runner_project).to have_content(@project1.path) + end + end end describe 'runners registration token' do diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index de6aed74fb4..91704377a07 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -61,7 +61,7 @@ describe "User Feed", feature: true do end it 'should have XHTML summaries in merge request descriptions' do - expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/ + expect(body).to match /Here is the fix: <\/p><div[^>]*><a[^>]*><img[^>]*\/><\/a><\/div>/ end end end diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb index 53b4f027117..203e55a36f2 100644 --- a/spec/features/container_registry_spec.rb +++ b/spec/features/container_registry_spec.rb @@ -26,7 +26,8 @@ describe "Container Registry" do end context 'when there are tags' do - it { expect(page).to have_content(tag_name)} + it { expect(page).to have_content(tag_name) } + it { expect(page).to have_content('d7a513a66') } end end diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 40fea5211e9..7fb28f4174b 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -20,7 +20,7 @@ feature 'Environments', feature: true do context 'without environments' do scenario 'does show no environments' do - expect(page).to have_content('No environments to show') + expect(page).to have_content('You don\'t have any environments right now.') end end @@ -61,7 +61,7 @@ feature 'Environments', feature: true do context 'without deployments' do scenario 'does show no deployments' do - expect(page).to have_content('No deployments for') + expect(page).to have_content('You don\'t have any deployments right now.') end end @@ -108,7 +108,7 @@ feature 'Environments', feature: true do end scenario 'does create a new pipeline' do - expect(page).to have_content('production') + expect(page).to have_content('Production') end end diff --git a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb new file mode 100644 index 00000000000..33bf6d3752f --- /dev/null +++ b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +feature 'Groups > Members > Last owner cannot leave group', feature: true do + let(:owner) { create(:user) } + let(:group) { create(:group) } + + background do + group.add_owner(owner) + login_as(owner) + visit group_path(group) + end + + scenario 'user does not see a "Leave Group" link' do + expect(page).not_to have_content 'Leave Group' + end +end diff --git a/spec/features/groups/members/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb new file mode 100644 index 00000000000..3185ff924b9 --- /dev/null +++ b/spec/features/groups/members/member_leaves_group_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +feature 'Groups > Members > Member leaves group', feature: true do + let(:user) { create(:user) } + let(:owner) { create(:user) } + let(:group) { create(:group, :public) } + + background do + group.add_owner(owner) + group.add_developer(user) + login_as(user) + visit group_path(group) + end + + scenario 'user leaves group' do + click_link 'Leave Group' + + expect(current_path).to eq(dashboard_groups_path) + expect(group.users.exists?(user.id)).to be_falsey + end +end diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index 22525ce530b..321c9bad7d0 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -42,7 +42,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do def expect_visible_access_request(group, user) expect(group.members.request.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{group.name} access requests (1)" + expect(page).to have_content "#{group.name} access requests 1" expect(page).to have_content user.name end end diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb index a878a96b6ee..4944301c938 100644 --- a/spec/features/groups/members/user_requests_access_spec.rb +++ b/spec/features/groups/members/user_requests_access_spec.rb @@ -4,6 +4,7 @@ feature 'Groups > Members > User requests access', feature: true do let(:user) { create(:user) } let(:owner) { create(:user) } let(:group) { create(:group, :public) } + let!(:project) { create(:project, :private, namespace: group) } background do group.add_owner(owner) @@ -21,6 +22,21 @@ feature 'Groups > Members > User requests access', feature: true do expect(page).to have_content 'Your request for access has been queued for review.' expect(page).to have_content 'Withdraw Access Request' + expect(page).not_to have_content 'Leave Group' + end + + scenario 'user does not see private projects' do + perform_enqueued_jobs { click_link 'Request Access' } + + expect(page).not_to have_content project.name + end + + scenario 'user does not see group in the Dashboard > Groups page' do + perform_enqueued_jobs { click_link 'Request Access' } + + visit dashboard_groups_path + + expect(page).not_to have_content group.name end scenario 'user is not listed in the group members page' do diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb index 7143d0e40f3..afc093cc1f5 100644 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ b/spec/features/issues/bulk_assignment_labels_spec.rb @@ -10,7 +10,7 @@ feature 'Issues > Labels bulk assignment', feature: true do let!(:bug) { create(:label, project: project, title: 'bug') } let!(:feature) { create(:label, project: project, title: 'feature') } - context 'as a allowed user', js: true do + context 'as an allowed user', js: true do before do project.team << [user, :master] @@ -164,6 +164,133 @@ feature 'Issues > Labels bulk assignment', feature: true do end end end + + context 'toggling a milestone' do + let!(:milestone) { create(:milestone, project: project, title: 'First Release') } + + context 'setting a milestone' do + before do + issue1.labels << bug + issue2.labels << feature + visit namespace_project_issues_path(project.namespace, project) + end + + it 'labels are kept' do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + + check 'check_all_issues' + open_milestone_dropdown(['First Release']) + update_issues + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'First Release' + end + end + + context 'setting a milestone and adding another label' do + before do + issue1.labels << bug + + visit namespace_project_issues_path(project.namespace, project) + end + + it 'existing label is kept and new label is present' do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + + check 'check_all_issues' + open_milestone_dropdown ['First Release'] + open_labels_dropdown ['feature'] + update_issues + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + expect(find("#issue_#{issue1.id}")).to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'First Release' + end + end + + context 'setting a milestone and removing existing label' do + before do + issue1.labels << bug + issue1.labels << feature + issue2.labels << feature + + visit namespace_project_issues_path(project.namespace, project) + end + + it 'existing label is kept and new label is present' do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + + check 'check_all_issues' + open_milestone_dropdown ['First Release'] + unmark_labels_in_dropdown ['feature'] + update_issues + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).not_to have_content 'feature' + expect(find("#issue_#{issue1.id}")).to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'First Release' + end + end + + context 'unsetting a milestone' do + before do + issue1.milestone = milestone + issue2.milestone = milestone + issue1.save + issue2.save + issue1.labels << bug + issue2.labels << feature + + visit namespace_project_issues_path(project.namespace, project) + end + + it 'labels are kept' do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'First Release' + + check 'check_all_issues' + open_milestone_dropdown(['No Milestone']) + update_issues + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).not_to have_content 'First Release' + expect(find("#issue_#{issue2.id}")).to have_content 'feature' + expect(find("#issue_#{issue2.id}")).not_to have_content 'First Release' + end + end + end + + context 'toggling checked issues' do + before do + issue1.labels << bug + + visit namespace_project_issues_path(project.namespace, project) + end + + it do + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + + check_issue issue1 + open_labels_dropdown ['feature'] + uncheck_issue issue1 + check_issue issue1 + update_issues + sleep 1 # needed + + expect(find("#issue_#{issue1.id}")).to have_content 'bug' + expect(find("#issue_#{issue1.id}")).not_to have_content 'feature' + end + end end context 'as a guest' do @@ -181,6 +308,16 @@ feature 'Issues > Labels bulk assignment', feature: true do end end + def open_milestone_dropdown(items = []) + page.within('.issues_bulk_update') do + click_button 'Milestone' + wait_for_ajax + items.map do |item| + click_link item + end + end + end + def open_labels_dropdown(items = [], unmark = false) page.within('.issues_bulk_update') do click_button 'Label' @@ -201,12 +338,20 @@ feature 'Issues > Labels bulk assignment', feature: true do open_labels_dropdown(items, true) end - def check_issue(issue) + def check_issue(issue, uncheck = false) page.within('.issues-list') do - check "selected_issue_#{issue.id}" + if uncheck + uncheck "selected_issue_#{issue.id}" + else + check "selected_issue_#{issue.id}" + end end end + def uncheck_issue(issue) + check_issue(issue, true) + end + def update_issues click_button 'Update issues' wait_for_ajax diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index c3cb3379440..5065dfb849c 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -22,7 +22,7 @@ describe 'Issues', feature: true do before do visit edit_namespace_project_issue_path(project.namespace, project, issue) - click_button "Go full screen" + find('.js-zen-enter').click end it 'should open new issue popup' do diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb index 51be81d634c..01e90618a98 100644 --- a/spec/features/projects/badges/list_spec.rb +++ b/spec/features/projects/badges/list_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' feature 'list of badges' do - include Select2Helper - background do user = create(:user) project = create(:project) @@ -24,7 +22,11 @@ feature 'list of badges' do end scenario 'user changes current ref on badges list page', js: true do - select2('improve/awesome', from: '#ref') + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'improve/awesome' + end expect(page).to have_content 'badges/improve/awesome/build.svg' end diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb index 073a83b6896..9ebef505b92 100644 --- a/spec/features/projects/files/gitignore_dropdown_spec.rb +++ b/spec/features/projects/files/gitignore_dropdown_spec.rb @@ -24,6 +24,7 @@ feature 'User wants to add a .gitignore file', feature: true do end wait_for_ajax + expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Rails') expect(page).to have_content('/.bundle') expect(page).to have_content('# Gemfile.lock, .ruby-version, .ruby-gemset') end diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb new file mode 100644 index 00000000000..b8c06c383fb --- /dev/null +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +feature 'User wants to add a .gitlab-ci.yml file', feature: true do + include WaitForAjax + + before do + user = create(:user) + project = create(:project) + project.team << [user, :master] + login_as user + visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitlab-ci.yml') + end + + scenario 'user can see .gitlab-ci.yml dropdown' do + expect(page).to have_css('.gitlab-ci-yml-selector') + end + + scenario 'user can pick a template from the dropdown', js: true do + find('.js-gitlab-ci-yml-selector').click + wait_for_ajax + within '.gitlab-ci-yml-selector' do + find('.dropdown-input-field').set('jekyll') + find('.dropdown-content li', text: 'jekyll').click + end + wait_for_ajax + + expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'jekyll') + expect(page).to have_content('This file is a template, and might need editing before it works on your project') + expect(page).to have_content('jekyll build -d test') + end +end diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index 5fe4caa12f0..aa2d906fa2e 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -41,7 +41,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do def expect_visible_access_request(project, user) expect(project.members.request.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{project.name} access requests (1)" + expect(page).to have_content "#{project.name} access requests 1" expect(page).to have_content user.name end end diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb new file mode 100644 index 00000000000..79dec442818 --- /dev/null +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +feature 'Projects > Members > Member leaves project', feature: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + + background do + project.team << [user, :developer] + login_as(user) + visit namespace_project_path(project.namespace, project) + end + + scenario 'user leaves project' do + click_link 'Leave Project' + + expect(current_path).to eq(dashboard_projects_path) + expect(project.users.exists?(user.id)).to be_falsey + end +end diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb new file mode 100644 index 00000000000..67811b1048e --- /dev/null +++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +feature 'Projects > Members > Owner cannot leave project', feature: true do + let(:owner) { create(:user) } + let(:project) { create(:project) } + + background do + project.team << [owner, :owner] + login_as(owner) + visit namespace_project_path(project.namespace, project) + end + + scenario 'user does not see a "Leave Project" link' do + expect(page).not_to have_content 'Leave Project' + end +end diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index fd92a3a2f0c..af420c170ef 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -21,6 +21,7 @@ feature 'Projects > Members > User requests access', feature: true do expect(page).to have_content 'Your request for access has been queued for review.' expect(page).to have_content 'Withdraw Access Request' + expect(page).not_to have_content 'Leave Project' end scenario 'user is not listed in the project members page' do diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 9dd0378d165..6fa8298d489 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -70,22 +70,6 @@ feature 'Project', feature: true do end end - describe 'leave project link' do - let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } - - before do - login_with(user) - project.team.add_user(user, Gitlab::Access::MASTER) - visit namespace_project_path(project.namespace, project) - end - - it 'click project-settings and find leave project' do - find('#project-settings-button').click - expect(page).to have_link('Leave Project') - end - end - describe 'project title' do include WaitForAjax diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 8625ea6bc10..13d980a326f 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -288,4 +288,142 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end + + describe "GET /:project_path/pipelines" do + subject { namespace_project_pipelines_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/pipelines/:id" do + let(:pipeline) { create(:ci_pipeline, project: project) } + subject { namespace_project_pipeline_path(project.namespace, project, pipeline) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/builds" do + subject { namespace_project_builds_path(project.namespace, project) } + + context "when allowed for public and internal" do + before { project.update(public_builds: true) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + context "when disallowed for public and internal" do + before { project.update(public_builds: false) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + end + + describe "GET /:project_path/builds/:id" do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + subject { namespace_project_build_path(project.namespace, project, build.id) } + + context "when allowed for public and internal" do + before { project.update(public_builds: true) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + context "when disallowed for public and internal" do + before { project.update(public_builds: false) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + end + + describe "GET /:project_path/environments" do + subject { namespace_project_environments_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/environments/:id" do + let(:environment) { create(:environment, project: project) } + subject { namespace_project_environment_path(project.namespace, project, environment) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/environments/new" do + subject { new_namespace_project_environment_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 544270b4037..ac9690cc127 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -260,4 +260,106 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end + + describe "GET /:project_path/pipelines" do + subject { namespace_project_pipelines_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/pipelines/:id" do + let(:pipeline) { create(:ci_pipeline, project: project) } + subject { namespace_project_pipeline_path(project.namespace, project, pipeline) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/builds" do + subject { namespace_project_builds_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/builds/:id" do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + subject { namespace_project_build_path(project.namespace, project, build.id) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/environments" do + subject { namespace_project_environments_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/environments/:id" do + let(:environment) { create(:environment, project: project) } + subject { namespace_project_environment_path(project.namespace, project, environment) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/environments/new" do + subject { new_namespace_project_environment_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index f6c6687e162..737897de52b 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -109,6 +109,35 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :external } end + describe "GET /:project_path/pipelines" do + subject { namespace_project_pipelines_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } + it { is_expected.to be_allowed_for :visitor } + end + + describe "GET /:project_path/pipelines/:id" do + let(:pipeline) { create(:ci_pipeline, project: project) } + subject { namespace_project_pipeline_path(project.namespace, project, pipeline) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } + it { is_expected.to be_allowed_for :visitor } + end + describe "GET /:project_path/builds" do subject { namespace_project_builds_path(project.namespace, project) } @@ -191,7 +220,7 @@ describe "Public Project Access", feature: true do describe "GET /:project_path/environments/:id" do let(:environment) { create(:environment, project: project) } - subject { namespace_project_environments_path(project.namespace, project, environment) } + subject { namespace_project_environment_path(project.namespace, project, environment) } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for owner } diff --git a/spec/fixtures/container_registry/tag_manifest.json b/spec/fixtures/container_registry/tag_manifest.json index 1b6008e2872..8d1b874c29b 100644 --- a/spec/fixtures/container_registry/tag_manifest.json +++ b/spec/fixtures/container_registry/tag_manifest.json @@ -1 +1,16 @@ -{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/octet-stream","size":1145,"digest":"sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","size":2319870,"digest":"sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"}]} +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/octet-stream", + "size": 1145, + "digest": "sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 2319870, + "digest": "sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d" + } + ] +} diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index f6c1005d265..bb28866f010 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -174,51 +174,6 @@ describe ApplicationHelper do end end - describe 'grouped_options_refs' do - let(:options) { helper.grouped_options_refs } - let(:project) { create(:project) } - - before do - assign(:project, project) - - # Override Rails' grouped_options_for_select helper to just return the - # first argument (`options`), since it's easier to work with than the - # generated HTML. - allow(helper).to receive(:grouped_options_for_select). - and_wrap_original { |_, *args| args.first } - end - - it 'includes a list of branch names' do - expect(options[0][0]).to eq('Branches') - expect(options[0][1]).to include('master', 'feature') - end - - it 'includes a list of tag names' do - expect(options[1][0]).to eq('Tags') - expect(options[1][1]).to include('v1.0.0', 'v1.1.0') - end - - it 'includes a specific commit ref if defined' do - # Must be an instance variable - ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8' - assign(:ref, ref) - - expect(options[2][0]).to eq('Commit') - expect(options[2][1]).to eq([ref]) - end - - it 'sorts tags in a natural order' do - # Stub repository.tag_names to make sure we get some valid testing data - expect(project.repository).to receive(:tag_names). - and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿', - 'v1.0.9a', 'v2.0-rc1', 'v2.0rc2']) - - expect(options[1][1]). - to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0rc1¿', 'v2.0-rc1', 'v1.0.10', - 'v1.0.9', 'v1.0.9a']) - end - end - describe 'simple_sanitize' do let(:a_tag) { '<a href="#">Foo</a>' } diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index ff98249570d..5e7594170c5 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -1,11 +1,6 @@ require 'spec_helper' describe VisibilityLevelHelper do - include Haml::Helpers - - before :all do - init_haml_helpers - end let(:project) { build(:project) } let(:group) { build(:group) } diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb index e58f2c80e95..1bcae8a27db 100644 --- a/spec/initializers/settings_spec.rb +++ b/spec/initializers/settings_spec.rb @@ -1,3 +1,4 @@ +require 'spec_helper' require_relative '../../config/initializers/1_settings' describe Settings, lib: true do diff --git a/spec/javascripts/application_spec.js.coffee b/spec/javascripts/application_spec.js.coffee index 8af39c41f2f..4b6a2bb5440 100644 --- a/spec/javascripts/application_spec.js.coffee +++ b/spec/javascripts/application_spec.js.coffee @@ -1,4 +1,4 @@ -#= require lib/common_utils +#= require lib/utils/common_utils describe 'Application', -> describe 'disable buttons', -> diff --git a/spec/javascripts/awards_handler_spec.js.coffee b/spec/javascripts/awards_handler_spec.js.coffee index ba191199dc7..d7f9c6fc076 100644 --- a/spec/javascripts/awards_handler_spec.js.coffee +++ b/spec/javascripts/awards_handler_spec.js.coffee @@ -160,7 +160,6 @@ describe 'AwardsHandler', -> expect($('[data-emoji=angel]').is(':visible')).toBe no expect($('[data-emoji=anger]').is(':visible')).toBe no expect($('[data-emoji=alien]').is(':visible')).toBe yes - expect($('h5.emoji-search').is(':visible')).toBe yes describe 'emoji menu', -> diff --git a/spec/javascripts/fixtures/emoji_menu.coffee b/spec/javascripts/fixtures/emoji_menu.coffee index e529dd5f1cd..ce1a41390d2 100644 --- a/spec/javascripts/fixtures/emoji_menu.coffee +++ b/spec/javascripts/fixtures/emoji_menu.coffee @@ -1,7 +1,7 @@ window.emojiMenu = """ <div class='emoji-menu'> + <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" /> <div class='emoji-menu-content'> - <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" /> <h5 class='emoji-menu-title'> Emoticons </h5> diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index ea27f36e9b5..d84d80f266b 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -1,3 +1,4 @@ +#= require lib/utils/text_utility #= require issue describe 'Issue', -> @@ -38,7 +39,7 @@ describe 'reopen/close issue', -> expect(typeof $btnClose.prop('disabled')).toBe('undefined') $btnClose.trigger('click') - + expect($btnReopen).toBeVisible() expect($btnClose).toBeHidden() expect($('div.status-box-closed')).toBeVisible() @@ -50,7 +51,7 @@ describe 'reopen/close issue', -> expect(req.type).toBe('PUT') expect(req.url).toBe('http://goesnowhere.nothing/whereami') req.success saved: false - + $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') $btnClose.attr('href','http://goesnowhere.nothing/whereami') @@ -59,7 +60,7 @@ describe 'reopen/close issue', -> expect(typeof $btnClose.prop('disabled')).toBe('undefined') $btnClose.trigger('click') - + expect($btnReopen).toBeHidden() expect($btnClose).toBeVisible() expect($('div.status-box-closed')).toBeHidden() @@ -73,7 +74,7 @@ describe 'reopen/close issue', -> expect(req.type).toBe('PUT') expect(req.url).toBe('http://goesnowhere.nothing/whereami') req.error() - + $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') $btnClose.attr('href','http://goesnowhere.nothing/whereami') @@ -82,7 +83,7 @@ describe 'reopen/close issue', -> expect(typeof $btnClose.prop('disabled')).toBe('undefined') $btnClose.trigger('click') - + expect($btnReopen).toBeHidden() expect($btnClose).toBeVisible() expect($('div.status-box-closed')).toBeHidden() @@ -105,4 +106,4 @@ describe 'reopen/close issue', -> expect($btnReopen).toBeHidden() expect($btnClose).toBeVisible() expect($('div.status-box-open')).toBeVisible() - expect($('div.status-box-closed')).toBeHidden()
\ No newline at end of file + expect($('div.status-box-closed')).toBeHidden() diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee index 9be29097f4c..f0d26fb5446 100644 --- a/spec/javascripts/project_title_spec.js.coffee +++ b/spec/javascripts/project_title_spec.js.coffee @@ -1,6 +1,6 @@ #= require bootstrap #= require select2 -#= require lib/type_utility +#= require lib/utils/type_utility #= require gl_dropdown #= require api #= require project_select diff --git a/spec/javascripts/search_autocomplete_spec.js.coffee b/spec/javascripts/search_autocomplete_spec.js.coffee index e77177783a7..1c1faca3333 100644 --- a/spec/javascripts/search_autocomplete_spec.js.coffee +++ b/spec/javascripts/search_autocomplete_spec.js.coffee @@ -1,8 +1,8 @@ #= require gl_dropdown #= require search_autocomplete #= require jquery -#= require lib/common_utils -#= require lib/type_utility +#= require lib/utils/common_utils +#= require lib/utils/type_utility #= require fuzzaldrin-plus diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb index dd5594750c8..a2a1ed58d1b 100644 --- a/spec/lib/banzai/filter/image_link_filter_spec.rb +++ b/spec/lib/banzai/filter/image_link_filter_spec.rb @@ -21,4 +21,9 @@ describe Banzai::Filter::ImageLinkFilter, lib: true do doc = filter(image('https://i.imgur.com/DfssX9C.jpg')) expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href'] end + + it 'wraps the image with a link and a div' do + doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.to_html).to include('<div class="image-container">') + end end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 5b63c946114..8d6ce114aa9 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -198,4 +198,40 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/) end end + + describe '#issues_per_Project' do + context 'using an internal issue tracker' do + it 'returns a Hash containing the issues per project' do + doc = Nokogiri::HTML.fragment('') + filter = described_class.new(doc, project: project) + + expect(filter).to receive(:projects_per_reference). + and_return({ project.path_with_namespace => project }) + + expect(filter).to receive(:references_per_project). + and_return({ project.path_with_namespace => Set.new([issue.iid]) }) + + expect(filter.issues_per_project). + to eq({ project => { issue.iid => issue } }) + end + end + + context 'using an external issue tracker' do + it 'returns a Hash containing the issues per project' do + doc = Nokogiri::HTML.fragment('') + filter = described_class.new(doc, project: project) + + expect(project).to receive(:default_issues_tracker?).and_return(false) + + expect(filter).to receive(:projects_per_reference). + and_return({ project.path_with_namespace => project }) + + expect(filter).to receive(:references_per_project). + and_return({ project.path_with_namespace => Set.new([1]) }) + + expect(filter.issues_per_project[project][1]). + to be_an_instance_of(ExternalIssue) + end + end + end end diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 0e6685f0ffb..b9e4a4eaf0e 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -132,11 +132,8 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do path = 'files/images/한글.png' escaped = Addressable::URI.escape(path) - # Stub these methods so the file doesn't actually need to be in the repo - allow_any_instance_of(described_class). - to receive(:file_exists?).and_return(true) - allow_any_instance_of(described_class). - to receive(:image?).with(path).and_return(true) + # Stub this method so the file doesn't actually need to be in the repo + allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw) doc = filter(image(escaped)) expect(doc.at_css('img')['src']).to match '/raw/' diff --git a/spec/lib/banzai/note_renderer_spec.rb b/spec/lib/banzai/note_renderer_spec.rb new file mode 100644 index 00000000000..98f76f36fd5 --- /dev/null +++ b/spec/lib/banzai/note_renderer_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Banzai::NoteRenderer do + describe '.render' do + it 'renders a Note' do + note = double(:note) + project = double(:project) + wiki = double(:wiki) + user = double(:user) + + expect(Banzai::ObjectRenderer).to receive(:new). + with(project, user, + requested_path: 'foo', + project_wiki: wiki, + ref: 'bar', + pipeline: :note). + and_call_original + + expect_any_instance_of(Banzai::ObjectRenderer). + to receive(:render).with([note], :note) + + described_class.render([note], project, user, 'foo', wiki, 'bar') + end + end +end diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb new file mode 100644 index 00000000000..44256b32bdc --- /dev/null +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +describe Banzai::ObjectRenderer do + let(:project) { create(:empty_project) } + let(:user) { project.owner } + + describe '#render' do + it 'renders and redacts an Array of objects' do + renderer = described_class.new(project, user) + object = double(:object, note: 'hello', note_html: nil) + + expect(renderer).to receive(:render_objects).with([object], :note). + and_call_original + + expect(renderer).to receive(:redact_documents). + with(an_instance_of(Array)). + and_call_original + + expect(object).to receive(:note_html=).with('<p>hello</p>') + + renderer.render([object], :note) + end + end + + describe '#render_objects' do + it 'renders an Array of objects' do + object = double(:object, note: 'hello') + renderer = described_class.new(project, user) + + expect(renderer).to receive(:render_attribute).with(object, :note). + and_call_original + + rendered = renderer.render_objects([object], :note) + + expect(rendered).to be_an_instance_of(Array) + expect(rendered[0]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment) + end + end + + describe '#redact_documents' do + it 'redacts a set of documents and returns them as an Array of Strings' do + doc = Nokogiri::HTML.fragment('<p>hello</p>') + renderer = described_class.new(project, user) + + expect_any_instance_of(Banzai::Redactor).to receive(:redact). + with([doc]). + and_call_original + + redacted = renderer.redact_documents([doc]) + + expect(redacted).to eq(['<p>hello</p>']) + end + end + + describe '#context_for' do + let(:object) { double(:object, note: 'hello') } + let(:renderer) { described_class.new(project, user) } + + it 'returns a Hash' do + expect(renderer.context_for(object, :note)).to be_an_instance_of(Hash) + end + + it 'includes the cache key' do + context = renderer.context_for(object, :note) + + expect(context[:cache_key]).to eq([object, :note]) + end + + context 'when the object responds to "author"' do + it 'includes the author in the context' do + expect(object).to receive(:author).and_return('Alice') + + context = renderer.context_for(object, :note) + + expect(context[:author]).to eq('Alice') + end + end + + context 'when the object does not respond to "author"' do + it 'does not include the author in the context' do + context = renderer.context_for(object, :note) + + expect(context.key?(:author)).to eq(false) + end + end + end + + describe '#render_attribute' do + it 'renders the attribute of an object' do + object = double(:doc, note: 'hello') + renderer = described_class.new(project, user, pipeline: :note) + doc = renderer.render_attribute(object, :note) + + expect(doc).to be_an_instance_of(Nokogiri::HTML::DocumentFragment) + expect(doc.to_html).to eq('<p>hello</p>') + end + end + + describe '#base_context' do + let(:context) do + described_class.new(project, user, pipeline: :note).base_context + end + + it 'returns a Hash' do + expect(context).to be_an_instance_of(Hash) + end + + it 'includes the custom attributes' do + expect(context[:pipeline]).to eq(:note) + end + + it 'includes the current user' do + expect(context[:current_user]).to eq(user) + end + + it 'includes the current project' do + expect(context[:project]).to eq(project) + end + end +end diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb new file mode 100644 index 00000000000..488f465bcda --- /dev/null +++ b/spec/lib/banzai/redactor_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Banzai::Redactor do + let(:user) { build(:user) } + let(:project) { build(:empty_project) } + let(:redactor) { described_class.new(project, user) } + + describe '#redact' do + it 'redacts an Array of documents' do + doc1 = Nokogiri::HTML. + fragment('<a class="gfm" data-reference-type="issue">foo</a>') + + doc2 = Nokogiri::HTML. + fragment('<a class="gfm" data-reference-type="issue">bar</a>') + + expect(redactor).to receive(:nodes_visible_to_user).and_return([]) + + expect(redactor.redact([doc1, doc2])).to eq([doc1, doc2]) + + expect(doc1.to_html).to eq('foo') + expect(doc2.to_html).to eq('bar') + end + end + + describe '#redact_nodes' do + it 'redacts an Array of nodes' do + doc = Nokogiri::HTML.fragment('<a href="foo">foo</a>') + node = doc.children[0] + + expect(redactor).to receive(:nodes_visible_to_user). + with([node]). + and_return(Set.new) + + redactor.redact_nodes([node]) + + expect(doc.to_html).to eq('foo') + end + end + + describe '#nodes_visible_to_user' do + it 'returns a Set containing the visible nodes' do + doc = Nokogiri::HTML.fragment('<a data-reference-type="issue"></a>') + node = doc.children[0] + + expect_any_instance_of(Banzai::ReferenceParser::IssueParser). + to receive(:nodes_visible_to_user). + with(user, [node]). + and_return([node]) + + expect(redactor.nodes_visible_to_user([node])).to eq(Set.new([node])) + end + end +end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 6ef6a59f186..147301b3128 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1219,5 +1219,17 @@ EOT end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings") end end + + describe "Validate configuration templates" do + templates = Dir.glob("#{Rails.root.join('vendor/gitlab-ci-yml')}/**/*.gitlab-ci.yml") + + templates.each do |file| + it "does not return errors for #{file}" do + file = File.read(file) + + expect { GitlabCiYamlProcessor.new(file) }.not_to raise_error + end + end + end end end diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index 1620eb6c60a..364532e94e3 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::Highlight, lib: true do include RepoHelpers let(:project) { create(:project) } + let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } describe '.highlight_lines' do @@ -18,4 +19,30 @@ describe Gitlab::Highlight, lib: true do end end + describe 'custom highlighting from .gitattributes' do + let(:branch) { 'gitattributes' } + let(:blob) { repository.blob_at_branch(branch, path) } + + let(:highlighter) do + Gitlab::Highlight.new(blob.path, blob.data, repository: repository) + end + + before { project.change_head('gitattributes') } + + describe 'basic language selection' do + let(:path) { 'custom-highlighting/test.gitlab-custom' } + it 'highlights as ruby' do + expect(highlighter.lexer.tag).to eq 'ruby' + end + end + + describe 'cgi options' do + let(:path) { 'custom-highlighting/test.gitlab-cgi' } + + it 'highlights as json with erb' do + expect(highlighter.lexer.tag).to eq 'erb' + expect(highlighter.lexer.parent.tag).to eq 'json' + end + end + end end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 400d44ac162..403bd582ef3 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -4894,6 +4894,29 @@ "started_at": null, "finished_at": null, "duration": null, + "notes": [ + { + "id": 999, + "note": "Natus rerum qui dolorem dolorum voluptas.", + "noteable_type": "Commit", + "author_id": 1, + "created_at": "2016-03-22T15:19:59.469Z", + "updated_at": "2016-03-22T15:19:59.469Z", + "project_id": 5, + "attachment": { + "url": null + }, + "line_code": null, + "commit_id": "be93687618e4b132087f430a4d8fc3a609c9b77c", + "noteable_id": 36, + "system": false, + "st_diff": null, + "updated_by_id": null, + "author": { + "name": "Administrator" + } + } + ], "statuses": [ { "id": 71, diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 7a40a43f8ae..23036ab8108 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -18,6 +18,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do it 'restores models based on JSON' do expect(restored_project_json).to be true end + + it 'creates a valid pipeline note' do + restored_project_json + + expect(Ci::Pipeline.first.notes).not_to be_empty + end end end end diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb index 88814bc474d..659facd6c19 100644 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -17,12 +17,15 @@ describe Gitlab::Lfs::Router, lib: true do } end - let(:lfs_router_auth) { new_lfs_router(project, user) } - let(:lfs_router_noauth) { new_lfs_router(project, nil) } - let(:lfs_router_public_auth) { new_lfs_router(public_project, user) } - let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) } - let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) } - let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) } + let(:lfs_router_auth) { new_lfs_router(project, user: user) } + let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) } + let(:lfs_router_noauth) { new_lfs_router(project) } + let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) } + let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) } + let(:lfs_router_public_noauth) { new_lfs_router(public_project) } + let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) } + let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) } + let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) } let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } let(:sample_size) { 499013 } @@ -80,6 +83,7 @@ describe Gitlab::Lfs::Router, lib: true do context 'with required headers' do before do + project.lfs_objects << lfs_object env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile" end @@ -91,7 +95,6 @@ describe Gitlab::Lfs::Router, lib: true do context 'when user has project access' do before do - project.lfs_objects << lfs_object project.team << [user, :master] end @@ -104,6 +107,17 @@ describe Gitlab::Lfs::Router, lib: true do expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) end end + + context 'when CI is authorized' do + it "responds with status 200" do + expect(lfs_router_ci_auth.try_call.first).to eq(200) + end + + it "responds with the file location" do + expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") + expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) + end + end end context 'without required headers' do @@ -134,143 +148,145 @@ describe Gitlab::Lfs::Router, lib: true do end describe 'download' do - describe 'when user is authenticated' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end - describe 'when user has download access' do + shared_examples 'an authorized requests' do + context 'when downloading an lfs object that is assigned to our project' do before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :reporter] + project.lfs_objects << lfs_object end - context 'when downloading an lfs object that is assigned to our project' do - before do - project.lfs_objects << lfs_object - end - - it 'responds with status 200 and href to download' do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) + it 'responds with status 200 and href to download' do + response = router.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => { 'Authorization' => @auth } - } + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => auth } } - }]) - end + } + }]) end + end - context 'when downloading an lfs object that is assigned to other project' do - before do - public_project.lfs_objects << lfs_object - end + context 'when downloading an lfs object that is assigned to other project' do + before do + public_project.lfs_objects << lfs_object + end - it 'responds with status 200 and error message' do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) + it 'responds with status 200 and error message' do + response = router.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) - end + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) end + end - context 'when downloading a lfs object that does not exist' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end + context 'when downloading a lfs object that does not exist' do + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end - it "responds with status 200 and error message" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) + it "responds with status 200 and error message" do + response = router.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq('objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) - end + expect(response_body).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) end + end - context 'when downloading one new and one existing lfs object' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ] - }.to_json - env['rack.input'] = StringIO.new(body) - project.lfs_objects << lfs_object - end + context 'when downloading one new and one existing lfs object' do + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] + }.to_json + env['rack.input'] = StringIO.new(body) + project.lfs_objects << lfs_object + end - it "responds with status 200 with upload hypermedia link for the new object" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) + it "responds with status 200 with upload hypermedia link for the new object" do + response = router.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq('objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }, - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => { 'Authorization' => @auth } - } + expect(response_body).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }, + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => auth } } - }]) - end + } + }]) end end + end + + context 'when user is authenticated' do + let(:auth) { authorize(user) } + + before do + env["HTTP_AUTHORIZATION"] = auth + project.team << [user, role] + end + + it_behaves_like 'an authorized requests' do + let(:role) { :reporter } + let(:router) { lfs_router_auth } + end context 'when user does is not member of the project' do - before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :guest] - end + let(:role) { :guest } it 'responds with 403' do expect(lfs_router_auth.try_call.first).to eq(403) @@ -278,11 +294,7 @@ describe Gitlab::Lfs::Router, lib: true do end context 'when user does not have download access' do - before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :guest] - end + let(:role) { :guest } it 'responds with 403' do expect(lfs_router_auth.try_call.first).to eq(403) @@ -290,18 +302,19 @@ describe Gitlab::Lfs::Router, lib: true do end end - context 'when user is not authenticated' do + context 'when CI is authorized' do + let(:auth) { 'gitlab-ci-token:password' } + before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }], + env["HTTP_AUTHORIZATION"] = auth + end - }.to_json - env['rack.input'] = StringIO.new(body) + it_behaves_like 'an authorized requests' do + let(:router) { lfs_router_ci_auth } end + end + context 'when user is not authenticated' do describe 'is accessing public project' do before do public_project.lfs_objects << lfs_object @@ -338,17 +351,17 @@ describe Gitlab::Lfs::Router, lib: true do end describe 'upload' do - describe 'when user is authenticated' do - before do - body = { 'operation' => 'upload', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end + before do + body = { 'operation' => 'upload', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + describe 'when request is authenticated' do describe 'when user has project push access' do before do @auth = authorize(user) @@ -440,15 +453,15 @@ describe Gitlab::Lfs::Router, lib: true do expect(lfs_router_auth.try_call.first).to eq(403) end end - end - context 'when user is not authenticated' do - before do - env['rack.input'] = StringIO.new( - { 'objects' => [], 'operation' => 'upload' }.to_json - ) + context 'when CI is authorized' do + it 'responds with 401' do + expect(lfs_router_ci_auth.try_call.first).to eq(401) + end end + end + context 'when user is not authenticated' do context 'when user has push access' do before do project.team << [user, :master] @@ -465,6 +478,18 @@ describe Gitlab::Lfs::Router, lib: true do end end end + + context 'when CI is authorized' do + let(:auth) { 'gitlab-ci-token:password' } + + before do + env["HTTP_AUTHORIZATION"] = auth + end + + it "responds with status 403" do + expect(lfs_router_public_ci_auth.try_call.first).to eq(401) + end + end end describe 'unsupported' do @@ -490,13 +515,68 @@ describe Gitlab::Lfs::Router, lib: true do env['REQUEST_METHOD'] = 'PUT' end - describe 'to one project' do - describe 'when user has push access to the project' do + shared_examples 'unauthorized' do + context 'and request is sent by gitlab-workhorse to authorize the request' do before do - project.team << [user, :master] + header_for_upload_authorize(router.project) + end + + it 'responds with status 401' do + expect(router.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(router.project) + end + + it 'responds with status 401' do + expect(router.try_call.first).to eq(401) + end + end + + context 'and request is sent with a malformed headers' do + before do + env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" + end + + it 'does not recognize it as a valid lfs command' do + expect(router.try_call).to eq(nil) + end + end + end + + shared_examples 'forbidden' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(router.project) + end + + it 'responds with 403' do + expect(router.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(router.project) + end + + it 'responds with 403' do + expect(router.try_call.first).to eq(403) end + end + end + + describe 'to one project' do + describe 'when user is authenticated' do + describe 'when user has push access to the project' do + before do + project.team << [user, :developer] + end - describe 'when user is authenticated' do context 'and request is sent by gitlab-workhorse to authorize the request' do before do header_for_upload_authorize(project) @@ -524,100 +604,35 @@ describe Gitlab::Lfs::Router, lib: true do end end - describe 'when user is unauthenticated' do - let(:lfs_router_noauth) { new_lfs_router(project, nil) } + describe 'and user does not have push access' do + let(:router) { lfs_router_auth } - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(project) - end - - it 'responds with status 401' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(project) - end - - it 'responds with status 401' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - - context 'and request is sent with a malformed headers' do - before do - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" - env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" - end - - it 'does not recognize it as a valid lfs command' do - expect(lfs_router_noauth.try_call).to eq(nil) - end - end + it_behaves_like 'forbidden' end end - describe 'and user does not have push access' do - describe 'when user is authenticated' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(project) - end - - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(project) - end - - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - end + context 'when CI is authenticated' do + let(:router) { lfs_router_ci_auth } - describe 'when user is unauthenticated' do - let(:lfs_router_noauth) { new_lfs_router(project, nil) } - - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(project) - end + it_behaves_like 'unauthorized' + end - it 'responds with 401' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end + context 'for unauthenticated' do + let(:router) { new_lfs_router(project) } - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(project) - end - - it 'responds with 401' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - end + it_behaves_like 'unauthorized' end end - describe "to a forked project" do + describe 'to a forked project' do let(:forked_project) { fork_project(public_project, user) } - describe 'when user has push access to the project' do - before do - forked_project.team << [user_two, :master] - end + describe 'when user is authenticated' do + describe 'when user has push access to the project' do + before do + forked_project.team << [user_two, :developer] + end - describe 'when user is authenticated' do context 'and request is sent by gitlab-workhorse to authorize the request' do before do header_for_upload_authorize(forked_project) @@ -645,78 +660,28 @@ describe Gitlab::Lfs::Router, lib: true do end end - describe 'when user is unauthenticated' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(forked_project) - end - - it 'responds with status 401' do - expect(lfs_router_forked_noauth.try_call.first).to eq(401) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(forked_project) - end + describe 'and user does not have push access' do + let(:router) { lfs_router_forked_auth } - it 'responds with status 401' do - expect(lfs_router_forked_noauth.try_call.first).to eq(401) - end - end + it_behaves_like 'forbidden' end end - describe 'and user does not have push access' do - describe 'when user is authenticated' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(forked_project) - end + context 'when CI is authenticated' do + let(:router) { lfs_router_forked_ci_auth } - it 'responds with 403' do - expect(lfs_router_forked_auth.try_call.first).to eq(403) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(forked_project) - end - - it 'responds with 403' do - expect(lfs_router_forked_auth.try_call.first).to eq(403) - end - end - end - - describe 'when user is unauthenticated' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(forked_project) - end - - it 'responds with 401' do - expect(lfs_router_forked_noauth.try_call.first).to eq(401) - end - end + it_behaves_like 'unauthorized' + end - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(forked_project) - end + context 'for unauthenticated' do + let(:router) { lfs_router_forked_noauth } - it 'responds with 401' do - expect(lfs_router_forked_noauth.try_call.first).to eq(401) - end - end - end + it_behaves_like 'unauthorized' end describe 'and second project not related to fork or a source project' do let(:second_project) { create(:project) } - let(:lfs_router_second_project) { new_lfs_router(second_project, user) } + let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) } before do public_project.lfs_objects << lfs_object @@ -745,8 +710,8 @@ describe Gitlab::Lfs::Router, lib: true do ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) end - def new_lfs_router(project, user) - Gitlab::Lfs::Router.new(project, user, request) + def new_lfs_router(project, user: nil, ci: false) + Gitlab::Lfs::Router.new(project, user, ci, request) end def header_for_upload_authorize(project) diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb index e520a968999..4d2aa03e722 100644 --- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe Gitlab::Metrics::SidekiqMiddleware do let(:middleware) { described_class.new } + let(:message) { { 'args' => ['test'], 'enqueued_at' => Time.new(2016, 6, 23, 6, 59).to_f } } describe '#call' do it 'tracks the transaction' do @@ -11,9 +12,23 @@ describe Gitlab::Metrics::SidekiqMiddleware do with('TestWorker#perform'). and_call_original + expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).with(:sidekiq_queue_duration, instance_of(Float)) expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish) - middleware.call(worker, 'test', :test) { nil } + middleware.call(worker, message, :test) { nil } + end + + it 'tracks the transaction (for messages without `enqueued_at`)' do + worker = double(:worker, class: double(:class, name: 'TestWorker')) + + expect(Gitlab::Metrics::Transaction).to receive(:new). + with('TestWorker#perform'). + and_call_original + + expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).with(:sidekiq_queue_duration, instance_of(Float)) + expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish) + + middleware.call(worker, {}, :test) { nil } end end end diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index 84c21ceefd9..2753aecc1f4 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -164,7 +164,14 @@ describe Gitlab::Saml::User, lib: true do end context 'and LDAP user has an account already' do - let!(:existing_user) { create(:omniauth_user, email: 'john@mail.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } + before do + create(:omniauth_user, + email: 'john@mail.com', + extern_uid: 'uid=user1,ou=People,dc=example', + provider: 'ldapmain', + username: 'john') + end + it 'adds the omniauth identity to the LDAP account' do saml_user.save @@ -177,6 +184,15 @@ describe Gitlab::Saml::User, lib: true do { provider: 'saml', extern_uid: uid } ]) end + + it 'saves successfully on subsequent tries, when both identities are present' do + saml_user.save + local_saml_user = described_class.new(auth_hash) + local_saml_user.save + + expect(local_saml_user.gl_user).to be_valid + expect(local_saml_user.gl_user).to be_persisted + end end context 'user has SAML user, and wants to add their LDAP identity' do diff --git a/spec/lib/gitlab/gitignore_spec.rb b/spec/lib/gitlab/template/gitignore_spec.rb index 72baa516cc4..bc0ec9325cc 100644 --- a/spec/lib/gitlab/gitignore_spec.rb +++ b/spec/lib/gitlab/template/gitignore_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe Gitlab::Gitignore do - subject { Gitlab::Gitignore } +describe Gitlab::Template::Gitignore do + subject { described_class } describe '.all' do it 'strips the gitignore suffix' do @@ -24,7 +24,7 @@ describe Gitlab::Gitignore do it 'returns the Gitignore object of a valid file' do ruby = subject.find('Ruby') - expect(ruby).to be_a Gitlab::Gitignore + expect(ruby).to be_a Gitlab::Template::Gitignore expect(ruby.name).to eq('Ruby') end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 1e6eb20ab39..ae55a01ebea 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -401,23 +401,56 @@ describe Notify do end describe 'project access requested' do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:project_member) do - project.request_access(user) - project.members.request.find_by(user_id: user.id) + context 'for a project in a user namespace' do + let(:project) { create(:project).tap { |p| p.team << [p.owner, :master, p.owner] } } + let(:user) { create(:user) } + let(:project_member) do + project.request_access(user) + project.members.request.find_by(user_id: user.id) + end + subject { Notify.member_access_requested_email('project', project_member.id) } + + it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' + it_behaves_like "a user cannot unsubscribe through footer link" + + it 'contains all the useful information' do + to_emails = subject.header[:to].addrs + expect(to_emails.size).to eq(1) + expect(to_emails[0].address).to eq(project.members.owners_and_masters.first.user.notification_email) + + is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" + is_expected.to have_body_text /#{project.name_with_namespace}/ + is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/ + is_expected.to have_body_text /#{project_member.human_access}/ + end end - subject { Notify.member_access_requested_email('project', project_member.id) } - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" + context 'for a project in a group' do + let(:group_owner) { create(:user) } + let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } } + let(:project) { create(:project, namespace: group) } + let(:user) { create(:user) } + let(:project_member) do + project.request_access(user) + project.members.request.find_by(user_id: user.id) + end + subject { Notify.member_access_requested_email('project', project_member.id) } - it 'contains all the useful information' do - is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" - is_expected.to have_body_text /#{project.name_with_namespace}/ - is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/ - is_expected.to have_body_text /#{project_member.human_access}/ + it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' + it_behaves_like "a user cannot unsubscribe through footer link" + + it 'contains all the useful information' do + to_emails = subject.header[:to].addrs + expect(to_emails.size).to eq(1) + expect(to_emails[0].address).to eq(group.members.owners_and_masters.first.user.notification_email) + + is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" + is_expected.to have_body_text /#{project.name_with_namespace}/ + is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/ + is_expected.to have_body_text /#{project_member.human_access}/ + end end end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 5d1fa8226e5..8154001cf46 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -2,7 +2,12 @@ require 'spec_helper' describe Ci::Build, models: true do let(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project) } + + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.id) + end + let(:build) { create(:ci_build, pipeline: pipeline) } it { is_expected.to validate_presence_of :ref } @@ -36,32 +41,44 @@ describe Ci::Build, models: true do subject { build.ignored? } context 'if build is not allowed to fail' do - before { build.allow_failure = false } + before do + build.allow_failure = false + end context 'and build.status is success' do - before { build.status = 'success' } + before do + build.status = 'success' + end it { is_expected.to be_falsey } end context 'and build.status is failed' do - before { build.status = 'failed' } + before do + build.status = 'failed' + end it { is_expected.to be_falsey } end end context 'if build is allowed to fail' do - before { build.allow_failure = true } + before do + build.allow_failure = true + end context 'and build.status is success' do - before { build.status = 'success' } + before do + build.status = 'success' + end it { is_expected.to be_falsey } end context 'and build.status is failed' do - before { build.status = 'failed' } + before do + build.status = 'failed' + end it { is_expected.to be_truthy } end @@ -75,7 +92,9 @@ describe Ci::Build, models: true do context 'if build.trace contains text' do let(:text) { 'example output' } - before { build.trace = text } + before do + build.trace = text + end it { is_expected.to include(text) } it { expect(subject.length).to be >= text.length } @@ -188,7 +207,9 @@ describe Ci::Build, models: true do ] end - before { build.update_attributes(stage: 'stage') } + before do + build.update_attributes(stage: 'stage') + end it { is_expected.to eq(predefined_variables + yaml_variables) } @@ -199,7 +220,9 @@ describe Ci::Build, models: true do ] end - before { build.update_attributes(tag: true) } + before do + build.update_attributes(tag: true) + end it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) } end @@ -257,57 +280,6 @@ describe Ci::Build, models: true do end end - describe '#can_be_served?' do - let(:runner) { create(:ci_runner) } - - before { build.project.runners << runner } - - context 'when runner does not have tags' do - it 'can handle builds without tags' do - expect(build.can_be_served?(runner)).to be_truthy - end - - it 'cannot handle build with tags' do - build.tag_list = ['aa'] - expect(build.can_be_served?(runner)).to be_falsey - end - end - - context 'when runner has tags' do - before { runner.tag_list = ['bb', 'cc'] } - - shared_examples 'tagged build picker' do - it 'can handle build with matching tags' do - build.tag_list = ['bb'] - expect(build.can_be_served?(runner)).to be_truthy - end - - it 'cannot handle build without matching tags' do - build.tag_list = ['aa'] - expect(build.can_be_served?(runner)).to be_falsey - end - end - - context 'when runner can pick untagged jobs' do - it 'can handle builds without tags' do - expect(build.can_be_served?(runner)).to be_truthy - end - - it_behaves_like 'tagged build picker' - end - - context 'when runner can not pick untagged jobs' do - before { runner.run_untagged = false } - - it 'can not handle builds without tags' do - expect(build.can_be_served?(runner)).to be_falsey - end - - it_behaves_like 'tagged build picker' - end - end - end - describe '#has_tags?' do context 'when build has tags' do subject { create(:ci_build, tag_list: ['tag']) } @@ -348,7 +320,7 @@ describe Ci::Build, models: true do end it 'that cannot handle build' do - expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false) + expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false) is_expected.to be_falsey end @@ -360,7 +332,9 @@ describe Ci::Build, models: true do %w(pending).each do |state| context "if commit_status.status is #{state}" do - before { build.status = state } + before do + build.status = state + end it { is_expected.to be_truthy } @@ -379,7 +353,9 @@ describe Ci::Build, models: true do %w(success failed canceled running).each do |state| context "if commit_status.status is #{state}" do - before { build.status = state } + before do + build.status = state + end it { is_expected.to be_falsey } end @@ -390,7 +366,10 @@ describe Ci::Build, models: true do subject { build.artifacts? } context 'artifacts archive does not exist' do - before { build.update_attributes(artifacts_file: nil) } + before do + build.update_attributes(artifacts_file: nil) + end + it { is_expected.to be_falsy } end @@ -623,7 +602,9 @@ describe Ci::Build, models: true do let!(:build) { create(:ci_build, :trace, :success, :artifacts) } describe '#erase' do - before { build.erase(erased_by: user) } + before do + build.erase(erased_by: user) + end context 'erased by user' do let!(:user) { create(:user, username: 'eraser') } @@ -660,7 +641,9 @@ describe Ci::Build, models: true do end context 'build has been erased' do - before { build.erase } + before do + build.erase + end it { is_expected.to be true } end @@ -668,7 +651,9 @@ describe Ci::Build, models: true do context 'metadata and build trace are not available' do let!(:build) { create(:ci_build, :success, :artifacts) } - before { build.remove_artifacts_metadata! } + before do + build.remove_artifacts_metadata! + end describe '#erase' do it 'should not raise error' do @@ -678,4 +663,10 @@ describe Ci::Build, models: true do end end end + + describe '#commit' do + it 'returns commit pipeline has been created for' do + expect(build.commit).to eq project.commit + end + end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 5d04d8ffcff..ef65eb99328 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -20,34 +20,36 @@ describe Ci::Runner, models: true do end describe '#display_name' do - it 'should return the description if it has a value' do + it 'returns the description if it has a value' do runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448') expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448' end - it 'should return the token if it does not have a description' do + it 'returns the token if it does not have a description' do runner = FactoryGirl.create(:ci_runner) expect(runner.display_name).to eq runner.description end - it 'should return the token if the description is an empty string' do + it 'returns the token if the description is an empty string' do runner = FactoryGirl.build(:ci_runner, description: '', token: 'token') expect(runner.display_name).to eq runner.token end end - describe :assign_to do + describe '#assign_to' do let!(:project) { FactoryGirl.create :empty_project } let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) } - before { shared_runner.assign_to(project) } + before do + shared_runner.assign_to(project) + end it { expect(shared_runner).to be_specific } it { expect(shared_runner.projects).to eq([project]) } it { expect(shared_runner.only_for?(project)).to be_truthy } end - describe :online do + describe '.online' do subject { Ci::Runner.online } before do @@ -58,60 +60,269 @@ describe Ci::Runner, models: true do it { is_expected.to eq([@runner2])} end - describe :online? do + describe '#online?' do let(:runner) { FactoryGirl.create(:ci_runner, :shared) } subject { runner.online? } context 'never contacted' do - before { runner.contacted_at = nil } + before do + runner.contacted_at = nil + end it { is_expected.to be_falsey } end context 'contacted long time ago time' do - before { runner.contacted_at = 1.year.ago } + before do + runner.contacted_at = 1.year.ago + end it { is_expected.to be_falsey } end context 'contacted 1s ago' do - before { runner.contacted_at = 1.second.ago } + before do + runner.contacted_at = 1.second.ago + end it { is_expected.to be_truthy } end end - describe :status do + describe '#can_pick?' do + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:runner) { create(:ci_runner) } + + before do + build.project.runners << runner + end + + context 'when runner does not have tags' do + it 'can handle builds without tags' do + expect(runner.can_pick?(build)).to be_truthy + end + + it 'cannot handle build with tags' do + build.tag_list = ['aa'] + + expect(runner.can_pick?(build)).to be_falsey + end + end + + context 'when runner has tags' do + before do + runner.tag_list = ['bb', 'cc'] + end + + shared_examples 'tagged build picker' do + it 'can handle build with matching tags' do + build.tag_list = ['bb'] + + expect(runner.can_pick?(build)).to be_truthy + end + + it 'cannot handle build without matching tags' do + build.tag_list = ['aa'] + + expect(runner.can_pick?(build)).to be_falsey + end + end + + context 'when runner can pick untagged jobs' do + it 'can handle builds without tags' do + expect(runner.can_pick?(build)).to be_truthy + end + + it_behaves_like 'tagged build picker' + end + + context 'when runner cannot pick untagged jobs' do + before do + runner.run_untagged = false + end + + it 'cannot handle builds without tags' do + expect(runner.can_pick?(build)).to be_falsey + end + + it_behaves_like 'tagged build picker' + end + end + + context 'when runner is locked' do + before do + runner.locked = true + end + + shared_examples 'locked build picker' do + context 'when runner cannot pick untagged jobs' do + before do + runner.run_untagged = false + end + + it 'cannot handle builds without tags' do + expect(runner.can_pick?(build)).to be_falsey + end + end + + context 'when having runner tags' do + before do + runner.tag_list = ['bb', 'cc'] + end + + it 'cannot handle it for builds without matching tags' do + build.tag_list = ['aa'] + + expect(runner.can_pick?(build)).to be_falsey + end + end + end + + context 'when serving the same project' do + it 'can handle it' do + expect(runner.can_pick?(build)).to be_truthy + end + + it_behaves_like 'locked build picker' + + context 'when having runner tags' do + before do + runner.tag_list = ['bb', 'cc'] + build.tag_list = ['bb'] + end + + it 'can handle it for matching tags' do + expect(runner.can_pick?(build)).to be_truthy + end + end + end + + context 'serving a different project' do + before do + runner.runner_projects.destroy_all + end + + it 'cannot handle it' do + expect(runner.can_pick?(build)).to be_falsey + end + + it_behaves_like 'locked build picker' + + context 'when having runner tags' do + before do + runner.tag_list = ['bb', 'cc'] + build.tag_list = ['bb'] + end + + it 'cannot handle it for matching tags' do + expect(runner.can_pick?(build)).to be_falsey + end + end + end + end + end + + describe '#status' do let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) } subject { runner.status } context 'never connected' do - before { runner.contacted_at = nil } + before do + runner.contacted_at = nil + end it { is_expected.to eq(:not_connected) } end context 'contacted 1s ago' do - before { runner.contacted_at = 1.second.ago } + before do + runner.contacted_at = 1.second.ago + end it { is_expected.to eq(:online) } end context 'contacted long time ago' do - before { runner.contacted_at = 1.year.ago } + before do + runner.contacted_at = 1.year.ago + end it { is_expected.to eq(:offline) } end context 'inactive' do - before { runner.active = false } + before do + runner.active = false + end it { is_expected.to eq(:paused) } end end + describe '.assignable_for' do + let(:runner) { create(:ci_runner) } + let(:project) { create(:project) } + let(:another_project) { create(:project) } + + before do + project.runners << runner + end + + context 'with shared runners' do + before do + runner.update(is_shared: true) + end + + context 'does not give owned runner' do + subject { Ci::Runner.assignable_for(project) } + + it { is_expected.to be_empty } + end + + context 'does not give shared runner' do + subject { Ci::Runner.assignable_for(another_project) } + + it { is_expected.to be_empty } + end + end + + context 'with unlocked runner' do + context 'does not give owned runner' do + subject { Ci::Runner.assignable_for(project) } + + it { is_expected.to be_empty } + end + + context 'does give a specific runner' do + subject { Ci::Runner.assignable_for(another_project) } + + it { is_expected.to contain_exactly(runner) } + end + end + + context 'with locked runner' do + before do + runner.update(locked: true) + end + + context 'does not give owned runner' do + subject { Ci::Runner.assignable_for(project) } + + it { is_expected.to be_empty } + end + + context 'does not give a locked runner' do + subject { Ci::Runner.assignable_for(another_project) } + + it { is_expected.to be_empty } + end + end + end + describe "belongs_to_one_project?" do it "returns false if there are two projects runner assigned to" do runner = FactoryGirl.create(:ci_runner) diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index beca8708c9d..ba02d5fe977 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -207,4 +207,16 @@ eos expect(commit.participants).to include(note1.author, note2.author) end end + + describe '#uri_type' do + it 'returns the URI type at the given path' do + expect(commit.uri_type('files/html')).to be(:tree) + expect(commit.uri_type('files/images/logo-black.png')).to be(:raw) + expect(commit.uri_type('files/js/application.js')).to be(:blob) + end + + it "returns nil if the path doesn't exists" do + expect(commit.uri_type('this/path/doesnt/exist')).to be_nil + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 8fb605fff8a..96397d7c8a9 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,8 +1,13 @@ require 'spec_helper' describe CommitStatus, models: true do - let(:pipeline) { FactoryGirl.create :ci_pipeline } - let(:commit_status) { FactoryGirl.create :commit_status, pipeline: pipeline } + let(:project) { create(:project) } + + let(:pipeline) do + create(:ci_pipeline, project: project, sha: project.commit.id) + end + + let(:commit_status) { create(:commit_status, pipeline: pipeline) } it { is_expected.to belong_to(:pipeline) } it { is_expected.to belong_to(:user) } @@ -13,7 +18,7 @@ describe CommitStatus, models: true do it { is_expected.to delegate_method(:sha).to(:pipeline) } it { is_expected.to delegate_method(:short_sha).to(:pipeline) } - + it { is_expected.to respond_to :success? } it { is_expected.to respond_to :failed? } it { is_expected.to respond_to :running? } @@ -116,7 +121,7 @@ describe CommitStatus, models: true do it { is_expected.to be > 0.0 } end end - + describe :latest do subject { CommitStatus.latest.order(:id) } @@ -198,4 +203,10 @@ describe CommitStatus, models: true do end end end + + describe '#commit' do + it 'returns commit pipeline has been created for' do + expect(commit_status.commit).to eq project.commit + end + end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index efbcbf72f76..89730ab8eb8 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -154,6 +154,20 @@ describe Issue, "Issuable" do expect(issues).to match_array([issue1, issue2, issue, issue3]) end end + + context 'when all of the results are level on the sort key' do + let!(:issues) do + 10.times { create(:issue, project: project) } + end + + it 'has no duplicates across pages' do + sorted_issue_ids = 1.upto(10).map do |i| + project.issues.sort('milestone_due_desc').page(i).per(1).first.id + end + + expect(sorted_issue_ids).to eq(sorted_issue_ids.uniq) + end + end end diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb index 7e4ea0f2d66..a9f4ef9ee5e 100644 --- a/spec/models/concerns/participable_spec.rb +++ b/spec/models/concerns/participable_spec.rb @@ -37,6 +37,16 @@ describe Participable, models: true do expect(participants).to include(user3) end + it 'caches the raw list of participants' do + instance = model.new + user1 = build(:user) + + expect(instance).to receive(:raw_participants).once + + instance.participants(user1) + instance.participants(user1) + end + it 'supports attributes returning another Participable' do other_model = Class.new { include Participable } diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 26fbedbef2f..49cf3d8633a 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -26,7 +26,7 @@ describe Key, models: true do end end - context "validation of uniqueness" do + context "validation of uniqueness (based on fingerprint uniqueness)" do let(:user) { create(:user) } it "accepts the key once" do diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 3ed3202ac6c..e9134a3d283 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -134,18 +134,6 @@ describe Member, models: true do it { is_expected.to respond_to(:user_email) } end - describe 'Callbacks' do - describe 'after_destroy :post_decline_request, if: :request?' do - let(:member) { create(:project_member, requested_at: Time.now.utc) } - - it 'calls #post_decline_request' do - expect(member).to receive(:post_decline_request) - - member.destroy - end - end - end - describe ".add_user" do let!(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index eeb74a462ac..18439cac2a4 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -61,16 +61,6 @@ describe GroupMember, models: true do end end - describe '#post_decline_request' do - it 'calls NotificationService.decline_group_access_request' do - member = create(:group_member, user: build_stubbed(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:decline_group_access_request) - - member.__send__(:post_decline_request) - end - end - describe '#real_source_type' do subject { create(:group_member).real_source_type } diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 1e466f9c620..bbf65edb27c 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -152,15 +152,5 @@ describe ProjectMember, models: true do member.__send__(:after_accept_request) end end - - describe '#post_decline_request' do - it 'calls NotificationService.decline_project_access_request' do - member = create(:project_member, user: build_stubbed(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:decline_project_access_request) - - member.__send__(:post_decline_request) - end - end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 53c8408633c..d305cd9ff1e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -63,6 +63,27 @@ describe Project, models: true do expect(project2).not_to be_valid expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/) end + + describe 'wiki path conflict' do + context "when the new path has been used by the wiki of other Project" do + it 'should have an error on the name attribute' do + new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki") + + expect(new_project).not_to be_valid + expect(new_project.errors[:name].first).to eq('has already been taken') + end + end + + context "when the new wiki path has been used by the path of other Project" do + it 'should have an error on the name attribute' do + project_with_wiki_suffix = create(:project, path: 'foo.wiki') + new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo') + + expect(new_project).not_to be_valid + expect(new_project.errors[:name].first).to eq('has already been taken') + end + end + end end describe 'default_scope' do diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 789816bf2c7..0621c6a06ce 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -72,7 +72,7 @@ describe Snippet, models: true do end end - describe '#search_code' do + describe '.search_code' do let(:snippet) { create(:snippet, content: 'class Foo; end') } it 'returns snippets with matching content' do @@ -88,6 +88,46 @@ describe Snippet, models: true do end end + describe '.accessible_to' do + let(:author) { create(:author) } + let(:project) { create(:empty_project) } + + let!(:public_snippet) { create(:snippet, :public) } + let!(:internal_snippet) { create(:snippet, :internal) } + let!(:private_snippet) { create(:snippet, :private, author: author) } + + let!(:project_public_snippet) { create(:snippet, :public, project: project) } + let!(:project_internal_snippet) { create(:snippet, :internal, project: project) } + let!(:project_private_snippet) { create(:snippet, :private, project: project) } + + it 'returns only public snippets when user is blank' do + expect(described_class.accessible_to(nil)).to match_array [public_snippet, project_public_snippet] + end + + it 'returns only public, and internal snippets for regular users' do + user = create(:user) + + expect(described_class.accessible_to(user)).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet] + end + + it 'returns public, internal snippets and project private snippets for project members' do + member = create(:user) + project.team << [member, :developer] + + expect(described_class.accessible_to(member)).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] + end + + it 'returns private snippets where the user is the author' do + expect(described_class.accessible_to(author)).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet] + end + + it 'returns all snippets when for admins' do + admin = create(:admin) + + expect(described_class.accessible_to(admin)).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] + end + end + describe '#participants' do let(:project) { create(:project, :public) } let(:snippet) { create(:snippet, content: 'foo', project: project) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 73bee535fe3..328254ed56b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -31,6 +31,26 @@ describe User, models: true do it { is_expected.to have_many(:spam_logs).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) } it { is_expected.to have_many(:award_emoji).dependent(:destroy) } + + describe '#group_members' do + it 'does not include group memberships for which user is a requester' do + user = create(:user) + group = create(:group, :public) + group.request_access(user) + + expect(user.group_members).to be_empty + end + end + + describe '#project_members' do + it 'does not include project memberships for which user is a requester' do + user = create(:user) + project = create(:project, :public) + project.request_access(user) + + expect(user.project_members).to be_empty + end + end end describe 'validations' do diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 2e65e7f1920..ed78d582bd0 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -17,7 +17,7 @@ describe API::API, api: true do it "returns an array of award_emoji" do get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(award_emoji.name) end @@ -25,7 +25,7 @@ describe API::API, api: true do it "should return a 404 error when issue id not found" do get api("/projects/#{project.id}/issues/12345/award_emoji", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -33,7 +33,7 @@ describe API::API, api: true do it "returns an array of award_emoji" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(downvote.name) end @@ -45,7 +45,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -56,7 +56,7 @@ describe API::API, api: true do it 'returns an array of award emoji' do get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(rocket.name) end @@ -68,7 +68,7 @@ describe API::API, api: true do it "returns the award emoji" do get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(award_emoji.name) expect(json_response['awardable_id']).to eq(issue.id) expect(json_response['awardable_type']).to eq("Issue") @@ -77,7 +77,7 @@ describe API::API, api: true do it "returns a 404 error if the award is not found" do get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -85,7 +85,7 @@ describe API::API, api: true do it 'returns the award emoji' do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(downvote.name) expect(json_response['awardable_id']).to eq(merge_request.id) expect(json_response['awardable_type']).to eq("MergeRequest") @@ -98,7 +98,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -109,7 +109,7 @@ describe API::API, api: true do it 'returns an award emoji' do get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).not_to be_an Array expect(json_response['name']).to eq(rocket.name) end @@ -120,7 +120,7 @@ describe API::API, api: true do it "creates a new award emoji" do post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'blowfish' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('blowfish') expect(json_response['user']['username']).to eq(user.username) end @@ -128,13 +128,13 @@ describe API::API, api: true do it "should return a 400 bad request error if the name is not given" do post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 401 unauthorized error if the user is not authenticated" do post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji"), name: 'thumbsup' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -145,7 +145,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' end.to change { note.award_emoji.count }.from(0).to(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['user']['username']).to eq(user.username) end end @@ -157,13 +157,13 @@ describe API::API, api: true do delete api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) end.to change { issue.award_emoji.count }.from(1).to(0) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'returns a 404 error when the award emoji can not be found' do delete api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -173,13 +173,13 @@ describe API::API, api: true do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) end.to change { merge_request.award_emoji.count }.from(1).to(0) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -192,7 +192,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) end.to change { note.award_emoji.count }.from(1).to(0) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 55582aa53d2..b11ca26ee68 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -17,7 +17,7 @@ describe API::API, api: true do project.repository.expire_cache get api("/projects/#{project.id}/repository/branches", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array branch_names = json_response.map { |x| x['name'] } expect(branch_names).to match_array(project.repository.branch_names) @@ -27,7 +27,7 @@ describe API::API, api: true do describe "GET /projects/:id/repository/branches/:branch" do it "should return the branch information for a single branch" do get api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) @@ -36,19 +36,19 @@ describe API::API, api: true do it "should return a 403 error if guest" do get api("/projects/#{project.id}/repository/branches", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return a 404 error if branch is not available" do get api("/projects/#{project.id}/repository/branches/unknown", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe "PUT /projects/:id/repository/branches/:branch/protect" do it "should protect a single branch" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) @@ -57,25 +57,25 @@ describe API::API, api: true do it "should return a 404 error if branch not found" do put api("/projects/#{project.id}/repository/branches/unknown/protect", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return a 403 error if guest" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return success when protect branch again" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end describe "PUT /projects/:id/repository/branches/:branch/unprotect" do it "should unprotect a single branch" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) @@ -84,13 +84,13 @@ describe API::API, api: true do it "should return success when unprotect branch" do put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return success when unprotect branch again" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -100,7 +100,7 @@ describe API::API, api: true do branch_name: 'feature1', ref: branch_sha - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('feature1') expect(json_response['commit']['id']).to eq(branch_sha) @@ -110,14 +110,14 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/branches", user2), branch_name: branch_name, ref: branch_sha - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should return 400 if branch name is invalid' do post api("/projects/#{project.id}/repository/branches", user), branch_name: 'new design', ref: branch_sha - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Branch name is invalid') end @@ -125,12 +125,12 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/branches", user), branch_name: 'new_design1', ref: branch_sha - expect(response.status).to eq(201) + expect(response).to have_http_status(201) post api("/projects/#{project.id}/repository/branches", user), branch_name: 'new_design1', ref: branch_sha - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Branch already exists') end @@ -138,7 +138,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/branches", user), branch_name: 'new_design3', ref: 'foo' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Invalid reference name') end end @@ -150,25 +150,25 @@ describe API::API, api: true do it "should remove branch" do delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['branch_name']).to eq(branch_name) end it 'should return 404 if branch not exists' do delete api("/projects/#{project.id}/repository/branches/foobar", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should remove protected branch" do project.protected_branches.create(name: branch_name) delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('Protected branch cant be removed') end it "should not remove HEAD branch" do delete api("/projects/#{project.id}/repository/branches/master", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('Cannot remove HEAD branch') end end diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index ac85f340922..2ab9d640269 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -9,8 +9,8 @@ describe API::API, api: true do let!(:project) { create(:project, creator_id: user.id) } let!(:developer) { create(:project_member, :developer, user: user, project: project) } let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) } - let(:pipeline) { create(:ci_pipeline, project: project)} - let(:build) { create(:ci_build, pipeline: pipeline) } + let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id) } + let!(:build) { create(:ci_build, pipeline: pipeline) } describe 'GET /projects/:id/builds ' do let(:query) { '' } @@ -19,15 +19,20 @@ describe API::API, api: true do context 'authorized user' do it 'should return project builds' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array end + it 'returns correct values' do + expect(json_response).not_to be_empty + expect(json_response.first['commit']['id']).to eq project.commit.id + end + context 'filter project with one scope element' do let(:query) { 'scope=pending' } it do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array end end @@ -36,7 +41,7 @@ describe API::API, api: true do let(:query) { 'scope[0]=pending&scope[1]=running' } it do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array end end @@ -44,7 +49,7 @@ describe API::API, api: true do context 'respond 400 when scope contains invalid state' do let(:query) { 'scope[0]=pending&scope[1]=unknown_status' } - it { expect(response.status).to eq(400) } + it { expect(response).to have_http_status(400) } end end @@ -52,7 +57,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not return project builds' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -65,7 +70,7 @@ describe API::API, api: true do context 'authorized user' do it 'should return project builds for specific commit' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array end end @@ -74,7 +79,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not return project builds' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -84,7 +89,7 @@ describe API::API, api: true do context 'authorized user' do it 'should return specific build data' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq('test') end end @@ -93,7 +98,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not return specific build data' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -111,7 +116,7 @@ describe API::API, api: true do end it 'should return specific build artifacts' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.headers).to include(download_headers) end end @@ -120,24 +125,24 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not return specific build artifacts' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end it 'should not return build artifacts if not uploaded' do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe 'GET /projects/:id/builds/:build_id/trace' do let(:build) { create(:ci_build, :trace, pipeline: pipeline) } - + before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) } context 'authorized user' do it 'should return specific build trace' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq(build.trace) end end @@ -146,7 +151,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not return specific build trace' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -157,7 +162,7 @@ describe API::API, api: true do context 'authorized user' do context 'user with :update_build persmission' do it 'should cancel running or pending build' do - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(project.builds.first.status).to eq('canceled') end end @@ -166,7 +171,7 @@ describe API::API, api: true do let(:api_user) { user2 } it 'should not cancel build' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -175,7 +180,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not cancel build' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -188,7 +193,7 @@ describe API::API, api: true do context 'authorized user' do context 'user with :update_build permission' do it 'should retry non-running build' do - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(project.builds.first.status).to eq('canceled') expect(json_response['status']).to eq('pending') end @@ -198,7 +203,7 @@ describe API::API, api: true do let(:api_user) { user2 } it 'should not retry build' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -207,7 +212,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not retry build' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 298cdbad329..6668f3543f6 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -41,7 +41,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, reporter) } it 'returns latest commit statuses' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id) @@ -54,7 +54,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, reporter), all: 1 } it 'returns all commit statuses' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status1.id, status2.id, @@ -67,7 +67,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, reporter), ref: 'develop' } it 'returns latest commit statuses for specific ref' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status3.id, status5.id) @@ -78,7 +78,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, reporter), name: 'coverage' } it 'return latest commit statuses for specific name' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status4.id, status5.id) @@ -101,7 +101,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, guest) } it "should not return project commits" do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -109,7 +109,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url) } it "should not return project commits" do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -122,7 +122,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, developer), state: 'success' } it 'creates commit status' do - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('default') @@ -141,7 +141,7 @@ describe API::CommitStatuses, api: true do end it 'creates commit status' do - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('coverage') @@ -155,7 +155,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, developer), state: 'invalid' } it 'does not create commit status' do - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -163,7 +163,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, developer) } it 'does not create commit status' do - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -172,7 +172,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, developer), state: 'running' } it 'returns not found error' do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -181,7 +181,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, reporter) } it 'should not create commit status' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -189,7 +189,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, guest) } it 'should not create commit status' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -197,7 +197,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url) } it 'should not create commit status' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 6fc38f537d3..5219c808791 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -19,7 +19,7 @@ describe API::API, api: true do it "should return project commits" do get api("/projects/#{project.id}/repository/commits", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['id']).to eq(project.repository.commit.id) @@ -29,7 +29,7 @@ describe API::API, api: true do context "unauthorized user" do it "should not return project commits" do get api("/projects/#{project.id}/repository/commits") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -63,7 +63,7 @@ describe API::API, api: true 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(response).to have_http_status(400) expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format" end end @@ -73,26 +73,26 @@ describe API::API, api: true do context "authorized user" do it "should return a commit by sha" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(project.repository.commit.id) expect(json_response['title']).to eq(project.repository.commit.title) end it "should return a 404 error if not found" do get api("/projects/#{project.id}/repository/commits/invalid_sha", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return nil for commit without CI" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['status']).to be_nil end it "should return status for CI" do pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master') get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['status']).to eq(pipeline.status) end end @@ -100,7 +100,7 @@ describe API::API, api: true do context "unauthorized user" do it "should not return the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -111,7 +111,7 @@ describe API::API, api: true do it "should return the diff of the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to be >= 1 @@ -120,14 +120,14 @@ describe API::API, api: true do it "should return a 404 error if invalid commit" do get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context "unauthorized user" do it "should not return the diff of the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -136,7 +136,7 @@ describe API::API, api: true do context 'authorized user' do it 'should return merge_request comments' do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['note']).to eq('a comment on a commit') @@ -145,14 +145,14 @@ describe API::API, api: true do it 'should return a 404 error if merge_request_id not found' do get api("/projects/#{project.id}/repository/commits/1234ab/comments", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context 'unauthorized user' do it 'should not return the diff of the selected commit' do get api("/projects/#{project.id}/repository/commits/1234ab/comments") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -161,7 +161,7 @@ describe API::API, api: true do context 'authorized user' do it 'should return comment' do post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['path']).to be_nil expect(json_response['line']).to be_nil @@ -170,7 +170,7 @@ describe API::API, api: true do it 'should return the inline comment' do post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['path']).to eq(project.repository.commit.diffs.first.new_path) expect(json_response['line']).to eq(7) @@ -179,19 +179,19 @@ describe API::API, api: true do it 'should return 400 if note is missing' do post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 404 if note is attached to non existent commit' do post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context 'unauthorized user' do it 'should not return the diff of the selected commit' do post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 0afc3e79339..881b818b5e9 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -11,21 +11,21 @@ describe API::API, api: true do describe "when unauthenticated" do it "returns authentication success" do get api("/user"), access_token: token.token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end describe "when token invalid" do it "returns authentication error" do get api("/user"), access_token: "123a" - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end describe "authorization by private token" do it "returns authentication success" do get api("/user", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 8efa09f75fd..2e5448143d5 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -16,7 +16,7 @@ describe API::API, api: true do } get api("/projects/#{project.id}/repository/files", user), params - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['file_path']).to eq(file_path) expect(json_response['file_name']).to eq('popen.rb') expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') @@ -25,7 +25,7 @@ describe API::API, api: true do it "should return a 400 bad request if no params given" do get api("/projects/#{project.id}/repository/files", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 404 if such file does not exist" do @@ -35,7 +35,7 @@ describe API::API, api: true do } get api("/projects/#{project.id}/repository/files", user), params - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -51,13 +51,13 @@ describe API::API, api: true do it "should create a new file in project repo" do post api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['file_path']).to eq('newfile.rb') end it "should return a 400 bad request if no params given" do post api("/projects/#{project.id}/repository/files", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 400 if editor fails to create file" do @@ -65,7 +65,7 @@ describe API::API, api: true do and_return(false) post api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -81,13 +81,13 @@ describe API::API, api: true do it "should update existing file in project repo" do put api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['file_path']).to eq(file_path) end it "should return a 400 bad request if no params given" do put api("/projects/#{project.id}/repository/files", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -102,20 +102,20 @@ describe API::API, api: true do it "should delete existing file in project repo" do delete api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['file_path']).to eq(file_path) end it "should return a 400 bad request if no params given" do delete api("/projects/#{project.id}/repository/files", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 400 if fails to create file" do allow_any_instance_of(Repository).to receive(:remove_file).and_return(false) delete api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -143,7 +143,7 @@ describe API::API, api: true do it "remains unchanged" do get api("/projects/#{project.id}/repository/files", user), get_params - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['file_path']).to eq(file_path) expect(json_response['file_name']).to eq(file_path) expect(json_response['content']).to eq(put_params[:content]) diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index fa94e03ec32..a9f5aa924b7 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -22,7 +22,7 @@ describe API::API, api: true do context 'when authenticated' do it 'should fork if user has sufficient access to project' do post api("/projects/fork/#{project.id}", user2) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq(project.name) expect(json_response['path']).to eq(project.path) expect(json_response['owner']['id']).to eq(user2.id) @@ -32,7 +32,7 @@ describe API::API, api: true do it 'should fork if user is admin' do post api("/projects/fork/#{project.id}", admin) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq(project.name) expect(json_response['path']).to eq(project.path) expect(json_response['owner']['id']).to eq(admin.id) @@ -42,20 +42,20 @@ describe API::API, api: true do it 'should fail on missing project access for the project to fork' do post api("/projects/fork/#{project.id}", user3) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'should fail if forked project exists in the user namespace' do post api("/projects/fork/#{project.id}", user) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']['name']).to eq(['has already been taken']) expect(json_response['message']['path']).to eq(['has already been taken']) end it 'should fail if project to fork from does not exist' do post api('/projects/fork/424242', user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end end @@ -63,7 +63,7 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do post api("/projects/fork/#{project.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['message']).to eq('401 Unauthorized') end end diff --git a/spec/requests/api/gitignores_spec.rb b/spec/requests/api/gitignores_spec.rb deleted file mode 100644 index aab2d8c81b9..00000000000 --- a/spec/requests/api/gitignores_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'spec_helper' - -describe API::Gitignores, api: true do - include ApiHelpers - - describe 'Entity Gitignore' do - before { get api('/gitignores/Ruby') } - - it { expect(json_response['name']).to eq('Ruby') } - it { expect(json_response['content']).to include('*.gem') } - end - - describe 'Entity GitignoresList' do - before { get api('/gitignores') } - - it { expect(json_response.first['name']).not_to be_nil } - it { expect(json_response.first['content']).to be_nil } - end - - describe 'GET /gitignores' do - it 'returns a list of available license templates' do - get api('/gitignores') - - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.size).to be > 15 - end - end -end diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb index 02553d0f8e2..52f9e7d4681 100644 --- a/spec/requests/api/group_members_spec.rb +++ b/spec/requests/api/group_members_spec.rb @@ -31,7 +31,7 @@ describe API::API, api: true do it "each user: should return an array of members groups of group3" do [owner, master, developer, reporter, guest].each do |user| get api("/groups/#{group_with_members.id}/members", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(5) expect(json_response.find { |e| e['id'] == owner.id }['access_level']).to eq(GroupMember::OWNER) @@ -45,7 +45,7 @@ describe API::API, api: true do it 'users not part of the group should get access error' do get api("/groups/#{group_with_members.id}/members", stranger) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -54,7 +54,7 @@ describe API::API, api: true do context "when not a member of the group" do it "should not add guest as member of group_no_members when adding being done by person outside the group" do post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -66,7 +66,7 @@ describe API::API, api: true do post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER end.to change { group_no_members.members.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq(new_user.name) expect(json_response['access_level']).to eq(GroupMember::MASTER) end @@ -78,27 +78,27 @@ describe API::API, api: true do post api("/groups/#{group_with_members.id}/members", guest), user_id: new_user.id, access_level: GroupMember::MASTER end.not_to change { group_with_members.members.count } - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return error if member already exists" do post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER - expect(response.status).to eq(409) + expect(response).to have_http_status(409) end it "should return a 400 error when user id is not given" do post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 400 error when access level is not given" do post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error when access level is not known" do post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end end @@ -110,7 +110,7 @@ describe API::API, api: true do api("/groups/#{group_no_members.id}/members/#{developer.id}", owner), access_level: GroupMember::MASTER ) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -122,7 +122,7 @@ describe API::API, api: true do access_level: GroupMember::MASTER ) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) get api("/groups/#{group_with_members.id}/members", owner) json_reporter = json_response.find do |e| @@ -139,7 +139,7 @@ describe API::API, api: true do access_level: GroupMember::MASTER ) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) get api("/groups/#{group_with_members.id}/members", owner) json_developer = json_response.find do |e| @@ -153,7 +153,7 @@ describe API::API, api: true do put( api("/groups/#{group_with_members.id}/members/#{master.id}", owner) ) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return a 422 error when access level is not known' do @@ -161,7 +161,7 @@ describe API::API, api: true do api("/groups/#{group_with_members.id}/members/#{master.id}", owner), access_level: 1234 ) - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end end @@ -172,7 +172,7 @@ describe API::API, api: true do random_user = create(:user) delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -182,17 +182,17 @@ describe API::API, api: true do delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) end.to change { group_with_members.members.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return a 404 error when user id is not known" do delete api("/groups/#{group_with_members.id}/members/1328", owner) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should not allow guest to modify group members" do delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 7ecefce80d6..04141a45031 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -23,14 +23,14 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/groups") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when authenticated as user" do it "normal user: should return an array of groups of user1" do get api("/groups", user1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(group1.name) @@ -40,7 +40,7 @@ describe API::API, api: true do context "when authenticated as admin" do it "admin: should return an array of all groups" do get api("/groups", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -51,51 +51,51 @@ describe API::API, api: true do context "when authenticated as user" do it "should return one of user1's groups" do get api("/groups/#{group1.id}", user1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) json_response['name'] == group1.name end it "should not return a non existing group" do get api("/groups/1328", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should not return a group not attached to user1" do get api("/groups/#{group2.id}", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context "when authenticated as admin" do it "should return any existing group" do get api("/groups/#{group2.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(group2.name) end it "should not return a non existing group" do get api("/groups/1328", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context 'when using group path in URL' do it 'should return any existing group' do get api("/groups/#{group1.path}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(group1.name) end it 'should not return a non existing group' do get api('/groups/unknown', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not return a group not attached to user1' do get api("/groups/#{group2.path}", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -107,14 +107,14 @@ describe API::API, api: true do it 'updates the group' do put api("/groups/#{group1.id}", user1), name: new_group_name - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(new_group_name) end it 'returns 404 for a non existing group' do put api('/groups/1328', user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -122,7 +122,7 @@ describe API::API, api: true do it 'updates the group' do put api("/groups/#{group1.id}", admin), name: new_group_name - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(new_group_name) end end @@ -131,7 +131,7 @@ describe API::API, api: true do it 'does not updates the group' do put api("/groups/#{group1.id}", user2), name: new_group_name - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -139,7 +139,7 @@ describe API::API, api: true do it 'returns 404 when trying to update the group' do put api("/groups/#{group2.id}", user1), name: new_group_name - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -149,7 +149,7 @@ describe API::API, api: true do it "should return the group's projects" do get api("/groups/#{group1.id}/projects", user1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) @@ -157,13 +157,13 @@ describe API::API, api: true do it "should not return a non existing group" do get api("/groups/1328/projects", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should not return a group not attached to user1" do get api("/groups/#{group2.id}/projects", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should only return projects to which user has access" do @@ -171,7 +171,7 @@ describe API::API, api: true do get api("/groups/#{group1.id}/projects", user3) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project3.name) end @@ -180,14 +180,14 @@ describe API::API, api: true do context "when authenticated as admin" do it "should return any existing group" do get api("/groups/#{group2.id}/projects", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project2.name) end it "should not return a non existing group" do get api("/groups/1328/projects", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -195,20 +195,20 @@ describe API::API, api: true do it 'should return any existing group' do get api("/groups/#{group1.path}/projects", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) end it 'should not return a non existing group' do get api('/groups/unknown/projects', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not return a group not attached to user1' do get api("/groups/#{group2.path}/projects", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -217,30 +217,30 @@ describe API::API, api: true do context "when authenticated as user without group permissions" do it "should not create group" do post api("/groups", user1), attributes_for(:group) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context "when authenticated as user with group permissions" do it "should create group" do post api("/groups", user3), attributes_for(:group) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it "should not create group, duplicate" do post api("/groups", user3), { name: 'Duplicate Test', path: group2.path } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(response.message).to eq("Bad Request") end it "should return 400 bad request error if name not given" do post api("/groups", user3), { path: group2.path } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 bad request error if path not given" do post api("/groups", user3), { name: 'test' } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end end @@ -249,37 +249,37 @@ describe API::API, api: true do context "when authenticated as user" do it "should remove group" do delete api("/groups/#{group1.id}", user1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should not remove a group if not an owner" do user4 = create(:user) group1.add_master(user4) delete api("/groups/#{group1.id}", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should not remove a non existing group" do delete api("/groups/1328", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should not remove a group not attached to user1" do delete api("/groups/#{group2.id}", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context "when authenticated as admin" do it "should remove any existing group" do delete api("/groups/#{group2.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should not remove a non existing group" do delete api("/groups/1328", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -295,14 +295,14 @@ describe API::API, api: true do context "when authenticated as user" do it "should not transfer project to group" do post api("/groups/#{group1.id}/projects/#{project.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context "when authenticated as admin" do it "should transfer project to group" do post api("/groups/#{group1.id}/projects/#{project.id}", admin) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 22802dd0e05..437c89c3577 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -11,7 +11,7 @@ describe API::API, api: true do it do get api("/internal/check"), secret_token: secret_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['api_version']).to eq(API::API.version) end end @@ -23,7 +23,7 @@ describe API::API, api: true do it do get api("/internal/broadcast_message"), secret_token: secret_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["message"]).to eq(broadcast_message.message) end end @@ -32,7 +32,7 @@ describe API::API, api: true do it do get api("/internal/broadcast_message"), secret_token: secret_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_empty end end @@ -42,7 +42,7 @@ describe API::API, api: true do it do get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(user.name) end @@ -61,7 +61,7 @@ describe API::API, api: true do push(key, project_wiki) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -70,7 +70,7 @@ describe API::API, api: true do it do pull(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -79,7 +79,7 @@ describe API::API, api: true do it do push(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -94,7 +94,7 @@ describe API::API, api: true do it do pull(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -103,7 +103,7 @@ describe API::API, api: true do it do push(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -120,7 +120,7 @@ describe API::API, api: true do it do pull(key, personal_project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -129,7 +129,7 @@ describe API::API, api: true do it do push(key, personal_project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -147,7 +147,7 @@ describe API::API, api: true do it do pull(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -156,7 +156,7 @@ describe API::API, api: true do it do push(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -173,7 +173,7 @@ describe API::API, api: true do it do archive(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -182,7 +182,7 @@ describe API::API, api: true do it do archive(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -192,7 +192,7 @@ describe API::API, api: true do it do pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists')) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -201,7 +201,7 @@ describe API::API, api: true do it do pull(OpenStruct.new(id: 0), project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 59e557c5b2a..2cf130df328 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -51,14 +51,14 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/issues") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when authenticated" do it "should return an array of issues" do get api("/issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(issue.title) end @@ -72,7 +72,7 @@ describe API::API, api: true do it 'should return an array of closed issues' do get api('/issues?state=closed', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) @@ -80,7 +80,7 @@ describe API::API, api: true do it 'should return an array of opened issues' do get api('/issues?state=opened', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(issue.id) @@ -88,7 +88,7 @@ describe API::API, api: true do it 'should return an array of all issues' do get api('/issues?state=all', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['id']).to eq(issue.id) @@ -97,7 +97,7 @@ describe API::API, api: true do it 'should return an array of labeled issues' do get api("/issues?labels=#{label.title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -105,7 +105,7 @@ describe API::API, api: true do it 'should return an array of labeled issues when at least one label matches' do get api("/issues?labels=#{label.title},foo,bar", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -113,14 +113,14 @@ describe API::API, api: true do it 'should return an empty array if no issue matches labels' do get api('/issues?labels=foo,bar', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end it 'should return an array of labeled issues matching given state' do get api("/issues?labels=#{label.title}&state=opened", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -129,20 +129,162 @@ describe API::API, api: true do it 'should return an empty array if no issue matches labels and state filters' do get api("/issues?labels=#{label.title}&state=closed", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end end end + describe "GET /groups/:id/issues" do + let!(:group) { create(:group) } + let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) } + let!(:group_closed_issue) do + create :closed_issue, + author: user, + assignee: user, + project: group_project, + state: :closed, + milestone: group_milestone + end + let!(:group_confidential_issue) do + create :issue, + :confidential, + project: group_project, + author: author, + assignee: assignee + end + let!(:group_issue) do + create :issue, + author: user, + assignee: user, + project: group_project, + milestone: group_milestone + end + let!(:group_label) do + create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project) + end + let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) } + let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) } + let!(:group_empty_milestone) do + create(:milestone, title: '4.0.0', project: group_project) + end + let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) } + + before do + group_project.team << [user, :reporter] + end + let(:base_url) { "/groups/#{group.id}/issues" } + + it 'returns group issues without confidential issues for non project members' do + get api(base_url, non_member) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['title']).to eq(group_issue.title) + end + + it 'returns group confidential issues for author' do + get api(base_url, author) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + end + + it 'returns group confidential issues for assignee' do + get api(base_url, assignee) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + end + + it 'returns group issues with confidential issues for project members' do + get api(base_url, user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + end + + it 'returns group confidential issues for admin' do + get api(base_url, admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + end + + it 'returns an array of labeled group issues' do + get api("#{base_url}?labels=#{group_label.title}", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([group_label.title]) + end + + it 'returns an array of labeled group issues where all labels match' do + get api("#{base_url}?labels=#{group_label.title},foo,bar", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'returns an empty array if no group issue matches labels' do + get api("#{base_url}?labels=foo,bar", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'returns an empty array if no issue matches milestone' do + get api("#{base_url}?milestone=#{group_empty_milestone.title}", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'returns an empty array if milestone does not exist' do + get api("#{base_url}?milestone=foo", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'returns an array of issues in given milestone' do + get api("#{base_url}?milestone=#{group_milestone.title}", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(group_issue.id) + end + + it 'returns an array of issues matching state in milestone' do + get api("#{base_url}?milestone=#{group_milestone.title}"\ + '&state=closed', user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(group_closed_issue.id) + end + end + describe "GET /projects/:id/issues" do let(:base_url) { "/projects/#{project.id}" } let(:title) { milestone.title } it 'should return project issues without confidential issues for non project members' do get api("#{base_url}/issues", non_member) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['title']).to eq(issue.title) @@ -150,7 +292,7 @@ describe API::API, api: true do it 'should return project issues without confidential issues for project members with guest role' do get api("#{base_url}/issues", guest) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['title']).to eq(issue.title) @@ -158,7 +300,7 @@ describe API::API, api: true do it 'should return project confidential issues for author' do get api("#{base_url}/issues", author) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -166,7 +308,7 @@ describe API::API, api: true do it 'should return project confidential issues for assignee' do get api("#{base_url}/issues", assignee) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -174,7 +316,7 @@ describe API::API, api: true do it 'should return project issues with confidential issues for project members' do get api("#{base_url}/issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -182,7 +324,7 @@ describe API::API, api: true do it 'should return project confidential issues for admin' do get api("#{base_url}/issues", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -190,7 +332,7 @@ describe API::API, api: true do it 'should return an array of labeled project issues' do get api("#{base_url}/issues?labels=#{label.title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -198,7 +340,7 @@ describe API::API, api: true do it 'should return an array of labeled project issues when at least one label matches' do get api("#{base_url}/issues?labels=#{label.title},foo,bar", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -206,28 +348,28 @@ describe API::API, api: true do it 'should return an empty array if no project issue matches labels' do get api("#{base_url}/issues?labels=foo,bar", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end it 'should return an empty array if no issue matches milestone' do get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end it 'should return an empty array if milestone does not exist' do get api("#{base_url}/issues?milestone=foo", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end it 'should return an array of issues in given milestone' do get api("#{base_url}/issues?milestone=#{title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['id']).to eq(issue.id) @@ -237,7 +379,7 @@ describe API::API, api: true do it 'should return an array of issues matching state in milestone' do get api("#{base_url}/issues?milestone=#{milestone.title}"\ '&state=closed', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) @@ -248,7 +390,7 @@ describe API::API, api: true do it 'exposes known attributes' do get api("/projects/#{project.id}/issues/#{issue.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(issue.id) expect(json_response['iid']).to eq(issue.iid) expect(json_response['project_id']).to eq(issue.project.id) @@ -266,7 +408,7 @@ describe API::API, api: true do it "should return a project issue by id" do get api("/projects/#{project.id}/issues/#{issue.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(issue.title) expect(json_response['iid']).to eq(issue.iid) end @@ -281,44 +423,44 @@ describe API::API, api: true do it "should return 404 if issue id not found" do get api("/projects/#{project.id}/issues/54321", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context 'confidential issues' do it "should return 404 for non project members" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 404 for project members with guest role" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return confidential issue for project members" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end it "should return confidential issue for author" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end it "should return confidential issue for assignee" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end it "should return confidential issue for admin" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -329,7 +471,7 @@ describe API::API, api: true do it "should create a new project issue" do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['description']).to be_nil expect(json_response['labels']).to eq(['label', 'label2']) @@ -337,21 +479,21 @@ describe API::API, api: true do it "should return a 400 bad request if title not given" do post api("/projects/#{project.id}/issues", user), labels: 'label, label2' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 on invalid label names' do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, ?' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) end it 'should return 400 if title is too long' do post api("/projects/#{project.id}/issues", user), title: 'g' * 256 - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' ]) @@ -363,7 +505,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2', created_at: creation_time - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) end end @@ -387,7 +529,7 @@ describe API::API, api: true do it "should not create a new project issue" do expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) spam_logs = SpamLog.all @@ -404,7 +546,7 @@ describe API::API, api: true do it "should update a project issue" do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -412,14 +554,14 @@ describe API::API, api: true do it "should return 404 error if issue id not found" do put api("/projects/#{project.id}/issues/44444", user), title: 'updated title' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should return 400 on invalid label names' do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title', labels: 'label, ?' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) end @@ -427,33 +569,33 @@ describe API::API, api: true do it "should return 403 for non project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member), title: 'updated title' - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return 403 for project members with guest role" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest), title: 'updated title' - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should update a confidential issue for project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end it "should update a confidential issue for author" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end it "should update a confidential issue for admin" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end end @@ -466,21 +608,21 @@ describe API::API, api: true do it 'should not update labels if not present' do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to eq([label.title]) end it 'should remove all labels' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: '' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to eq([]) end it 'should update labels' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'foo,bar' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to include 'foo' expect(json_response['labels']).to include 'bar' end @@ -488,14 +630,14 @@ describe API::API, api: true do it 'should return 400 on invalid label names' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label, ?' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) end it 'should allow special label names' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label:foo, label-bar,label_bar,label/bar' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to include 'label:foo' expect(json_response['labels']).to include 'label-bar' expect(json_response['labels']).to include 'label_bar' @@ -505,7 +647,7 @@ describe API::API, api: true do it 'should return 400 if title is too long' do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'g' * 256 - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' ]) @@ -516,7 +658,7 @@ describe API::API, api: true do it "should update a project issue" do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label2', state_event: "close" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to include 'label2' expect(json_response['state']).to eq "closed" @@ -527,7 +669,7 @@ describe API::API, api: true do update_time = 2.weeks.ago put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label3', state_event: 'close', updated_at: update_time - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to include 'label3' expect(Time.parse(json_response['updated_at'])).to be_within(1.second).of(update_time) @@ -538,12 +680,12 @@ describe API::API, api: true do describe "DELETE /projects/:id/issues/:issue_id" do it "rejects a non member from deleting an issue" do delete api("/projects/#{project.id}/issues/#{issue.id}", non_member) - expect(response.status).to be(403) + expect(response).to have_http_status(403) end it "rejects a developer from deleting an issue" do delete api("/projects/#{project.id}/issues/#{issue.id}", author) - expect(response.status).to be(403) + expect(response).to have_http_status(403) end context "when the user is project owner" do @@ -552,7 +694,7 @@ describe API::API, api: true do it "deletes the issue if an admin requests it" do delete api("/projects/#{project.id}/issues/#{issue.id}", owner) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['state']).to eq 'opened' end end @@ -566,7 +708,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: target_project.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['project_id']).to eq(target_project.id) end @@ -575,7 +717,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: project.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Cannot move issue to project it originates from!') end end @@ -585,7 +727,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: target_project2.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') end end @@ -594,7 +736,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", admin), to_project_id: target_project2.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['project_id']).to eq(target_project2.id) end @@ -603,7 +745,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/123/move", user), to_project_id: target_project.id - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -612,7 +754,7 @@ describe API::API, api: true do post api("/projects/123/issues/#{issue.id}/move", user), to_project_id: target_project.id - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -621,7 +763,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: 123 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -630,26 +772,26 @@ describe API::API, api: true do it 'subscribes to an issue' do post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end it 'returns 404 if the issue is not found' do post api("/projects/#{project.id}/issues/123/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns 404 if the issue is confidential' do post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -657,26 +799,26 @@ describe API::API, api: true do it 'unsubscribes from an issue' do delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end it 'returns 404 if the issue is not found' do delete api("/projects/#{project.id}/issues/123/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns 404 if the issue is confidential' do delete api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index d2b87f88712..1861882d59e 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -14,14 +14,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api("/keys/#{key.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return 404 for non-existing key' do get api('/keys/999999', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -29,7 +29,7 @@ describe API::API, api: true do user.keys << key user.save get api("/keys/#{key.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(key.title) expect(json_response['user']['id']).to eq(user.id) expect(json_response['user']['username']).to eq(user.username) diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index b2c7f8d9acb..39736779986 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -15,7 +15,7 @@ describe API::API, api: true do describe 'GET /projects/:id/labels' do it 'should return project labels' do get api("/projects/#{project.id}/labels", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.first['name']).to eq(label1.name) @@ -28,7 +28,7 @@ describe API::API, api: true do name: 'Foo', color: '#FFAABB', description: 'test' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('Foo') expect(json_response['color']).to eq('#FFAABB') expect(json_response['description']).to eq('test') @@ -38,7 +38,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAABB' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('Foo') expect(json_response['color']).to eq('#FFAABB') expect(json_response['description']).to be_nil @@ -46,19 +46,19 @@ describe API::API, api: true do it 'should return a 400 bad request if name not given' do post api("/projects/#{project.id}/labels", user), color: '#FFAABB' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return a 400 bad request if color not given' do post api("/projects/#{project.id}/labels", user), name: 'Foobar' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 for invalid color' do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAA' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end @@ -66,7 +66,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAAFFFF' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end @@ -74,7 +74,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/labels", user), name: '?', color: '#FFAABB' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) end @@ -82,7 +82,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FFAABB' - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']).to eq('Label already exists') end end @@ -90,18 +90,18 @@ describe API::API, api: true do describe 'DELETE /projects/:id/labels' do it 'should return 200 for existing label' do delete api("/projects/#{project.id}/labels", user), name: 'label1' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return 404 for non existing label' do delete api("/projects/#{project.id}/labels", user), name: 'label2' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Label Not Found') end it 'should return 400 for wrong parameters' do delete api("/projects/#{project.id}/labels", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -112,7 +112,7 @@ describe API::API, api: true do new_name: 'New Label', color: '#FFFFFF', description: 'test' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq('New Label') expect(json_response['color']).to eq('#FFFFFF') expect(json_response['description']).to eq('test') @@ -122,7 +122,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label1', new_name: 'New Label' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq('New Label') expect(json_response['color']).to eq(label1.color) end @@ -131,7 +131,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FFFFFF' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(label1.name) expect(json_response['color']).to eq('#FFFFFF') end @@ -140,7 +140,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label1', description: 'test' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(label1.name) expect(json_response['description']).to eq('test') end @@ -149,18 +149,18 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label2', new_name: 'label3' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should return 400 if no label name given' do put api("/projects/#{project.id}/labels", user), new_name: 'label2' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "name" not given') end it 'should return 400 if no new parameters given' do put api("/projects/#{project.id}/labels", user), name: 'label1' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Required parameters '\ '"new_name" or "color" missing') end @@ -170,7 +170,7 @@ describe API::API, api: true do name: 'label1', new_name: '?', color: '#FFFFFF' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) end @@ -178,7 +178,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FF' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end @@ -186,7 +186,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAAFFFF' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end end @@ -196,7 +196,7 @@ describe API::API, api: true do it "should subscribe to the label" do post api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_truthy end @@ -206,7 +206,7 @@ describe API::API, api: true do it "should subscribe to the label" do post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_truthy end @@ -218,7 +218,7 @@ describe API::API, api: true do it "should return 304" do post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end end @@ -226,7 +226,7 @@ describe API::API, api: true do it "should a return 404 error" do post api("/projects/#{project.id}/labels/1234/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -238,7 +238,7 @@ describe API::API, api: true do it "should unsubscribe from the label" do delete api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_falsey end @@ -248,7 +248,7 @@ describe API::API, api: true do it "should unsubscribe from the label" do delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_falsey end @@ -260,7 +260,7 @@ describe API::API, api: true do it "should return 304" do delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end end @@ -268,7 +268,7 @@ describe API::API, api: true do it "should a return 404 error" do delete api("/projects/#{project.id}/labels/1234/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/api/licenses_spec.rb b/spec/requests/api/license_templates_spec.rb index 3726b2f5688..9a1894d63a2 100644 --- a/spec/requests/api/licenses_spec.rb +++ b/spec/requests/api/license_templates_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::Licenses, api: true do +describe API::API, api: true do include ApiHelpers describe 'Entity' do @@ -23,7 +23,7 @@ describe API::Licenses, api: true do it 'returns a list of available license templates' do get api('/licenses') - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(15) expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') @@ -34,7 +34,7 @@ describe API::Licenses, api: true do it 'returns a list of available popular license templates' do get api('/licenses?popular=1') - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(3) expect(json_response.map { |l| l['key'] }).to include('apache-2.0') @@ -116,7 +116,7 @@ describe API::Licenses, api: true do let(:license_type) { 'muth-over9000' } it 'returns a 404' do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 5896b93603f..61e897edf87 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -22,14 +22,14 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/projects/#{project.id}/merge_requests") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when authenticated" do it "should return an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.last['title']).to eq(merge_request.title) @@ -37,7 +37,7 @@ describe API::API, api: true do it "should return an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests?state", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.last['title']).to eq(merge_request.title) @@ -45,7 +45,7 @@ describe API::API, api: true do it "should return an array of open merge_requests" do get api("/projects/#{project.id}/merge_requests?state=opened", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.last['title']).to eq(merge_request.title) @@ -53,7 +53,7 @@ describe API::API, api: true do it "should return an array of closed merge_requests" do get api("/projects/#{project.id}/merge_requests?state=closed", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(merge_request_closed.title) @@ -61,7 +61,7 @@ describe API::API, api: true do it "should return an array of merged merge_requests" do get api("/projects/#{project.id}/merge_requests?state=merged", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(merge_request_merged.title) @@ -75,7 +75,7 @@ describe API::API, api: true do it "should return an array of merge_requests in ascending order" do get api("/projects/#{project.id}/merge_requests?sort=asc", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map{ |merge_request| merge_request['created_at'] } @@ -84,7 +84,7 @@ describe API::API, api: true do it "should return an array of merge_requests in descending order" do get api("/projects/#{project.id}/merge_requests?sort=desc", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map{ |merge_request| merge_request['created_at'] } @@ -93,7 +93,7 @@ describe API::API, api: true do it "should return an array of merge_requests ordered by updated_at" do get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map{ |merge_request| merge_request['updated_at'] } @@ -102,7 +102,7 @@ describe API::API, api: true do it "should return an array of merge_requests ordered by created_at" do get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map{ |merge_request| merge_request['created_at'] } @@ -116,7 +116,7 @@ describe API::API, api: true do it 'exposes known attributes' do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(merge_request.id) expect(json_response['iid']).to eq(merge_request.iid) expect(json_response['project_id']).to eq(merge_request.project.id) @@ -142,7 +142,7 @@ describe API::API, api: true do it "should return merge_request" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(merge_request.title) expect(json_response['iid']).to eq(merge_request.iid) expect(json_response['work_in_progress']).to eq(false) @@ -159,7 +159,7 @@ describe API::API, api: true do it "should return a 404 error if merge_request_id not found" do get api("/projects/#{project.id}/merge_requests/999", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context 'Work in Progress' do @@ -167,7 +167,7 @@ describe API::API, api: true do it "should return merge_request" do get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['work_in_progress']).to eq(true) end end @@ -186,7 +186,7 @@ describe API::API, api: true do it 'returns a 404 when merge_request_id not found' do get api("/projects/#{project.id}/merge_requests/999/commits", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -199,7 +199,7 @@ describe API::API, api: true do it 'returns a 404 when merge_request_id not found' do get api("/projects/#{project.id}/merge_requests/999/changes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -213,7 +213,7 @@ describe API::API, api: true do author: user, labels: 'label, label2', milestone_id: milestone.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['labels']).to eq(['label', 'label2']) expect(json_response['milestone']['id']).to eq(milestone.id) @@ -222,25 +222,25 @@ describe API::API, api: true do it "should return 422 when source_branch equals target_branch" do post api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", source_branch: "master", target_branch: "master", author: user - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end it "should return 400 when source_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", target_branch: "master", author: user - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 when target_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", source_branch: "markdown", author: user - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 when title is missing" do post api("/projects/#{project.id}/merge_requests", user), target_branch: 'master', source_branch: 'markdown' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 on invalid label names' do @@ -250,7 +250,7 @@ describe API::API, api: true do target_branch: 'master', author: user, labels: 'label, ?' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['labels']['?']['title']).to eq( ['is invalid'] ) @@ -274,7 +274,7 @@ describe API::API, api: true do target_branch: 'master', author: user end.to change { MergeRequest.count }.by(0) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) end end end @@ -292,7 +292,7 @@ describe API::API, api: true do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['description']).to eq('Test description for Test merge_request') end @@ -303,26 +303,26 @@ describe API::API, api: true do expect(fork_project.forked_from_project).to eq(project) post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('Test merge_request') end it "should return 400 when source_branch is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 when target_branch is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 when title is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end context 'when target_branch is specified' do @@ -333,7 +333,7 @@ describe API::API, api: true do source_branch: 'markdown', author: user, target_project_id: fork_project.id - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end it 'should return 422 if targeting a different fork' do @@ -343,14 +343,14 @@ describe API::API, api: true do source_branch: 'markdown', author: user2, target_project_id: unrelated_project.id - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end it "should return 201 when target_branch is specified and for the same project" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end end end @@ -365,7 +365,7 @@ describe API::API, api: true do it "denies the deletion of the merge request" do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", developer) - expect(response.status).to be(403) + expect(response).to have_http_status(403) end end @@ -373,7 +373,7 @@ describe API::API, api: true do it "destroys the merge request owners can destroy" do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -381,7 +381,7 @@ describe API::API, api: true do describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do it "should return merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['state']).to eq('closed') end end @@ -392,7 +392,7 @@ describe API::API, api: true do it "should return merge_request in case of success" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 406 if branch can't be merged" do @@ -401,21 +401,21 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(406) + expect(response).to have_http_status(406) expect(json_response['message']).to eq('Branch cannot be merged') end it "should return 405 if merge_request is not open" do merge_request.close put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('405 Method Not Allowed') end it "should return 405 if merge_request is a work in progress" do merge_request.update_attribute(:title, "WIP: #{merge_request.title}") put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('405 Method Not Allowed') end @@ -424,7 +424,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('405 Method Not Allowed') end @@ -432,21 +432,21 @@ describe API::API, api: true do user2 = create(:user) project.team << [user2, :reporter] put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2) - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['message']).to eq('401 Unauthorized') end it "returns 409 if the SHA parameter doesn't match" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.source_sha.succ - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']).to start_with('SHA does not match HEAD of source branch') end it "succeeds if the SHA parameter matches" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.source_sha - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "enables merge when build succeeds if the ci is active" do @@ -455,7 +455,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('Test') expect(json_response['merge_when_build_succeeds']).to eq(true) end @@ -464,31 +464,31 @@ describe API::API, api: true do describe "PUT /projects/:id/merge_requests/:merge_request_id" do it "updates title and returns merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('New title') end it "updates description and returns merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq('New description') end it "updates milestone_id and returns merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['milestone']['id']).to eq(milestone.id) end it "should return 400 when source_branch is specified" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), source_branch: "master", target_branch: "master" - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return merge_request with renamed target_branch" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['target_branch']).to eq('wiki') end @@ -497,7 +497,7 @@ describe API::API, api: true do user), title: 'new issue', labels: 'label, ?' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) end end @@ -507,7 +507,7 @@ describe API::API, api: true do original_count = merge_request.notes.size post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment" - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['author']['name']).to eq(user.name) expect(json_response['author']['username']).to eq(user.username) @@ -516,20 +516,20 @@ describe API::API, api: true do it "should return 400 if note is missing" do post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 404 if note is attached to non existent merge request" do post api("/projects/#{project.id}/merge_requests/404/comments", user), note: 'My comment' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe "GET :id/merge_requests/:merge_request_id/comments" do it "should return merge_request comments ordered by created_at" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['note']).to eq("a comment on a MR") @@ -539,7 +539,7 @@ describe API::API, api: true do it "should return a 404 error if merge_request_id not found" do get api("/projects/#{project.id}/merge_requests/999/comments", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -551,7 +551,7 @@ describe API::API, api: true do end get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(issue.id) @@ -559,7 +559,7 @@ describe API::API, api: true do it 'returns an empty array when there are no issues to be closed' do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -572,7 +572,7 @@ describe API::API, api: true do get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(issue.title) @@ -584,20 +584,20 @@ describe API::API, api: true do it 'subscribes to a merge request' do post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end it 'returns 404 if the merge request is not found' do post api("/projects/#{project.id}/merge_requests/123/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -605,20 +605,20 @@ describe API::API, api: true do it 'unsubscribes from a merge request' do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end it 'returns 404 if the merge request is not found' do post api("/projects/#{project.id}/merge_requests/123/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 0154d1c62cc..0f4e38b2475 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -12,20 +12,20 @@ describe API::API, api: true do describe 'GET /projects/:id/milestones' do it 'should return project milestones' do get api("/projects/#{project.id}/milestones", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(milestone.title) end it 'should return a 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it 'returns an array of active milestones' do get api("/projects/#{project.id}/milestones?state=active", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(milestone.id) @@ -34,7 +34,7 @@ describe API::API, api: true do it 'returns an array of closed milestones' do get api("/projects/#{project.id}/milestones?state=closed", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_milestone.id) @@ -44,7 +44,7 @@ describe API::API, api: true do describe 'GET /projects/:id/milestones/:milestone_id' do it 'should return a project milestone by id' do get api("/projects/#{project.id}/milestones/#{milestone.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(milestone.title) expect(json_response['iid']).to eq(milestone.iid) end @@ -60,19 +60,19 @@ describe API::API, api: true do it 'should return 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones/#{milestone.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it 'should return a 404 error if milestone id not found' do get api("/projects/#{project.id}/milestones/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe 'POST /projects/:id/milestones' do it 'should create a new project milestone' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('new milestone') expect(json_response['description']).to be_nil end @@ -80,14 +80,14 @@ describe API::API, api: true do it 'should create a new project milestone with description and due date' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone', description: 'release', due_date: '2013-03-02' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['description']).to eq('release') expect(json_response['due_date']).to eq('2013-03-02') end it 'should return a 400 error if title is missing' do post api("/projects/#{project.id}/milestones", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -95,14 +95,14 @@ describe API::API, api: true do it 'should update a project milestone' do put api("/projects/#{project.id}/milestones/#{milestone.id}", user), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end it 'should return a 404 error if milestone id not found' do put api("/projects/#{project.id}/milestones/1234", user), title: 'updated title' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -110,7 +110,7 @@ describe API::API, api: true do it 'should update a project milestone' do put api("/projects/#{project.id}/milestones/#{milestone.id}", user), state_event: 'close' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['state']).to eq('closed') end @@ -131,14 +131,14 @@ describe API::API, api: true do end it 'should return project issues for a particular milestone' do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['milestone']['title']).to eq(milestone.title) end it 'should return a 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end describe 'confidential issues' do @@ -155,7 +155,7 @@ describe API::API, api: true do 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(response).to have_http_status(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) @@ -167,7 +167,7 @@ describe API::API, api: true do get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member) - expect(response.status).to eq(200) + expect(response).to have_http_status(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) @@ -176,7 +176,7 @@ describe API::API, api: true do 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(response).to have_http_status(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) diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 21787fdd895..237b4b17eb5 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -11,14 +11,14 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/namespaces") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when authenticated as admin" do it "admin: should return an array of all namespaces" do get api("/namespaces", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(Namespace.count) @@ -26,7 +26,7 @@ describe API::API, api: true do it "admin: should return an array of matched namespaces" do get api("/namespaces?search=#{group1.name}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -36,7 +36,7 @@ describe API::API, api: true do context "when authenticated as a regular user" do it "user: should return an array of namespaces" do get api("/namespaces", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -44,7 +44,7 @@ describe API::API, api: true do it "admin: should return an array of matched namespaces" do get api("/namespaces?search=#{user.username}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index beb29a68692..bacd01f8e74 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -40,7 +40,7 @@ describe API::API, api: true do it "should return an array of issue notes" do get api("/projects/#{project.id}/issues/#{issue.id}/notes", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(issue_note.note) end @@ -48,14 +48,14 @@ describe API::API, api: true do it "should return a 404 error when issue id not found" do get api("/projects/#{project.id}/issues/12345/notes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context "and current user cannot view the notes" do it "should return an empty array" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response).to be_empty end @@ -66,7 +66,7 @@ describe API::API, api: true do it "returns 404" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -74,7 +74,7 @@ describe API::API, api: true do it "should return an empty array" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(cross_reference_note.note) end @@ -86,7 +86,7 @@ describe API::API, api: true do it "should return an array of snippet notes" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(snippet_note.note) end @@ -94,13 +94,13 @@ describe API::API, api: true do it "should return a 404 error when snippet id not found" do get api("/projects/#{project.id}/snippets/42/notes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "returns 404 when not authorized" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -108,7 +108,7 @@ describe API::API, api: true do it "should return an array of merge_requests notes" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(merge_request_note.note) end @@ -116,13 +116,13 @@ describe API::API, api: true do it "should return a 404 error if merge request id not found" do get api("/projects/#{project.id}/merge_requests/4444/notes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "returns 404 when not authorized" do get api("/projects/#{project.id}/merge_requests/4444/notes", private_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -132,21 +132,21 @@ describe API::API, api: true do it "should return an issue note by id" do get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq(issue_note.note) end it "should return a 404 error if issue note not found" do get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context "and current user cannot view the note" do it "should return a 404 error" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context "when issue is confidential" do @@ -155,7 +155,7 @@ describe API::API, api: true do it "returns 404" do get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", private_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -164,7 +164,7 @@ describe API::API, api: true do it "should return an issue note by id" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq(cross_reference_note.note) end end @@ -175,14 +175,14 @@ describe API::API, api: true do it "should return a snippet note by id" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq(snippet_note.note) end it "should return a 404 error if snippet note not found" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -192,7 +192,7 @@ describe API::API, api: true do it "should create a new issue note" do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) end @@ -200,13 +200,13 @@ describe API::API, api: true do it "should return a 400 bad request error if body not given" do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 401 unauthorized error if user not authenticated" do post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end context 'when an admin or owner makes the request' do @@ -215,7 +215,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!', created_at: creation_time - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) @@ -228,7 +228,7 @@ describe API::API, api: true do it "should create a new snippet note" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) end @@ -236,13 +236,13 @@ describe API::API, api: true do it "should return a 400 bad request error if body not given" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 401 unauthorized error if user not authenticated" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -282,7 +282,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user), body: 'Hello!' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -290,14 +290,14 @@ describe API::API, api: true do put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user), body: 'Hello!' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should return a 400 bad request error if body not given' do put api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -306,7 +306,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user), body: 'Hello!' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -314,7 +314,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/12345", user), body: "Hello!" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -323,7 +323,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ "notes/#{merge_request_note.id}", user), body: 'Hello!' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -331,7 +331,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ "notes/12345", user), body: "Hello!" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -342,17 +342,17 @@ describe API::API, api: true do delete api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) # Check if note is really deleted delete api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -361,18 +361,18 @@ describe API::API, api: true do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) # Check if note is really deleted delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -381,18 +381,18 @@ describe API::API, api: true do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/#{merge_request_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) # Check if note is really deleted delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/#{merge_request_note.id}", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index ffb93bbb120..fd1fffa6223 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -22,7 +22,7 @@ describe API::API, 'ProjectHooks', api: true do context "authorized user" do it "should return project hooks" do get api("/projects/#{project.id}/hooks", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.count).to eq(1) @@ -40,7 +40,7 @@ describe API::API, 'ProjectHooks', api: true do context "unauthorized user" do it "should not access project hooks" do get api("/projects/#{project.id}/hooks", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -49,7 +49,7 @@ describe API::API, 'ProjectHooks', api: true do context "authorized user" do it "should return a project hook" do get api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['url']).to eq(hook.url) expect(json_response['issues_events']).to eq(hook.issues_events) expect(json_response['push_events']).to eq(hook.push_events) @@ -61,20 +61,20 @@ describe API::API, 'ProjectHooks', api: true do it "should return a 404 error if hook id is not available" do get api("/projects/#{project.id}/hooks/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context "unauthorized user" do it "should not access an existing hook" do get api("/projects/#{project.id}/hooks/#{hook.id}", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end it "should return a 404 error if hook id is not available" do get api("/projects/#{project.id}/hooks/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -83,7 +83,7 @@ describe API::API, 'ProjectHooks', api: true do expect do post api("/projects/#{project.id}/hooks", user), url: "http://example.com", issues_events: true end.to change {project.hooks.count}.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['url']).to eq('http://example.com') expect(json_response['issues_events']).to eq(true) expect(json_response['push_events']).to eq(true) @@ -96,12 +96,12 @@ describe API::API, 'ProjectHooks', api: true do it "should return a 400 error if url not given" do post api("/projects/#{project.id}/hooks", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error if url not valid" do post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com" - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -109,7 +109,7 @@ describe API::API, 'ProjectHooks', api: true do it "should update an existing project hook" do put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'http://example.org', push_events: false - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['url']).to eq('http://example.org') expect(json_response['issues_events']).to eq(hook.issues_events) expect(json_response['push_events']).to eq(false) @@ -121,17 +121,17 @@ describe API::API, 'ProjectHooks', api: true do it "should return 404 error if hook id not found" do put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 400 error if url is not given" do put api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error if url is not valid" do put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -140,22 +140,22 @@ describe API::API, 'ProjectHooks', api: true do expect do delete api("/projects/#{project.id}/hooks/#{hook.id}", user) end.to change {project.hooks.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return success when deleting hook" do delete api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return a 404 error when deleting non existent hook" do delete api("/projects/#{project.id}/hooks/42", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return a 405 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end it "shold return a 404 if a user attempts to delete project hooks he/she does not own" do @@ -164,7 +164,7 @@ describe API::API, 'ProjectHooks', api: true do other_project.team << [test_user, :master] delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(WebHook.exists?(hook.id)).to be_truthy end end diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb index 44b532b10e1..9a7c1da4401 100644 --- a/spec/requests/api/project_members_spec.rb +++ b/spec/requests/api/project_members_spec.rb @@ -15,7 +15,7 @@ describe API::API, api: true do it "should return project team members" do get api("/projects/#{project.id}/members", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.count).to eq(2) expect(json_response.map { |u| u['username'] }).to include user.username @@ -23,7 +23,7 @@ describe API::API, api: true do it "finds team members with query string" do get api("/projects/#{project.id}/members", user), query: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.count).to eq(1) expect(json_response.first['username']).to eq(user.username) @@ -31,7 +31,7 @@ describe API::API, api: true do it "should return a 404 error if id not found" do get api("/projects/9999/members", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -40,14 +40,14 @@ describe API::API, api: true do it "should return project team member" do get api("/projects/#{project.id}/members/#{user.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) expect(json_response['access_level']).to eq(ProjectMember::MASTER) end it "should return a 404 error if user id not found" do get api("/projects/#{project.id}/members/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -57,7 +57,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: ProjectMember::DEVELOPER end.to change { ProjectMember.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['username']).to eq(user2.username) expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER) end @@ -70,24 +70,24 @@ describe API::API, api: true do post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: ProjectMember::DEVELOPER end.not_to change { ProjectMember.count } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['username']).to eq(user2.username) expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER) end it "should return a 400 error when user id is not given" do post api("/projects/#{project.id}/members", user), access_level: ProjectMember::MASTER - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 400 error when access level is not given" do post api("/projects/#{project.id}/members", user), user_id: user2.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error when access level is not known" do post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234 - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -96,24 +96,24 @@ describe API::API, api: true do it "should update project team member" do put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: ProjectMember::MASTER - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user3.username) expect(json_response['access_level']).to eq(ProjectMember::MASTER) end it "should return a 404 error if user_id is not found" do put api("/projects/#{project.id}/members/1234", user), access_level: ProjectMember::MASTER - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return a 400 error when access level is not given" do put api("/projects/#{project.id}/members/#{user3.id}", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error when access level is not known" do put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: 123 - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -134,20 +134,20 @@ describe API::API, api: true do expect do delete api("/projects/#{project.id}/members/#{user3.id}", user) end.not_to change { ProjectMember.count } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 200 if team member already removed" do delete api("/projects/#{project.id}/members/#{user3.id}", user) delete api("/projects/#{project.id}/members/#{user3.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 200 OK when the user was not member" do expect do delete api("/projects/#{project.id}/members/1000000", user) end.to change { ProjectMember.count }.by(0) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(1000000) expect(json_response['message']).to eq('Access revoked') end @@ -158,7 +158,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/members/#{user3.id}", user3) end.to change { ProjectMember.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(project_member2.id) end end diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 9706d060cfa..4ebde201941 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -27,7 +27,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/snippets/", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(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 @@ -38,7 +38,7 @@ describe API::API, api: true do create(:project_snippet, :private, project: project) get api("/projects/#{project.id}/snippets/", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.size).to eq(0) end end @@ -56,7 +56,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/snippets/", admin), params - expect(response.status).to eq(201) + expect(response).to have_http_status(201) snippet = ProjectSnippet.find(json_response['id']) expect(snippet.content).to eq(params[:code]) expect(snippet.title).to eq(params[:title]) @@ -73,7 +73,7 @@ describe API::API, api: true do put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content - expect(response.status).to eq(200) + expect(response).to have_http_status(200) snippet.reload expect(snippet.content).to eq(new_content) end @@ -86,7 +86,7 @@ describe API::API, api: true do delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -97,7 +97,7 @@ describe API::API, api: true do get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'text/plain' expect(response.body).to eq(snippet.content) end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 01eb4b44b83..41b5ed9bc33 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -45,14 +45,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api('/projects') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return an array of projects' do get api('/projects', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(project.name) expect(json_response.first['owner']['username']).to eq(user.username) @@ -84,7 +84,7 @@ describe API::API, api: true do context 'and using search' do it 'should return searched project' do get api('/projects', user), { search: project.name } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) end @@ -93,21 +93,21 @@ describe API::API, api: true do context 'and using the visibility filter' do it 'should filter based on private visibility param' do get api('/projects', user), { visibility: 'private' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count) end it 'should filter based on internal visibility param' do get api('/projects', user), { visibility: 'internal' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count) end it 'should filter based on public visibility param' do get api('/projects', user), { visibility: 'public' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count) end @@ -121,7 +121,7 @@ describe API::API, api: true do it 'should return the correct order when sorted by id' do get api('/projects', user), { order_by: 'id', sort: 'desc' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['id']).to eq(project3.id) end @@ -135,21 +135,21 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api('/projects/all') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated as regular user' do it 'should return authentication error' do get api('/projects/all', user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context 'when authenticated as admin' do it 'should return an array of all projects' do get api('/projects/all', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response).to satisfy do |response| @@ -173,7 +173,7 @@ describe API::API, api: true do it 'should return the starred projects viewable by the user' do get api('/projects/starred', user3) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id) end @@ -185,25 +185,25 @@ describe API::API, api: true do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0) expect { post api('/projects', user2), name: 'foo' }. to change {Project.count}.by(0) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end it 'should create new project without path and return 201' do expect { post api('/projects', user), name: 'foo' }. to change { Project.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'should create last project before reaching project limit' do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1) post api('/projects', user2), name: 'foo' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'should not create new project without name and return 400' do expect { post api('/projects', user) }.not_to change { Project.count } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should assign attributes to project" do @@ -273,7 +273,7 @@ describe API::API, api: true do it 'should not allow a non-admin to use a restricted visibility level' do post api('/projects', user), @project - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['visibility_level'].first).to( match('restricted by your GitLab administrator') ) @@ -295,14 +295,14 @@ describe API::API, api: true do it 'should create new project without path and return 201' do expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'should respond with 400 on failure and not project' do expect { post api("/projects/user/#{user.id}", admin) }. not_to change { Project.count } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['name']).to eq([ 'can\'t be blank', 'is too short (minimum is 0 characters)', @@ -380,7 +380,7 @@ describe API::API, api: true do it "uploads the file and returns its info" do post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") - expect(response.status).to be(201) + expect(response).to have_http_status(201) expect(json_response['alt']).to eq("dk") expect(json_response['url']).to start_with("/uploads/") expect(json_response['url']).to end_with("/dk.png") @@ -394,27 +394,27 @@ describe API::API, api: true do it 'should return a project by id' do get api("/projects/#{project.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(project.name) expect(json_response['owner']['username']).to eq(user.username) end it 'should return a project by path name' do get api("/projects/#{project.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(project.name) end it 'should return a 404 error if not found' do get api('/projects/42', user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'should return a 404 error if user is not a member' do other_user = create(:user) get api("/projects/#{project.id}", other_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should handle users with dots' do @@ -422,7 +422,7 @@ describe API::API, api: true do project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(project.name) end @@ -433,7 +433,7 @@ describe API::API, api: true do it 'contains permission information' do get api("/projects", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.first['permissions']['project_access']['access_level']). to eq(Gitlab::Access::MASTER) expect(json_response.first['permissions']['group_access']).to be_nil @@ -445,7 +445,7 @@ describe API::API, api: true do project.team << [user, :master] get api("/projects/#{project.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['permissions']['project_access']['access_level']). to eq(Gitlab::Access::MASTER) expect(json_response['permissions']['group_access']).to be_nil @@ -460,7 +460,7 @@ describe API::API, api: true do it 'should set the owner and return 200' do get api("/projects/#{project2.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['permissions']['project_access']).to be_nil expect(json_response['permissions']['group_access']['access_level']). to eq(Gitlab::Access::OWNER) @@ -479,7 +479,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/events", user) end - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } context 'joined event' do let(:json_event) { json_response[1] } @@ -500,14 +500,14 @@ describe API::API, api: true do it 'should return a 404 error if not found' do get api('/projects/42/events', user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'should return a 404 error if user is not a member' do other_user = create(:user) get api("/projects/#{project.id}/events", other_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -516,7 +516,7 @@ describe API::API, api: true do it 'should return an array of project snippets' do get api("/projects/#{project.id}/snippets", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(snippet.title) end @@ -525,13 +525,13 @@ describe API::API, api: true do describe 'GET /projects/:id/snippets/:snippet_id' do it 'should return a project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(snippet.title) end it 'should return a 404 error if snippet id not found' do get api("/projects/#{project.id}/snippets/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -540,7 +540,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/snippets", user), title: 'api test', file_name: 'sample.rb', code: 'test', visibility_level: '0' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('api test') end @@ -554,7 +554,7 @@ describe API::API, api: true do it 'should update an existing project snippet' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), code: 'updated code' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('example') expect(snippet.reload.content).to eq('updated code') end @@ -562,7 +562,7 @@ describe API::API, api: true do it 'should update an existing project snippet with new title' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), title: 'other api test' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('other api test') end end @@ -574,24 +574,24 @@ describe API::API, api: true do expect do delete api("/projects/#{project.id}/snippets/#{snippet.id}", user) end.to change { Snippet.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return 404 when deleting unknown snippet id' do delete api("/projects/#{project.id}/snippets/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe 'GET /projects/:id/snippets/:snippet_id/raw' do it 'should get a raw project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return a 404 error if raw project snippet not found' do get api("/projects/#{project.id}/snippets/5555/raw", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -604,7 +604,7 @@ describe API::API, api: true do it 'should return array of ssh keys' do get api("/projects/#{project.id}/keys", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(deploy_key.title) end @@ -613,20 +613,20 @@ describe API::API, api: true do describe 'GET /projects/:id/keys/:key_id' do it 'should return a single key' do get api("/projects/#{project.id}/keys/#{deploy_key.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(deploy_key.title) end it 'should return 404 Not Found with invalid ID' do get api("/projects/#{project.id}/keys/404", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe 'POST /projects/:id/keys' do it 'should not create an invalid ssh key' do post api("/projects/#{project.id}/keys", user), { title: 'invalid key' } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['key']).to eq([ 'can\'t be blank', 'is too short (minimum is 0 characters)', @@ -636,7 +636,7 @@ describe API::API, api: true do it 'should not create a key without title' do post api("/projects/#{project.id}/keys", user), key: 'some key' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq([ 'can\'t be blank', 'is too short (minimum is 0 characters)' @@ -662,7 +662,7 @@ describe API::API, api: true do it 'should return 404 Not Found with invalid ID' do delete api("/projects/#{project.id}/keys/404", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -676,13 +676,13 @@ describe API::API, api: true do it "shouldn't available for non admin users" do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should allow project to be forked from an existing project' do expect(project_fork_target.forked?).not_to be_truthy post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) expect(project_fork_target.forked_project_link).not_to be_nil @@ -691,7 +691,7 @@ describe API::API, api: true do it 'should fail if forked_from project which does not exist' do post api("/projects/#{project_fork_target.id}/fork/9999", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should fail with 409 if already forked' do @@ -699,7 +699,7 @@ describe API::API, api: true do project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) expect(project_fork_target.forked?).to be_truthy @@ -710,7 +710,7 @@ describe API::API, api: true do it "shouldn't be visible to users outside group" do delete api("/projects/#{project_fork_target.id}/fork", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context 'when users belong to project group' do @@ -723,7 +723,7 @@ describe API::API, api: true do it 'should be forbidden to non-owner users' do delete api("/projects/#{project_fork_target.id}/fork", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should make forked project unforked' do @@ -732,7 +732,7 @@ describe API::API, api: true do expect(project_fork_target.forked_from_project).not_to be_nil expect(project_fork_target.forked?).to be_truthy delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_fork_target.reload expect(project_fork_target.forked_from_project).to be_nil expect(project_fork_target.forked?).not_to be_truthy @@ -741,7 +741,7 @@ describe API::API, api: true do it 'should be idempotent if not forked' do expect(project_fork_target.forked_from_project).to be_nil delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(project_fork_target.reload.forked_from_project).to be_nil end end @@ -799,14 +799,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api("/projects/search/#{query}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return an array of projects' do get api("/projects/search/#{query}",user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(6) json_response.each {|project| expect(project['name']).to match(/.*query.*/)} @@ -816,7 +816,7 @@ describe API::API, api: true do context 'when authenticated as a different user' do it 'should return matching public projects' do get api("/projects/search/#{query}", user2) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(2) json_response.each {|project| expect(project['name']).to match(/(internal|public) query/)} @@ -838,7 +838,7 @@ describe API::API, api: true do it 'should return authentication error' do project_param = { name: 'bar' } put api("/projects/#{project.id}"), project_param - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -846,7 +846,7 @@ describe API::API, api: true do it 'should update name' do project_param = { name: 'bar' } put api("/projects/#{project.id}", user), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -855,7 +855,7 @@ describe API::API, api: true do it 'should update visibility_level' do project_param = { visibility_level: 20 } put api("/projects/#{project3.id}", user), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -866,7 +866,7 @@ describe API::API, api: true do project_param = { public: false } put api("/projects/#{project3.id}", user), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -876,14 +876,14 @@ describe API::API, api: true do it 'should not update name to existing name' do project_param = { name: project3.name } put api("/projects/#{project.id}", user), project_param - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['name']).to eq(['has already been taken']) end it 'should update path & name to existing path & name in different namespace' do project_param = { path: project4.path, name: project4.name } put api("/projects/#{project3.id}", user), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -894,7 +894,7 @@ describe API::API, api: true do it 'should update path' do project_param = { path: 'bar' } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -908,7 +908,7 @@ describe API::API, api: true do description: 'new description' } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -917,20 +917,20 @@ describe API::API, api: true do it 'should not update path to existing path' do project_param = { path: project.path } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['path']).to eq(['has already been taken']) end it 'should not update name' do project_param = { name: 'bar' } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should not update visibility_level' do project_param = { visibility_level: 20 } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -943,7 +943,7 @@ describe API::API, api: true do merge_requests_enabled: true, description: 'new description' } put api("/projects/#{project.id}", user3), project_param - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -953,7 +953,7 @@ describe API::API, api: true do it 'archives the project' do post api("/projects/#{project.id}/archive", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['archived']).to be_truthy end end @@ -966,7 +966,7 @@ describe API::API, api: true do it 'remains archived' do post api("/projects/#{project.id}/archive", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['archived']).to be_truthy end end @@ -979,7 +979,7 @@ describe API::API, api: true do it 'rejects the action' do post api("/projects/#{project.id}/archive", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -989,7 +989,7 @@ describe API::API, api: true do it 'remains unarchived' do post api("/projects/#{project.id}/unarchive", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['archived']).to be_falsey end end @@ -1002,7 +1002,7 @@ describe API::API, api: true do it 'unarchives the project' do post api("/projects/#{project.id}/unarchive", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['archived']).to be_falsey end end @@ -1015,7 +1015,7 @@ describe API::API, api: true do it 'rejects the action' do post api("/projects/#{project.id}/unarchive", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -1025,7 +1025,7 @@ describe API::API, api: true do it 'stars the project' do expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['star_count']).to eq(1) end end @@ -1039,7 +1039,7 @@ describe API::API, api: true do it 'does not modify the star count' do expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end end end @@ -1054,7 +1054,7 @@ describe API::API, api: true do it 'unstars the project' do expect { delete api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['star_count']).to eq(0) end end @@ -1063,7 +1063,7 @@ describe API::API, api: true do it 'does not modify the star count' do expect { delete api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end end end @@ -1072,36 +1072,36 @@ describe API::API, api: true do context 'when authenticated as user' do it 'should remove project' do delete api("/projects/#{project.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should not remove a project if not an owner' do user3 = create(:user) project.team << [user3, :developer] delete api("/projects/#{project.id}", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should not remove a non existing project' do delete api('/projects/1328', user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not remove a project not attached to user' do delete api("/projects/#{project.id}", user2) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context 'when authenticated as admin' do it 'should remove any existing project' do delete api("/projects/#{project.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should not remove a non existing project' do delete api('/projects/1328', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 7cf4a01d76b..5890e9c9d3d 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -18,7 +18,7 @@ describe API::API, api: true do it "should return project commits" do get api("/projects/#{project.id}/repository/tree", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq('encoding') @@ -28,7 +28,7 @@ describe API::API, api: true do it 'should return a 404 for unknown ref' do get api("/projects/#{project.id}/repository/tree?ref_name=foo", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response).to be_an Object json_response['message'] == '404 Tree Not Found' @@ -38,7 +38,7 @@ describe API::API, api: true do context "unauthorized user" do it "should not return project commits" do get api("/projects/#{project.id}/repository/tree") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -46,41 +46,41 @@ describe API::API, api: true do describe "GET /projects/:id/repository/blobs/:sha" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 404 for invalid branch_name" do get api("/projects/#{project.id}/repository/blobs/invalid_branch_name?filepath=README.md", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 404 for invalid file" do get api("/projects/#{project.id}/repository/blobs/master?filepath=README.invalid", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return a 400 error if filepath is missing" do get api("/projects/#{project.id}/repository/blobs/master", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end describe "GET /projects/:id/repository/commits/:sha/blob" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end describe "GET /projects/:id/repository/raw_blobs/:sha" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return a 404 for unknown blob' do get api("/projects/#{project.id}/repository/raw_blobs/123456", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response).to be_an Object json_response['message'] == '404 Blob Not Found' @@ -91,7 +91,7 @@ describe API::API, api: true do it "should get the archive" do get api("/projects/#{project.id}/repository/archive", user) repo_name = project.repository.name.gsub("\.git", "") - expect(response.status).to eq(200) + expect(response).to have_http_status(200) type, params = workhorse_send_data expect(type).to eq('git-archive') expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/) @@ -100,7 +100,7 @@ describe API::API, api: true do it "should get the archive.zip" do get api("/projects/#{project.id}/repository/archive.zip", user) repo_name = project.repository.name.gsub("\.git", "") - expect(response.status).to eq(200) + expect(response).to have_http_status(200) type, params = workhorse_send_data expect(type).to eq('git-archive') expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/) @@ -109,7 +109,7 @@ describe API::API, api: true do it "should get the archive.tar.bz2" do get api("/projects/#{project.id}/repository/archive.tar.bz2", user) repo_name = project.repository.name.gsub("\.git", "") - expect(response.status).to eq(200) + expect(response).to have_http_status(200) type, params = workhorse_send_data expect(type).to eq('git-archive') expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/) @@ -117,28 +117,28 @@ describe API::API, api: true do it "should return 404 for invalid sha" do get api("/projects/#{project.id}/repository/archive/?sha=xxx", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe 'GET /projects/:id/repository/compare' do it "should compare branches" do get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'feature' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end it "should compare tags" do get api("/projects/#{project.id}/repository/compare", user), from: 'v1.0.0', to: 'v1.1.0' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end it "should compare commits" do get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.id, to: sample_commit.parent_id - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_empty expect(json_response['diffs']).to be_empty expect(json_response['compare_same_ref']).to be_falsey @@ -146,14 +146,14 @@ describe API::API, api: true do it "should compare commits in reverse order" do get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.parent_id, to: sample_commit.id - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end it "should compare same refs" do get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'master' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_empty expect(json_response['diffs']).to be_empty expect(json_response['compare_same_ref']).to be_truthy @@ -163,7 +163,7 @@ describe API::API, api: true do describe 'GET /projects/:id/repository/contributors' do it 'should return valid data' do get api("/projects/#{project.id}/repository/contributors", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array contributor = json_response.first expect(contributor['email']).to eq('dmitriy.zaporozhets@gmail.com') diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 73ae8ef631c..00a3c917b6a 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -39,7 +39,7 @@ describe API::Runners, api: true do get api('/runners', user) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_falsey end @@ -48,14 +48,14 @@ describe API::Runners, api: true do get api('/runners?scope=active', user) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_falsey end it 'should avoid filtering if scope is invalid' do get api('/runners?scope=unknown', user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -63,7 +63,7 @@ describe API::Runners, api: true do it 'should not return runners' do get api('/runners') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -75,7 +75,7 @@ describe API::Runners, api: true do get api('/runners/all', admin) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_truthy end @@ -85,7 +85,7 @@ describe API::Runners, api: true do it 'should not return runners list' do get api('/runners/all', user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -93,14 +93,14 @@ describe API::Runners, api: true do get api('/runners/all?scope=specific', admin) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_falsey end it 'should avoid filtering if scope is invalid' do get api('/runners?scope=unknown', admin) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -108,7 +108,7 @@ describe API::Runners, api: true do it 'should not return runners' do get api('/runners') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -119,7 +119,7 @@ describe API::Runners, api: true do it "should return runner's details" do get api("/runners/#{shared_runner.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq(shared_runner.description) end end @@ -128,7 +128,7 @@ describe API::Runners, api: true do it "should return runner's details" do get api("/runners/#{specific_runner.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq(specific_runner.description) end end @@ -136,7 +136,7 @@ describe API::Runners, api: true do it 'should return 404 if runner does not exists' do get api('/runners/9999', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -145,7 +145,7 @@ describe API::Runners, api: true do it "should return runner's details" do get api("/runners/#{specific_runner.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq(specific_runner.description) end end @@ -154,7 +154,7 @@ describe API::Runners, api: true do it "should return runner's details" do get api("/runners/#{shared_runner.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq(shared_runner.description) end end @@ -164,7 +164,7 @@ describe API::Runners, api: true do it "should not return runner's details" do get api("/runners/#{specific_runner.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -172,7 +172,7 @@ describe API::Runners, api: true do it "should not return runner's details" do get api("/runners/#{specific_runner.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -187,14 +187,16 @@ describe API::Runners, api: true do update_runner(shared_runner.id, admin, description: "#{description}_updated", active: !active, tag_list: ['ruby2.1', 'pgsql', 'mysql'], - run_untagged: 'false') + run_untagged: 'false', + locked: 'true') shared_runner.reload - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(shared_runner.description).to eq("#{description}_updated") expect(shared_runner.active).to eq(!active) expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql') - expect(shared_runner.run_untagged?).to be false + expect(shared_runner.run_untagged?).to be(false) + expect(shared_runner.locked?).to be(true) end end @@ -204,7 +206,7 @@ describe API::Runners, api: true do update_runner(specific_runner.id, admin, description: 'test') specific_runner.reload - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(specific_runner.description).to eq('test') expect(specific_runner.description).not_to eq(description) end @@ -213,7 +215,7 @@ describe API::Runners, api: true do it 'should return 404 if runner does not exists' do update_runner(9999, admin, description: 'test') - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end def update_runner(id, user, args) @@ -226,7 +228,7 @@ describe API::Runners, api: true do it 'should not update runner' do put api("/runners/#{shared_runner.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -234,7 +236,7 @@ describe API::Runners, api: true do it 'should not update runner without access to it' do put api("/runners/#{specific_runner.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should update runner with access to it' do @@ -242,7 +244,7 @@ describe API::Runners, api: true do put api("/runners/#{specific_runner.id}", admin), description: 'test' specific_runner.reload - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(specific_runner.description).to eq('test') expect(specific_runner.description).not_to eq(description) end @@ -253,7 +255,7 @@ describe API::Runners, api: true do it 'should not delete runner' do put api("/runners/#{specific_runner.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -265,7 +267,7 @@ describe API::Runners, api: true do expect do delete api("/runners/#{shared_runner.id}", admin) end.to change{ Ci::Runner.shared.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -274,21 +276,21 @@ describe API::Runners, api: true do expect do delete api("/runners/#{unused_specific_runner.id}", admin) end.to change{ Ci::Runner.specific.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should delete used runner' do expect do delete api("/runners/#{specific_runner.id}", admin) end.to change{ Ci::Runner.specific.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end it 'should return 404 if runner does not exists' do delete api('/runners/9999', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -296,26 +298,26 @@ describe API::Runners, api: true do context 'when runner is shared' do it 'should not delete runner' do delete api("/runners/#{shared_runner.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context 'when runner is not shared' do it 'should not delete runner without access to it' do delete api("/runners/#{specific_runner.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should not delete runner with more than one associated project' do delete api("/runners/#{two_projects_runner.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should delete runner for one owned project' do expect do delete api("/runners/#{specific_runner.id}", user) end.to change{ Ci::Runner.specific.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -324,7 +326,7 @@ describe API::Runners, api: true do it 'should not delete runner' do delete api("/runners/#{specific_runner.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -335,7 +337,7 @@ describe API::Runners, api: true do get api("/projects/#{project.id}/runners", user) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_truthy end @@ -345,7 +347,7 @@ describe API::Runners, api: true do it "should not return project's runners" do get api("/projects/#{project.id}/runners", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -353,35 +355,47 @@ describe API::Runners, api: true do it "should not return project's runners" do get api("/projects/#{project.id}/runners") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end describe 'POST /projects/:id/runners' do context 'authorized user' do - it 'should enable specific runner' do - specific_runner2 = create(:ci_runner).tap do |runner| + let(:specific_runner2) do + create(:ci_runner).tap do |runner| create(:ci_runner_project, runner: runner, project: project2) end + end + it 'should enable specific runner' do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id end.to change{ project.runners.count }.by(+1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'should avoid changes when enabling already enabled runner' do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id end.to change{ project.runners.count }.by(0) - expect(response.status).to eq(201) + expect(response).to have_http_status(409) + end + + it 'should not enable locked runner' do + specific_runner2.update(locked: true) + + expect do + post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id + end.to change{ project.runners.count }.by(0) + + expect(response).to have_http_status(403) end it 'should not enable shared runner' do post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end context 'user is admin' do @@ -389,7 +403,7 @@ describe API::Runners, api: true do expect do post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id end.to change{ project.runners.count }.by(+1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end end @@ -397,14 +411,14 @@ describe API::Runners, api: true do it 'should not enable runner without access to' do post api("/projects/#{project.id}/runners", user), runner_id: unused_specific_runner.id - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end it 'should raise an error when no runner_id param is provided' do post api("/projects/#{project.id}/runners", admin) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -412,7 +426,7 @@ describe API::Runners, api: true do it 'should not enable runner' do post api("/projects/#{project.id}/runners", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -420,7 +434,7 @@ describe API::Runners, api: true do it 'should not enable runner' do post api("/projects/#{project.id}/runners") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -432,7 +446,7 @@ describe API::Runners, api: true do expect do delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) end.to change{ project.runners.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -441,14 +455,14 @@ describe API::Runners, api: true do expect do delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user) end.to change{ project.runners.count }.by(0) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end it 'should return 404 is runner is not found' do delete api("/projects/#{project.id}/runners/9999", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -456,7 +470,7 @@ describe API::Runners, api: true do it "should not disable project's runner" do delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -464,7 +478,7 @@ describe API::Runners, api: true do it "should not disable project's runner" do delete api("/projects/#{project.id}/runners/#{specific_runner.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index fed9ae1949b..bf7eaaaaaed 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -14,7 +14,7 @@ describe API::API, api: true do it "should update #{service} settings" do put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return if required fields missing" do @@ -45,7 +45,7 @@ describe API::API, api: true do it "should delete #{service}" do delete api("/projects/#{project.id}/services/#{dashed_service}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project.send(service_method).reload expect(project.send(service_method).activated?).to be_falsey end @@ -64,20 +64,20 @@ describe API::API, api: true do it 'should return authentication error when unauthenticated' do get api("/projects/#{project.id}/services/#{dashed_service}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should return all properties of service #{service} when authenticated as admin" do get api("/projects/#{project.id}/services/#{dashed_service}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map) end it "should return properties of service #{service} other than passwords when authenticated as project owner" do get api("/projects/#{project.id}/services/#{dashed_service}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords) end @@ -85,7 +85,7 @@ describe API::API, api: true do project.team << [user2, :developer] get api("/projects/#{project.id}/services/#{dashed_service}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index fbd57b34a58..c15b7ff9792 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -9,7 +9,7 @@ describe API::API, api: true do context "when valid password" do it "should return private token" do post api("/session"), email: user.email, password: '12345678' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['email']).to eq(user.email) expect(json_response['private_token']).to eq(user.private_token) @@ -48,7 +48,7 @@ describe API::API, api: true do context "when invalid password" do it "should return authentication error" do post api("/session"), email: user.email, password: '123' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['email']).to be_nil expect(json_response['private_token']).to be_nil @@ -58,7 +58,7 @@ describe API::API, api: true do context "when empty password" do it "should return authentication error" do post api("/session"), email: user.email - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['email']).to be_nil expect(json_response['private_token']).to be_nil @@ -68,7 +68,7 @@ describe API::API, api: true do context "when empty name" do it "should return authentication error" do post api("/session"), password: user.password - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['email']).to be_nil expect(json_response['private_token']).to be_nil diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index c815a8e1d73..f756101c514 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -10,7 +10,7 @@ describe API::API, 'Settings', api: true do describe "GET /application/settings" do it "should return application settings" do get api("/application/settings", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) expect(json_response['signin_enabled']).to be_truthy @@ -21,7 +21,7 @@ describe API::API, 'Settings', api: true do it "should update application settings" do put api("/application/settings", admin), default_projects_limit: 3, signin_enabled: false - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) expect(json_response['signin_enabled']).to be_falsey end diff --git a/spec/requests/api/sidekiq_metrics_spec.rb b/spec/requests/api/sidekiq_metrics_spec.rb index 41cbf0c6669..28067f8ca88 100644 --- a/spec/requests/api/sidekiq_metrics_spec.rb +++ b/spec/requests/api/sidekiq_metrics_spec.rb @@ -9,28 +9,28 @@ describe API::SidekiqMetrics, api: true do it 'defines the `queue_metrics` endpoint' do get api('/sidekiq/queue_metrics', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a Hash end it 'defines the `process_metrics` endpoint' do get api('/sidekiq/process_metrics', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['processes']).to be_an Array end it 'defines the `job_stats` endpoint' do get api('/sidekiq/job_stats', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a Hash end it 'defines the `compound_metrics` endpoint' do get api('/sidekiq/compound_metrics', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a Hash expect(json_response['queues']).to be_a Hash expect(json_response['processes']).to be_an Array diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 94eebc48ec8..cf66f261ade 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -13,21 +13,21 @@ describe API::API, api: true do context "when no user" do it "should return authentication error" do get api("/hooks") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when not an admin" do it "should return forbidden error" do get api("/hooks", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context "when authenticated as admin" do it "should return an array of hooks" do get api("/hooks", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['url']).to eq(hook.url) end @@ -43,7 +43,7 @@ describe API::API, api: true do it "should respond with 400 if url not given" do post api("/hooks", admin) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should not create new hook without url" do @@ -56,13 +56,13 @@ describe API::API, api: true do describe "GET /hooks/:id" do it "should return hook by id" do get api("/hooks/#{hook.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['event_name']).to eq('project_create') end it "should return 404 on failure" do get api("/hooks/404", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -75,7 +75,7 @@ describe API::API, api: true do it "should return success if hook id not found" do delete api("/hooks/12345", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 12e170b232f..fa700ab7343 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -18,7 +18,7 @@ describe API::API, api: true do context 'without releases' do it "should return an array of project tags" do get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) end @@ -33,7 +33,7 @@ describe API::API, api: true do it "should return an array of project tags with release info" do get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) expect(json_response.first['message']).to eq('Version 1.1.0') @@ -48,14 +48,14 @@ describe API::API, api: true do it 'returns a specific tag' do get api("/projects/#{project.id}/repository/tags/#{tag_name}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(tag_name) end it 'returns 404 for an invalid tag name' do get api("/projects/#{project.id}/repository/tags/foobar", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -66,7 +66,7 @@ describe API::API, api: true do tag_name: 'v7.0.1', ref: 'master' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('v7.0.1') end end @@ -78,7 +78,7 @@ describe API::API, api: true do ref: 'master', release_description: 'Wow' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('v7.0.1') expect(json_response['release']['description']).to eq('Wow') end @@ -94,13 +94,13 @@ describe API::API, api: true do context 'delete tag' do it 'should delete an existing tag' do delete api("/projects/#{project.id}/repository/tags/#{tag_name}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['tag_name']).to eq(tag_name) end it 'should raise 404 if the tag does not exist' do delete api("/projects/#{project.id}/repository/tags/foobar", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -117,7 +117,7 @@ describe API::API, api: true do ref: 'master', message: 'Release 7.1.0' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('v7.1.0') expect(json_response['message']).to eq('Release 7.1.0') end @@ -127,14 +127,14 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags", user2), tag_name: 'v1.9.0', ref: '621491c677087aa243f165eab467bfdfbee00be1' - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should return 400 if tag name is invalid' do post api("/projects/#{project.id}/repository/tags", user), tag_name: 'v 1.0.0', ref: 'master' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Tag name invalid') end @@ -142,11 +142,11 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags", user), tag_name: 'v8.0.0', ref: 'master' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) post api("/projects/#{project.id}/repository/tags", user), tag_name: 'v8.0.0', ref: 'master' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Tag v8.0.0 already exists') end @@ -154,7 +154,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags", user), tag_name: 'mytag', ref: 'foo' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Target foo is invalid') end end @@ -167,7 +167,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: description - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['tag_name']).to eq(tag_name) expect(json_response['description']).to eq(description) end @@ -176,7 +176,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags/foobar/release", user), description: description - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('Tag does not exist') end @@ -190,7 +190,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: description - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']).to eq('Release already exists') end end @@ -211,7 +211,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: new_description - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['tag_name']).to eq(tag_name) expect(json_response['description']).to eq(new_description) end @@ -221,7 +221,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/repository/tags/foobar/release", user), description: new_description - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('Tag does not exist') end @@ -229,7 +229,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: new_description - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('Release does not exist') end end diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb new file mode 100644 index 00000000000..68d0f41b489 --- /dev/null +++ b/spec/requests/api/templates_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe API::Templates, api: true do + include ApiHelpers + + describe 'the Template Entity' do + before { get api('/gitignores/Ruby') } + + it { expect(json_response['name']).to eq('Ruby') } + it { expect(json_response['content']).to include('*.gem') } + end + + describe 'the TemplateList Entity' do + before { get api('/gitignores') } + + it { expect(json_response.first['name']).not_to be_nil } + it { expect(json_response.first['content']).to be_nil } + end + + context 'requesting gitignores' do + describe 'GET /gitignores' do + it 'returns a list of available gitignore templates' do + get api('/gitignores') + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to be > 15 + end + end + end + + context 'requesting gitlab-ci-ymls' do + describe 'GET /gitlab_ci_ymls' do + it 'returns a list of available gitlab_ci_ymls' do + get api('/gitlab_ci_ymls') + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).not_to be_nil + end + end + end + + describe 'GET /gitlab_ci_ymls/Ruby' do + it 'adds a disclaimer on the top' do + get api('/gitlab_ci_ymls/Ruby') + + expect(response).to have_http_status(200) + expect(json_response['content']).to start_with("# This file is a template,") + end + end +end diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index fdd4ec6d761..8992996c30a 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -29,17 +29,17 @@ describe API::API do context 'Handles errors' do it 'should return bad request if token is missing' do post api("/projects/#{project.id}/trigger/builds"), ref: 'master' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return not found if project is not found' do post api('/projects/0/trigger/builds'), options.merge(ref: 'master') - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should return unauthorized if token is for different project' do post api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -48,14 +48,14 @@ describe API::API do it 'should create builds' do post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master') - expect(response.status).to eq(201) + expect(response).to have_http_status(201) pipeline.builds.reload expect(pipeline.builds.size).to eq(2) end it 'should return bad request with no builds created if there\'s no commit for that ref' do post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch') - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('No builds created') end @@ -66,19 +66,19 @@ describe API::API do it 'should validate variables to be a hash' do post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master') - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('variables needs to be a hash') end it 'should validate variables needs to be a map of key-valued strings' do post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master') - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') end it 'create trigger request with variables' do post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master') - expect(response.status).to eq(201) + expect(response).to have_http_status(201) pipeline.builds.reload expect(pipeline.builds.first.trigger_request.variables).to eq(variables) end @@ -91,7 +91,7 @@ describe API::API do it 'should return list of triggers' do get api("/projects/#{project.id}/triggers", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a(Array) expect(json_response[0]).to have_key('token') end @@ -101,7 +101,7 @@ describe API::API do it 'should not return triggers list' do get api("/projects/#{project.id}/triggers", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -109,7 +109,7 @@ describe API::API do it 'should not return triggers list' do get api("/projects/#{project.id}/triggers") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -119,14 +119,14 @@ describe API::API do it 'should return trigger details' do get api("/projects/#{project.id}/triggers/#{trigger.token}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a(Hash) end it 'should respond with 404 Not Found if requesting non-existing trigger' do get api("/projects/#{project.id}/triggers/abcdef012345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -134,7 +134,7 @@ describe API::API do it 'should not return triggers list' do get api("/projects/#{project.id}/triggers/#{trigger.token}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -142,7 +142,7 @@ describe API::API do it 'should not return triggers list' do get api("/projects/#{project.id}/triggers/#{trigger.token}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -154,7 +154,7 @@ describe API::API do post api("/projects/#{project.id}/triggers", user) end.to change{project.triggers.count}.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response).to be_a(Hash) end end @@ -163,7 +163,7 @@ describe API::API do it 'should not create trigger' do post api("/projects/#{project.id}/triggers", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -171,7 +171,7 @@ describe API::API do it 'should not create trigger' do post api("/projects/#{project.id}/triggers") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -182,13 +182,13 @@ describe API::API do expect do delete api("/projects/#{project.id}/triggers/#{trigger.token}", user) end.to change{project.triggers.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should respond with 404 Not Found if requesting non-existing trigger' do delete api("/projects/#{project.id}/triggers/abcdef012345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -196,7 +196,7 @@ describe API::API do it 'should not delete trigger' do delete api("/projects/#{project.id}/triggers/#{trigger.token}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -204,7 +204,7 @@ describe API::API do it 'should not delete trigger' do delete api("/projects/#{project.id}/triggers/#{trigger.token}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index a7690f430c4..056256a29f5 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -15,7 +15,7 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/users") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -29,18 +29,18 @@ describe API::API, api: true do it "renders 403" do get api("/users") - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "renders 404" do get api("/users/#{user.id}") - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end it "should return an array of users" do get api("/users", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array username = user.username expect(json_response.detect do |user| @@ -50,7 +50,7 @@ describe API::API, api: true do it "should return one user" do get api("/users?username=#{omniauth_user.username}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['username']).to eq(omniauth_user.username) end @@ -59,7 +59,7 @@ describe API::API, api: true do context "when admin" do it "should return an array of users" do get api("/users", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first.keys).to include 'email' expect(json_response.first.keys).to include 'identities' @@ -74,24 +74,24 @@ describe API::API, api: true do describe "GET /users/:id" do it "should return a user by id" do get api("/users/#{user.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) end it "should return a 401 if unauthenticated" do get api("/users/9998") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should return a 404 error if user id not found" do get api("/users/9999", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end it "should return a 404 if invalid ID" do get api("/users/1ASDF", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -106,7 +106,7 @@ describe API::API, api: true do it "should create user with correct attributes" do post api('/users', admin), attributes_for(:user, admin: true, can_create_group: true) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) expect(new_user).not_to eq(nil) @@ -116,7 +116,7 @@ describe API::API, api: true do it "should create non-admin user" do post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) expect(new_user).not_to eq(nil) @@ -126,7 +126,7 @@ describe API::API, api: true do it "should create non-admin users by default" do post api('/users', admin), attributes_for(:user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) expect(new_user).not_to eq(nil) @@ -135,12 +135,12 @@ describe API::API, api: true do it "should return 201 Created on success" do post api("/users", admin), attributes_for(:user, projects_limit: 3) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'creates non-external users by default' do post api("/users", admin), attributes_for(:user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -150,7 +150,7 @@ describe API::API, api: true do it 'should allow an external user to be created' do post api("/users", admin), attributes_for(:user, external: true) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -163,27 +163,27 @@ describe API::API, api: true do email: 'invalid email', password: 'password', name: 'test' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if name not given' do post api('/users', admin), attributes_for(:user).except(:name) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if password not given' do post api('/users', admin), attributes_for(:user).except(:password) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if email not given' do post api('/users', admin), attributes_for(:user).except(:email) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if username not given' do post api('/users', admin), attributes_for(:user).except(:username) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if user does not validate' do @@ -194,7 +194,7 @@ describe API::API, api: true do name: 'test', bio: 'g' * 256, projects_limit: -1 - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['password']). to eq(['is too short (minimum is 8 characters)']) expect(json_response['message']['bio']). @@ -207,7 +207,7 @@ describe API::API, api: true do it "shouldn't available for non admin users" do post api("/users", user), attributes_for(:user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end context 'with existing user' do @@ -227,7 +227,7 @@ describe API::API, api: true do password: 'password', username: 'foo' end.to change { User.count }.by(0) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']).to eq('Email has already been taken') end @@ -239,7 +239,7 @@ describe API::API, api: true do password: 'password', username: 'test' end.to change { User.count }.by(0) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']).to eq('Username has already been taken') end end @@ -249,7 +249,7 @@ describe API::API, api: true do it "should redirect to sign in page" do get "/users/sign_up" - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(response).to redirect_to(new_user_session_path) end end @@ -261,41 +261,41 @@ describe API::API, api: true do it "should update user with new bio" do put api("/users/#{user.id}", admin), { bio: 'new test bio' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['bio']).to eq('new test bio') expect(user.reload.bio).to eq('new test bio') end it 'should update user with his own email' do put api("/users/#{user.id}", admin), email: user.email - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['email']).to eq(user.email) expect(user.reload.email).to eq(user.email) end it 'should update user with his own username' do put api("/users/#{user.id}", admin), username: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) expect(user.reload.username).to eq(user.username) end it "should update user's existing identity" do put api("/users/#{omniauth_user.id}", admin), provider: 'ldapmain', extern_uid: '654321' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321') end it 'should update user with new identity' do put api("/users/#{user.id}", admin), provider: 'github', extern_uid: '67890' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(user.reload.identities.first.extern_uid).to eq('67890') expect(user.reload.identities.first.provider).to eq('github') end it "should update admin status" do put api("/users/#{user.id}", admin), { admin: true } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['is_admin']).to eq(true) expect(user.reload.admin).to eq(true) end @@ -309,7 +309,7 @@ describe API::API, api: true do it "should not update admin status" do put api("/users/#{admin_user.id}", admin), { can_create_group: false } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['is_admin']).to eq(true) expect(admin_user.reload.admin).to eq(true) expect(admin_user.can_create_group).to eq(false) @@ -317,18 +317,18 @@ describe API::API, api: true do it "should not allow invalid update" do put api("/users/#{user.id}", admin), { email: 'invalid email' } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(user.reload.email).not_to eq('invalid email') end it "shouldn't available for non admin users" do put api("/users/#{user.id}", user), attributes_for(:user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return 404 for non-existing user" do put api("/users/999999", admin), { bio: 'update should fail' } - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -344,7 +344,7 @@ describe API::API, api: true do name: 'test', bio: 'g' * 256, projects_limit: -1 - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['password']). to eq(['is too short (minimum is 8 characters)']) expect(json_response['message']['bio']). @@ -364,14 +364,14 @@ describe API::API, api: true do it 'should return 409 conflict error if email address exists' do put api("/users/#{@user.id}", admin), email: 'test@example.com' - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(@user.reload.email).to eq(@user.email) end it 'should return 409 conflict error if username taken' do @user_id = User.all.last.id put api("/users/#{@user.id}", admin), username: 'test' - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(@user.reload.username).to eq(@user.username) end end @@ -382,13 +382,13 @@ describe API::API, api: true do it "should not create invalid ssh key" do post api("/users/#{user.id}/keys", admin), { title: "invalid key" } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "key" not given') end it 'should not create key without title' do post api("/users/#{user.id}/keys", admin), key: 'some key' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "title" not given') end @@ -401,7 +401,7 @@ describe API::API, api: true do it "should return 405 for invalid ID" do post api("/users/ASDF/keys", admin) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end end @@ -411,14 +411,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api("/users/#{user.id}/keys") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return 404 for non-existing user' do get api('/users/999999/keys', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -426,14 +426,14 @@ describe API::API, api: true do user.keys << key user.save get api("/users/#{user.id}/keys", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(key.title) end it "should return 405 for invalid ID" do get api("/users/ASDF/keys", admin) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end end end @@ -444,7 +444,7 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do delete api("/users/#{user.id}/keys/42") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -455,20 +455,20 @@ describe API::API, api: true do expect do delete api("/users/#{user.id}/keys/#{key.id}", admin) end.to change { user.keys.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return 404 error if user not found' do user.keys << key user.save delete api("/users/999999/keys/#{key.id}", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'should return 404 error if key not foud' do delete api("/users/#{user.id}/keys/42", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Key Not Found') end end @@ -479,7 +479,7 @@ describe API::API, api: true do it "should not create invalid email" do post api("/users/#{user.id}/emails", admin), {} - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "email" not given') end @@ -492,7 +492,7 @@ describe API::API, api: true do it "should raise error for invalid ID" do post api("/users/ASDF/emails", admin) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end end @@ -502,14 +502,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api("/users/#{user.id}/emails") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return 404 for non-existing user' do get api('/users/999999/emails', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -517,14 +517,14 @@ describe API::API, api: true do user.emails << email user.save get api("/users/#{user.id}/emails", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['email']).to eq(email.email) end it "should raise error for invalid ID" do put api("/users/ASDF/emails", admin) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end end end @@ -535,7 +535,7 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do delete api("/users/#{user.id}/emails/42") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -546,20 +546,20 @@ describe API::API, api: true do expect do delete api("/users/#{user.id}/emails/#{email.id}", admin) end.to change { user.emails.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return 404 error if user not found' do user.emails << email user.save delete api("/users/999999/emails/#{email.id}", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'should return 404 error if email not foud' do delete api("/users/#{user.id}/emails/42", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Email Not Found') end @@ -574,24 +574,24 @@ describe API::API, api: true do it "should delete user" do delete api("/users/#{user.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound expect(json_response['email']).to eq(user.email) end it "should not delete for unauthenticated user" do delete api("/users/#{user.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "shouldn't available for non admin users" do delete api("/users/#{user.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return 404 for non-existing user" do delete api("/users/999999", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -603,7 +603,7 @@ describe API::API, api: true do describe "GET /user" do it "should return current user" do get api("/user", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['email']).to eq(user.email) expect(json_response['is_admin']).to eq(user.is_admin?) expect(json_response['can_create_project']).to eq(user.can_create_project?) @@ -613,7 +613,7 @@ describe API::API, api: true do it "should return 401 error if user is unauthenticated" do get api("/user") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -621,7 +621,7 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/user/keys") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -630,7 +630,7 @@ describe API::API, api: true do user.keys << key user.save get api("/user/keys", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first["title"]).to eq(key.title) end @@ -642,13 +642,13 @@ describe API::API, api: true do user.keys << key user.save get api("/user/keys/#{key.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["title"]).to eq(key.title) end it "should return 404 Not Found within invalid ID" do get api("/user/keys/42", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -657,13 +657,13 @@ describe API::API, api: true do user.save admin get api("/user/keys/#{key.id}", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end it "should return 404 for invalid ID" do get api("/users/keys/ASDF", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -673,29 +673,29 @@ describe API::API, api: true do expect do post api("/user/keys", user), key_attrs end.to change{ user.keys.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it "should return a 401 error if unauthorized" do post api("/user/keys"), title: 'some title', key: 'some key' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should not create ssh key without key" do post api("/user/keys", user), title: 'title' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "key" not given') end it 'should not create ssh key without title' do post api('/user/keys', user), key: 'some key' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "title" not given') end it "should not create ssh key without title" do post api("/user/keys", user), key: "somekey" - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -706,19 +706,19 @@ describe API::API, api: true do expect do delete api("/user/keys/#{key.id}", user) end.to change{user.keys.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return success if key ID not found" do delete api("/user/keys/42", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 401 error if unauthorized" do user.keys << key user.save delete api("/user/keys/#{key.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should raise error for invalid ID" do @@ -730,7 +730,7 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/user/emails") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -739,7 +739,7 @@ describe API::API, api: true do user.emails << email user.save get api("/user/emails", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first["email"]).to eq(email.email) end @@ -751,13 +751,13 @@ describe API::API, api: true do user.emails << email user.save get api("/user/emails/#{email.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["email"]).to eq(email.email) end it "should return 404 Not Found within invalid ID" do get api("/user/emails/42", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -766,13 +766,13 @@ describe API::API, api: true do user.save admin get api("/user/emails/#{email.id}", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end it "should return 404 for invalid ID" do get api("/users/emails/ASDF", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -782,17 +782,17 @@ describe API::API, api: true do expect do post api("/user/emails", user), email_attrs end.to change{ user.emails.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it "should return a 401 error if unauthorized" do post api("/user/emails"), email: 'some email' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should not create email with invalid email" do post api("/user/emails", user), {} - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "email" not given') end end @@ -804,19 +804,19 @@ describe API::API, api: true do expect do delete api("/user/emails/#{email.id}", user) end.to change{user.emails.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return success if email ID not found" do delete api("/user/emails/42", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 401 error if unauthorized" do user.emails << email user.save delete api("/user/emails/#{email.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should raise error for invalid ID" do @@ -828,25 +828,25 @@ describe API::API, api: true do before { admin } it 'should block existing user' do put api("/users/#{user.id}/block", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(user.reload.state).to eq('blocked') end it 'should not re-block ldap blocked users' do put api("/users/#{ldap_blocked_user.id}/block", admin) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') end it 'should not be available for non admin users' do put api("/users/#{user.id}/block", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(user.reload.state).to eq('active') end it 'should return a 404 error if user id not found' do put api('/users/9999/block', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end end @@ -857,31 +857,31 @@ describe API::API, api: true do it 'should unblock existing user' do put api("/users/#{user.id}/unblock", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(user.reload.state).to eq('active') end it 'should unblock a blocked user' do put api("/users/#{blocked_user.id}/unblock", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(blocked_user.reload.state).to eq('active') end it 'should not unblock ldap blocked users' do put api("/users/#{ldap_blocked_user.id}/unblock", admin) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') end it 'should not be available for non admin users' do put api("/users/#{user.id}/unblock", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(user.reload.state).to eq('active') end it 'should return a 404 error if user id not found' do put api('/users/9999/block', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index b1e1053d037..ddba18245f8 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -15,7 +15,7 @@ describe API::API, api: true do it 'should return project variables' do get api("/projects/#{project.id}/variables", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a(Array) end end @@ -24,7 +24,7 @@ describe API::API, api: true do it 'should not return project variables' do get api("/projects/#{project.id}/variables", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -32,7 +32,7 @@ describe API::API, api: true do it 'should not return project variables' do get api("/projects/#{project.id}/variables") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -42,14 +42,14 @@ describe API::API, api: true do it 'should return project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['value']).to eq(variable.value) end it 'should respond with 404 Not Found if requesting non-existing variable' do get api("/projects/#{project.id}/variables/non_existing_variable", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -57,7 +57,7 @@ describe API::API, api: true do it 'should not return project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -65,7 +65,7 @@ describe API::API, api: true do it 'should not return project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -77,7 +77,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' end.to change{project.variables.count}.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') end @@ -87,7 +87,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2' end.to change{project.variables.count}.by(0) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -95,7 +95,7 @@ describe API::API, api: true do it 'should not create variable' do post api("/projects/#{project.id}/variables", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -103,7 +103,7 @@ describe API::API, api: true do it 'should not create variable' do post api("/projects/#{project.id}/variables") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -118,7 +118,7 @@ describe API::API, api: true do updated_variable = project.variables.first - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(value_before).to eq(variable.value) expect(updated_variable.value).to eq('VALUE_1_UP') end @@ -126,7 +126,7 @@ describe API::API, api: true do it 'should responde with 404 Not Found if requesting non-existing variable' do put api("/projects/#{project.id}/variables/non_existing_variable", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -134,7 +134,7 @@ describe API::API, api: true do it 'should not update variable' do put api("/projects/#{project.id}/variables/#{variable.key}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -142,7 +142,7 @@ describe API::API, api: true do it 'should not update variable' do put api("/projects/#{project.id}/variables/#{variable.key}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -153,13 +153,13 @@ describe API::API, api: true do expect do delete api("/projects/#{project.id}/variables/#{variable.key}", user) end.to change{project.variables.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should responde with 404 Not Found if requesting non-existing variable' do delete api("/projects/#{project.id}/variables/non_existing_variable", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -167,7 +167,7 @@ describe API::API, api: true do it 'should not delete variable' do delete api("/projects/#{project.id}/variables/#{variable.key}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -175,7 +175,7 @@ describe API::API, api: true do it 'should not delete variable' do delete api("/projects/#{project.id}/variables/#{variable.key}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 7e50bea90d1..1bc51783c3a 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -26,7 +26,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['sha']).to eq(build.sha) expect(runner.reload.platform).to eq("darwin") end @@ -34,7 +34,7 @@ describe Ci::API::API do it "should return 404 error if no pending build found" do post ci_api("/builds/register"), token: runner.token - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 404 error if no builds for specific runner" do @@ -43,7 +43,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 404 error if no builds for shared runner" do @@ -52,7 +52,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: shared_runner.token - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "returns options" do @@ -61,7 +61,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] }) end @@ -72,7 +72,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["variables"]).to eq([ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, @@ -91,7 +91,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["variables"]).to eq([ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, @@ -109,7 +109,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["depends_on_builds"].count).to eq(2) expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec") end @@ -122,7 +122,7 @@ describe Ci::API::API do it do post ci_api("/builds/register"), token: runner.token, info: { param => value } - expect(response.status).to eq(404) + expect(response).to have_http_status(404) runner.reload is_expected.to eq(value) end @@ -172,7 +172,7 @@ describe Ci::API::API do end it "should update a running build" do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should not override trace information when no trace is given' do @@ -252,13 +252,13 @@ describe Ci::API::API do context "should authorize posting artifact to running build" do it "using token as parameter" do post authorize_url, { token: build.token }, headers - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["TempPath"]).not_to be_nil end it "using token as header" do post authorize_url, {}, headers_with_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["TempPath"]).not_to be_nil end end @@ -267,13 +267,13 @@ describe Ci::API::API do it "using token as parameter" do stub_application_setting(max_artifacts_size: 0) post authorize_url, { token: build.token, filesize: 100 }, headers - expect(response.status).to eq(413) + expect(response).to have_http_status(413) end it "using token as header" do stub_application_setting(max_artifacts_size: 0) post authorize_url, { filesize: 100 }, headers_with_token - expect(response.status).to eq(413) + expect(response).to have_http_status(413) end end @@ -281,7 +281,7 @@ describe Ci::API::API do before { post authorize_url, { token: 'invalid', filesize: 100 } } it 'should respond with forbidden' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -305,20 +305,20 @@ describe Ci::API::API do context "should post artifact to running build" do it "uses regual file post" do upload_artifacts(file_upload, headers_with_token, false) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) end it "uses accelerated file post" do upload_artifacts(file_upload, headers_with_token, true) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) end it "updates artifact" do upload_artifacts(file_upload, headers_with_token) upload_artifacts(file_upload2, headers_with_token) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["artifacts_file"]["filename"]).to eq(file_upload2.original_filename) end end @@ -343,7 +343,7 @@ describe Ci::API::API do end it 'stores artifacts and artifacts metadata' do - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) end @@ -355,7 +355,7 @@ describe Ci::API::API do end it 'is expected to respond with bad request' do - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'does not store metadata' do @@ -382,7 +382,7 @@ describe Ci::API::API do it 'updates when specified' do build.reload - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['artifacts_expire_at']).not_to be_empty expect(build.artifacts_expire_at).to be_within(5.minutes).of(Time.now + 7.days) end @@ -393,7 +393,7 @@ describe Ci::API::API do it 'ignores if not specified' do build.reload - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['artifacts_expire_at']).to be_nil expect(build.artifacts_expire_at).to be_nil end @@ -404,21 +404,21 @@ describe Ci::API::API do it "should fail to post too large artifact" do stub_application_setting(max_artifacts_size: 0) upload_artifacts(file_upload, headers_with_token) - expect(response.status).to eq(413) + expect(response).to have_http_status(413) end end context "artifacts post request does not contain file" do it "should fail to post artifacts without file" do post post_url, {}, headers_with_token - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end context 'GitLab Workhorse is not configured' do it "should fail to post artifacts without GitLab-Workhorse" do post post_url, { token: build.token }, {} - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -437,7 +437,7 @@ describe Ci::API::API do it "should fail to post artifacts for outside of tmp path" do upload_artifacts(file_upload, headers_with_token) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -458,7 +458,7 @@ describe Ci::API::API do before { delete delete_url, token: build.token } it 'should remove build artifacts' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(build.artifacts_file.exists?).to be_falsy expect(build.artifacts_metadata.exists?).to be_falsy end @@ -475,14 +475,14 @@ describe Ci::API::API do end it 'should download artifact' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.headers).to include download_headers end end context 'build does not has artifacts' do it 'should respond with not found' do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 72f6a3c981d..f12678e5a8e 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -21,17 +21,17 @@ describe Ci::API::API do context 'Handles errors' do it 'should return bad request if token is missing' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger") - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return not found if project is not found' do post ci_api('/projects/0/refs/master/trigger'), options - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should return unauthorized if token is for different project' do post ci_api("/projects/#{project2.ci_id}/refs/master/trigger"), options - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -40,14 +40,14 @@ describe Ci::API::API do it 'should create builds' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options - expect(response.status).to eq(201) + expect(response).to have_http_status(201) pipeline.builds.reload expect(pipeline.builds.size).to eq(2) end it 'should return bad request with no builds created if there\'s no commit for that ref' do post ci_api("/projects/#{project.ci_id}/refs/other-branch/trigger"), options - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('No builds created') end @@ -58,19 +58,19 @@ describe Ci::API::API do it 'should validate variables to be a hash' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: 'value') - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('variables needs to be a hash') end it 'should validate variables needs to be a map of key-valued strings' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: { key: %w(1 2) }) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') end it 'create trigger request with variables' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: variables) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) pipeline.builds.reload expect(pipeline.builds.first.trigger_request.variables).to eq(variables) end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index fd26ca97818..bae56334be4 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -14,7 +14,7 @@ describe 'Git HTTP requests', lib: true do context "when no authentication is provided" do it "responds with status 401 (no project existence information leak)" do download('doesnt/exist.git') do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -23,7 +23,7 @@ describe 'Git HTTP requests', lib: true do context "when authentication fails" do it "responds with status 401" do download('doesnt/exist.git', user: user.username, password: "nope") do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -31,7 +31,7 @@ describe 'Git HTTP requests', lib: true do context "when authentication succeeds" do it "responds with status 404" do download('/doesnt/exist.git', user: user.username, password: user.password) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -46,7 +46,7 @@ describe 'Git HTTP requests', lib: true do download("/#{wiki.repository.path_with_namespace}.git") do |response| json_body = ActiveSupport::JSON.decode(response.body) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) end end @@ -62,13 +62,13 @@ describe 'Git HTTP requests', lib: true do it "downloads get status 200" do download(path, {}) do |response| - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end it "uploads get status 401" do upload(path, {}) do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -77,7 +77,7 @@ describe 'Git HTTP requests', lib: true do it "uploads get status 200 (because Git hooks do the real check)" do upload(path, env) do |response| - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -86,7 +86,7 @@ describe 'Git HTTP requests', lib: true do allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false) upload(path, env) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -97,7 +97,7 @@ describe 'Git HTTP requests', lib: true do allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) download(path, {}) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -111,13 +111,13 @@ describe 'Git HTTP requests', lib: true do context "when no authentication is provided" do it "responds with status 401 to downloads" do download(path, {}) do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end it "responds with status 401 to uploads" do upload(path, {}) do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -128,7 +128,7 @@ describe 'Git HTTP requests', lib: true do context "when authentication fails" do it "responds with status 401" do download(path, env) do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -139,7 +139,7 @@ describe 'Git HTTP requests', lib: true do clone_get(path, env) - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -158,7 +158,7 @@ describe 'Git HTTP requests', lib: true do project.team << [user, :master] download(path, env) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -169,12 +169,12 @@ describe 'Git HTTP requests', lib: true do clone_get(path, env) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "uploads get status 200" do upload(path, env) do |response| - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -188,13 +188,13 @@ describe 'Git HTTP requests', lib: true do it "downloads get status 200" do clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "uploads get status 401 (no project existence information leak)" do push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -232,13 +232,13 @@ describe 'Git HTTP requests', lib: true do context "when the user doesn't have access to the project" do it "downloads get status 404" do download(path, user: user.username, password: user.password) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end it "uploads get status 200 (because Git hooks do the real check)" do upload(path, user: user.username, password: user.password) do |response| - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -256,13 +256,13 @@ describe 'Git HTTP requests', lib: true do it "downloads get status 200" do clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "uploads get status 401 (no project existence information leak)" do push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -336,7 +336,7 @@ describe 'Git HTTP requests', lib: true do end it "returns the file" do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -344,7 +344,7 @@ describe 'Git HTTP requests', lib: true do before { get "/#{project.path_with_namespace}/blob/master/info/refs" } it "returns not found" do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index d2d4a9eca18..c6172b9cc7d 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -11,12 +11,12 @@ describe JwtController do context 'existing service' do subject! { get '/jwt/auth', parameters } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } context 'returning custom http code' do let(:service) { double(execute: { http_status: 505 }) } - it { expect(response.status).to eq(505) } + it { expect(response).to have_http_status(505) } end end @@ -36,7 +36,7 @@ describe JwtController do context 'project with disabled CI' do let(:builds_enabled) { false } - it { expect(response.status).to eq(403) } + it { expect(response).to have_http_status(403) } end end @@ -56,14 +56,14 @@ describe JwtController do subject! { get '/jwt/auth', parameters, headers } - it { expect(response.status).to eq(403) } + it { expect(response).to have_http_status(403) } end end context 'unknown service' do subject! { get '/jwt/auth', service: 'unknown' } - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end def credentials(login, password) diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb index deab242f45a..309213bd44c 100644 --- a/spec/services/create_commit_builds_service_spec.rb +++ b/spec/services/create_commit_builds_service_spec.rb @@ -83,6 +83,9 @@ describe CreateCommitBuildsService, services: true do context 'when commit contains a [ci skip] directive' do let(:message) { "some message[ci skip]" } + let(:messageFlip) { "some message[skip ci]" } + let(:capMessage) { "some message[CI SKIP]" } + let(:capMessageFlip) { "some message[SKIP CI]" } before do allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message } @@ -96,12 +99,55 @@ describe CreateCommitBuildsService, services: true do after: '31das312', commits: commits ) + + expect(pipeline).to be_persisted + expect(pipeline.builds.any?).to be false + expect(pipeline.status).to eq("skipped") + end + + it "skips builds creation if there is [skip ci] tag in commit message" do + commits = [{ message: messageFlip }] + pipeline = service.execute(project, user, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits + ) + + expect(pipeline).to be_persisted + expect(pipeline.builds.any?).to be false + expect(pipeline.status).to eq("skipped") + end + + it "skips builds creation if there is [CI SKIP] tag in commit message" do + commits = [{ message: capMessage }] + pipeline = service.execute(project, user, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits + ) + + expect(pipeline).to be_persisted + expect(pipeline.builds.any?).to be false + expect(pipeline.status).to eq("skipped") + end + + it "skips builds creation if there is [SKIP CI] tag in commit message" do + commits = [{ message: capMessageFlip }] + pipeline = service.execute(project, user, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits + ) + expect(pipeline).to be_persisted expect(pipeline.builds.any?).to be false expect(pipeline.status).to eq("skipped") end - it "does not skips builds creation if there is no [ci skip] tag in commit message" do + it "does not skips builds creation if there is no [ci skip] or [skip ci] tag in commit message" do allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { "some message" } commits = [{ message: "some message" }] diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb new file mode 100644 index 00000000000..2395445e7fd --- /dev/null +++ b/spec/services/members/destroy_service_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Members::DestroyService, services: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let!(:member) { create(:project_member, source: project) } + + context 'when member is nil' do + before do + project.team << [user, :developer] + end + + it 'does not destroy the member' do + expect { destroy_member(nil, user) }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + + context 'when current user cannot destroy the given member' do + before do + project.team << [user, :developer] + end + + it 'does not destroy the member' do + expect { destroy_member(member, user) }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + + context 'when current user can destroy the given member' do + before do + project.team << [user, :master] + end + + it 'destroys the member' do + destroy_member(member, user) + + expect(member).to be_destroyed + end + + context 'when the given member is a requester' do + before do + member.update_column(:requested_at, Time.now) + end + + it 'calls Member#after_decline_request' do + expect_any_instance_of(NotificationService).to receive(:decline_access_request).with(member) + + destroy_member(member, user) + end + + context 'when current user is the member' do + it 'does not call Member#after_decline_request' do + expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member) + + destroy_member(member, member.user) + end + end + + context 'when current user is the member and ' do + it 'does not call Member#after_decline_request' do + expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member) + + destroy_member(member, member.user) + end + end + end + end + + def destroy_member(member, user) + Members::DestroyService.new(member, user).execute + end +end diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb new file mode 100644 index 00000000000..14f3301d9f4 --- /dev/null +++ b/spec/services/search/snippet_service_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Search::SnippetService, services: true do + let(:author) { create(:author) } + let(:project) { create(:empty_project) } + + let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') } + let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') } + let!(:private_snippet) { create(:snippet, :private, content: 'password: XXX', author: author) } + + let!(:project_public_snippet) { create(:snippet, :public, project: project, content: 'password: XXX') } + let!(:project_internal_snippet) { create(:snippet, :internal, project: project, content: 'password: XXX') } + let!(:project_private_snippet) { create(:snippet, :private, project: project, content: 'password: XXX') } + + describe '#execute' do + context 'unauthenticated' do + it 'returns public snippets only' do + search = described_class.new(nil, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, project_public_snippet] + end + end + + context 'authenticated' do + it 'returns only public & internal snippets for regular users' do + user = create(:user) + search = described_class.new(user, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet] + end + + it 'returns public, internal snippets and project private snippets for project members' do + member = create(:user) + project.team << [member, :developer] + search = described_class.new(member, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] + end + + it 'returns public, internal and private snippets where user is the author' do + search = described_class.new(author, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet] + end + + it 'returns all snippets when user is admin' do + admin = create(:admin) + search = described_class.new(admin, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] + end + end + end +end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 498bd4bf800..426bf53f198 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -17,6 +17,7 @@ module TestEnv "'test'" => 'e56497b', 'orphaned-branch' => '45127a9', 'binary-encoding' => '7b1cf43', + 'gitattributes' => '5a62481', } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb index 1abd87d7d33..b5e1fdb8ded 100644 --- a/spec/workers/merge_worker_spec.rb +++ b/spec/workers/merge_worker_spec.rb @@ -9,7 +9,7 @@ describe MergeWorker do before do source_project.team << [author, :master] - source_project.repository.expire_branch_names + source_project.repository.expire_branches_cache end it 'clears cache of source repo after removing source branch' do diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore index a8368751267..f6b286cea98 100644 --- a/vendor/gitignore/Android.gitignore +++ b/vendor/gitignore/Android.gitignore @@ -2,7 +2,7 @@ *.apk *.ap_ -# Files for the Dalvik VM +# Files for the ART/Dalvik VM *.dex # Java class files @@ -34,6 +34,7 @@ captures/ # Intellij *.iml +.idea/workspace.xml # Keystore files *.jks diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore index b8bd0267bdf..4581ef2eeef 100644 --- a/vendor/gitignore/C++.gitignore +++ b/vendor/gitignore/C++.gitignore @@ -15,6 +15,7 @@ # Fortran module files *.mod +*.smod # Compiled Static libraries *.lai diff --git a/vendor/gitignore/CMake.gitignore b/vendor/gitignore/CMake.gitignore index b558e9afa6d..0cc7e4b5275 100644 --- a/vendor/gitignore/CMake.gitignore +++ b/vendor/gitignore/CMake.gitignore @@ -4,3 +4,4 @@ CMakeScripts Makefile cmake_install.cmake install_manifest.txt +CTestTestfile.cmake diff --git a/vendor/gitignore/D.gitignore b/vendor/gitignore/D.gitignore index b4433f8a512..74b926fc901 100644 --- a/vendor/gitignore/D.gitignore +++ b/vendor/gitignore/D.gitignore @@ -18,3 +18,7 @@ .dub docs.json __dummy.html +docs/ + +# Code coverage +*.lst diff --git a/vendor/gitignore/Global/Bazaar.gitignore b/vendor/gitignore/Global/Bazaar.gitignore new file mode 100644 index 00000000000..3cbbcbd11ec --- /dev/null +++ b/vendor/gitignore/Global/Bazaar.gitignore @@ -0,0 +1,2 @@ +.bzr/ +.bzrignore diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/OSX.gitignore index 660b31353e8..5972fe50f66 100644 --- a/vendor/gitignore/Global/OSX.gitignore +++ b/vendor/gitignore/Global/OSX.gitignore @@ -1,4 +1,4 @@ -.DS_Store +*.DS_Store .AppleDouble .LSOverride @@ -15,6 +15,7 @@ Icon .TemporaryItems .Trashes .VolumeIcon.icns +.com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB diff --git a/vendor/gitignore/Global/README.md b/vendor/gitignore/Global/README.md new file mode 100644 index 00000000000..06b6649bd9a --- /dev/null +++ b/vendor/gitignore/Global/README.md @@ -0,0 +1,10 @@ +## Globally Useful gitignores + +This directory contains globally useful gitignores, +e.g. OS-specific and editor specific. + +For more on global gitignores: +<https://help.github.com/articles/ignoring-files/#create-a-global-gitignore> + +And a good blog post about 'em: +<http://augustl.com/blog/2009/global_gitignores> diff --git a/vendor/gitignore/Global/SublimeText.gitignore b/vendor/gitignore/Global/SublimeText.gitignore index 1d4e6137591..69c8c2b29ce 100644 --- a/vendor/gitignore/Global/SublimeText.gitignore +++ b/vendor/gitignore/Global/SublimeText.gitignore @@ -12,3 +12,16 @@ # sftp configuration file sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore index 096abdd90b3..a4ee41ab62b 100644 --- a/vendor/gitignore/Haskell.gitignore +++ b/vendor/gitignore/Haskell.gitignore @@ -16,3 +16,4 @@ cabal.sandbox.config *.hp *.eventlog .stack-work/ +cabal.project.local diff --git a/vendor/gitignore/Julia.gitignore b/vendor/gitignore/Julia.gitignore new file mode 100644 index 00000000000..381e0b6d252 --- /dev/null +++ b/vendor/gitignore/Julia.gitignore @@ -0,0 +1,4 @@ +*.jl.cov +*.jl.*.cov +*.jl.mem +deps/deps.jl diff --git a/vendor/gitignore/LICENSE b/vendor/gitignore/LICENSE new file mode 100644 index 00000000000..b8a103ac9b1 --- /dev/null +++ b/vendor/gitignore/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 GitHub, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/vendor/gitignore/Laravel.gitignore b/vendor/gitignore/Laravel.gitignore index c491fa2bc6f..1cd717b6921 100644 --- a/vendor/gitignore/Laravel.gitignore +++ b/vendor/gitignore/Laravel.gitignore @@ -7,7 +7,6 @@ app/storage/ # Laravel 5 & Lumen specific bootstrap/cache/ -storage/ .env.*.php .env.php .env diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore index 3020bc327a7..86f21d8e0ff 100644 --- a/vendor/gitignore/Objective-C.gitignore +++ b/vendor/gitignore/Objective-C.gitignore @@ -24,6 +24,8 @@ xcuserdata/ ## Obj-C/Swift specific *.hmap *.ipa +*.dSYM.zip +*.dSYM # CocoaPods # @@ -49,3 +51,10 @@ Carthage/Build fastlane/report.xml fastlane/screenshots + +#Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore index fa24b2efee8..c7659c24f38 100644 --- a/vendor/gitignore/Qt.gitignore +++ b/vendor/gitignore/Qt.gitignore @@ -34,5 +34,5 @@ Makefile* *.qmlproject.user.* # QtCtreator CMake -CMakeLists.txt.user +CMakeLists.txt.user* diff --git a/vendor/gitignore/README.md b/vendor/gitignore/README.md deleted file mode 100644 index 43131e815cc..00000000000 --- a/vendor/gitignore/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# .gitignore templates - -This directory contains language-specific .gitignore templates that are used by GitLab. - -These files were automatically pulled from [this repository](https://github.com/github/gitignore). -Please submit pull requests to that repository. There is no need to edit the files in this directory. - -## Bulk Update - -To update this directory with the latest changes in the repository, run: - -```sh -bundle exec rake gitlab:update_gitignore -``` diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore index 2121e0a8038..d8c256c1925 100644 --- a/vendor/gitignore/Rails.gitignore +++ b/vendor/gitignore/Rails.gitignore @@ -16,6 +16,10 @@ pickle-email-*.html config/initializers/secret_token.rb config/secrets.yml +# dotenv +# TODO Comment out this rule if environment variables can be committed +.env + ## Environment normalization: /.bundle /vendor/bundle diff --git a/vendor/gitignore/Swift.gitignore b/vendor/gitignore/Swift.gitignore index 8a29fa52af4..2c22487b5e3 100644 --- a/vendor/gitignore/Swift.gitignore +++ b/vendor/gitignore/Swift.gitignore @@ -24,6 +24,8 @@ xcuserdata/ ## Obj-C/Swift specific *.hmap *.ipa +*.dSYM.zip +*.dSYM ## Playgrounds timeline.xctimeline diff --git a/vendor/gitignore/UnrealEngine.gitignore b/vendor/gitignore/UnrealEngine.gitignore index 75b1186b0af..be0e4913c3a 100644 --- a/vendor/gitignore/UnrealEngine.gitignore +++ b/vendor/gitignore/UnrealEngine.gitignore @@ -37,6 +37,7 @@ *.suo *.opensdf *.sdf +*.VC.db *.VC.opendb # Precompiled Assets diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index f1e3d20e056..67acbf42f5e 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -42,6 +42,7 @@ dlldata.c # DNX project.lock.json +project.fragment.lock.json artifacts/ *_i.c diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml new file mode 100644 index 00000000000..396d3f1b042 --- /dev/null +++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml @@ -0,0 +1,7 @@ +# Official docker image. +image: docker:latest + +build: + stage: build + script: + - docker build -t test . diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml new file mode 100644 index 00000000000..0b329aaf1c4 --- /dev/null +++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml @@ -0,0 +1,18 @@ +# This template uses the non default language docker image +# The image already has Hex installed. You might want to consider to use `elixir:latest` +image: trenpixster/elixir:latest + +# Pic zero or more services to be used on all builds. +# Only needed when using a docker container to run your tests in. +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +services: + - mysql:latest + - redis:latest + - postgres:latest + +before_script: + - mix deps.get + +mix: + script: + - mix test diff --git a/vendor/gitlab-ci-yml/LICENSE b/vendor/gitlab-ci-yml/LICENSE new file mode 100644 index 00000000000..80f7b87b6c0 --- /dev/null +++ b/vendor/gitlab-ci-yml/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 GitLab.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml new file mode 100644 index 00000000000..e5bce3503f3 --- /dev/null +++ b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml @@ -0,0 +1,27 @@ +# Official framework image. Look for the different tagged releases at: +# https://hub.docker.com/r/library/node/tags/ +image: node:latest + +# Pick zero or more services to be used on all builds. +# Only needed when using a docker container to run your tests in. +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +services: + - mysql:latest + - redis:latest + - postgres:latest + +# This folder is cached between builds +# http://docs.gitlab.com/ce/ci/yaml/README.html#cache +cache: + paths: + - node_modules/ + +test_async: + script: + - npm install + - node ./specs/start.js ./specs/async.spec.js + +test_db: + script: + - npm install + - node ./specs/start.js ./specs/db-postgres.spec.js diff --git a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml new file mode 100644 index 00000000000..7fcc0b436b5 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml @@ -0,0 +1,16 @@ +# Full project: https://gitlab.com/pages/brunch +image: node:4.2.2 + +pages: + cache: + paths: + - node_modules/ + + script: + - npm install -g brunch + - brunch build --production + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml new file mode 100644 index 00000000000..791afdd23f1 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml @@ -0,0 +1,13 @@ +# Full project: https://gitlab.com/pages/doxygen +image: alpine + +pages: + script: + - apk update && apk add doxygen + - doxygen doxygen/Doxyfile + - mv doxygen/documentation/html/ public/ + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml new file mode 100644 index 00000000000..dd3ef149668 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml @@ -0,0 +1,16 @@ +# Full project: https://gitlab.com/pages/harp +image: node:4.2.2 + +pages: + cache: + paths: + - node_modules + + script: + - npm install -g harp + - harp compile ./ public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml new file mode 100644 index 00000000000..b468d79bcad --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml @@ -0,0 +1,25 @@ +# Full project: https://gitlab.com/pages/hexo +image: python:2.7 + +cache: + paths: + - vendor/ + +test: + stage: test + script: + - pip install hyde + - hyde gen + except: + - master + +pages: + stage: deploy + script: + - pip install hyde + - hyde gen -d public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml new file mode 100644 index 00000000000..249a168aa33 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml @@ -0,0 +1,12 @@ +# Full project: https://gitlab.com/pages/plain-html +pages: + stage: deploy + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml new file mode 100644 index 00000000000..45df6975259 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml @@ -0,0 +1,11 @@ +# Full project: https://gitlab.com/pages/hugo +image: publysher/hugo + +pages: + script: + - hugo + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml new file mode 100644 index 00000000000..f5b40f2b9f1 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml @@ -0,0 +1,25 @@ +# Full project: https://gitlab.com/pages/hyde +image: python:2.7 + +cache: + paths: + - vendor/ + +test: + stage: test + script: + - pip install hyde + - hyde gen + except: + - master + +pages: + stage: deploy + script: + - pip install hyde + - hyde gen -d public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml new file mode 100644 index 00000000000..36918fc005a --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml @@ -0,0 +1,24 @@ +# Full project: https://gitlab.com/pages/jekyll +image: ruby:2.3 + +test: + stage: test + script: + - gem install jekyll + - jekyll build -d test + artifacts: + paths: + - test + except: + - master + +pages: + stage: deploy + script: + - gem install jekyll + - jekyll build -d public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml new file mode 100644 index 00000000000..c5c44a5d86c --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml @@ -0,0 +1,12 @@ +# Full project: https://gitlab.com/pages/hyde +image: python:2.7 + +pages: + script: + - pip install lektor + - lektor build --output-path public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml new file mode 100644 index 00000000000..50e8b7ccd46 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml @@ -0,0 +1,17 @@ +# Full project: https://gitlab.com/pages/metalsmith +image: node:4.2.2 + +pages: + cache: + paths: + - node_modules/ + + script: + - npm install -g metalsmith + - npm install + - make build + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml new file mode 100644 index 00000000000..9f4cc0574d6 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml @@ -0,0 +1,27 @@ +# Full project: https://gitlab.com/pages/middleman +image: ruby:2.3 + +cache: + paths: + - vendor + +test: + script: + - apt-get update -yqqq + - apt-get install -y nodejs + - bundle install --path vendor + - bundle exec middleman build + except: + - master + +pages: + script: + - apt-get update -yqqq + - apt-get install -y nodejs + - bundle install --path vendor + - bundle exec middleman build + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml new file mode 100644 index 00000000000..b469b316ba5 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml @@ -0,0 +1,12 @@ +# Full project: https://gitlab.com/pages/nanoc +image: ruby:2.3 + +pages: + script: + - bundle install -j4 + - nanoc + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml new file mode 100644 index 00000000000..4762ec9acfd --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml @@ -0,0 +1,15 @@ +# Full project: https://gitlab.com/pages/octopress +image: ruby:2.3 + +pages: + script: + - apt-get update -qq && apt-get install -qq nodejs + - bundle install -j4 + - bundle exec rake generate + - mv public .public + - mv .public/octopress public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml new file mode 100644 index 00000000000..c5f3154f587 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml @@ -0,0 +1,10 @@ +# Full project: https://gitlab.com/pages/pelican +image: python:2.7-alpine + +pages: + script: + - pip install -r requirements.txt + - pelican -s publishconf.py + artifacts: + paths: + - public/ diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml new file mode 100644 index 00000000000..78f3e39949f --- /dev/null +++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml @@ -0,0 +1,30 @@ +# Official language image. Look for the different tagged releases at: +# https://hub.docker.com/r/library/ruby/tags/ +image: "ruby:2.3" + +# Pick zero or more services to be used on all builds. +# Only needed when using a docker container to run your tests in. +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +services: + - mysql:latest + - redis:latest + - postgres:latest + +# This is a basic example for a gem or script which doesn't use +# services such as redis or postgres +before_script: + - gem install bundler # Bundler is not installed with the image + - bundle install -j $(nproc) # Install dependencies + +rubocop: + script: + - rubocop + +rspec: + script: + - rspec spec + +rails: + script: + - rake db:migrate + - rspec spec |