diff options
120 files changed, 1525 insertions, 1666 deletions
diff --git a/CHANGELOG b/CHANGELOG index e1145f36ce3..bc09a51cc1e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,35 +1,40 @@ Please view this file on the master branch, on stable branches it's out of date. + v 8.11.0 (unreleased) - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - Limit git rev-list output count to one in forced push check + - Retrieve rendered HTML from cache in one request v 8.10.0 (unreleased) - Fix profile activity heatmap to show correct day name (eanplatter) - Speed up ExternalWikiHelper#get_project_wiki_path - Expose {should,force}_remove_source_branch (Ben Boeckel) - - Add the functionality to be able to rename a file. !5049 (tiagonbotelho) + - Add the functionality to be able to rename a file. !5049 - Disable PostgreSQL statement timeout during migrations - - Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho) + - Fix projects dropdown loading performance with a simplified api cal. !5113 - Fix commit builds API, return all builds for all pipelines for given commit. !4849 - Replace Haml with Hamlit to make view rendering faster. !3666 - Refresh the branch cache after `git gc` runs - Allow to disable request access button on projects/groups - Refactor repository paths handling to allow multiple git mount points - - Optimize system note visibility checking by memoizing the visible reference count !5070 + - Optimize system note visibility checking by memoizing the visible reference count. !5070 - Add Application Setting to configure default Repository Path for new projects - Delete award emoji when deleting a user - - Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell) - - Add link to profile to commit avatar !5163 (winniehell) + - Remove pinTo from Flash and make inline flash messages look nicer. !4854 (winniehell) + - Add an API for downloading latest successful build from a particular branch or tag. !5347 + - Avoid data-integrity issue when cleaning up repository archive cache. + - Add link to profile to commit avatar. !5163 (winniehell) - Wrap code blocks on Activies and Todos page. !4783 (winniehell) - - Align flash messages with left side of page content !4959 (winniehell) - - Display tooltip for "Copy to Clipboard" button !5164 (winniehell) - - Use default cursor for table header of project files !5165 (winniehell) + - Align flash messages with left side of page content. !4959 (winniehell) + - Display tooltip for "Copy to Clipboard" button. !5164 (winniehell) + - Use default cursor for table header of project files. !5165 (winniehell) - Store when and yaml variables in builds table - - Display last commit of deleted branch in push events !4699 (winniehell) - - Escape file extension when parsing search results !5141 (winniehell) + - Display last commit of deleted branch in push events. !4699 (winniehell) + - Escape file extension when parsing search results. !5141 (winniehell) - Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004 + - Add image border in Markdown preview. !5162 (winniehell) - Apply the trusted_proxies config to the rack request object for use with rack_attack - - Added the ability to block sign ups using a domain blacklist !5259 + - Added the ability to block sign ups using a domain blacklist. !5259 - Upgrade to Rails 4.2.7. !5236 - Extend exposed environment variables for CI builds - Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead @@ -37,35 +42,36 @@ v 8.10.0 (unreleased) - Allow to pull code with deploy key from public projects - Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts) - Add Sidekiq queue duration to transaction metrics. - - Add a new column `artifacts_size` to table `ci_builds` !4964 + - Add a new column `artifacts_size` to table `ci_builds`. !4964 - Let Workhorse serve format-patch diffs - - Display tooltip for mentioned users and groups !5261 (winniehell) + - Display tooltip for mentioned users and groups. !5261 (winniehell) - Allow build email service to be tested - Added day name to contribution calendar tooltips - Refactor user authorization check for a single project to avoid querying all user projects - - Make images fit to the size of the viewport !4810 - - Fix check for New Branch button on Issue page !4630 (winniehell) + - Make images fit to the size of the viewport. !4810 + - Fix check for New Branch button on Issue page. !4630 (winniehell) - Fix GFM autocomplete not working on wiki pages + - Fixed enter key not triggering click on first row when searching in a dropdown - Fix MR-auto-close text added to description. !4836 - Support U2F devices in Firefox. !5177 - - Fix issue, preventing users w/o push access to sort tags !5105 (redetection) + - Fix issue, preventing users w/o push access to sort tags. !5105 (redetection) - Add Spring EmojiOne updates. - - Added Rake task for tracking deployments !5320 + - Added Rake task for tracking deployments. !5320 - Fix fetching LFS objects for private CI projects - Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237 - - Add syntax for multiline blockquote using `>>>` fence !3954 + - Add syntax for multiline blockquote using `>>>` fence. !3954 - Fix viewing notification settings when a project is pending deletion - Updated compare dropdown menus to use GL dropdown - Redirects back to issue after clicking login link - Eager load award emoji on notes - Allow to define manual actions/builds on Pipelines and Environments - Fix pagination when sorting by columns with lots of ties (like priority) - - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020 + - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020 - Updated project header design - Issuable collapsed assignee tooltip is now the users name - Fix compare view not changing code view rendering style - Exclude email check from the standard health check - - Updated layout for Projects, Groups, Users on Admin area !4424 + - Updated layout for Projects, Groups, Users on Admin area. !4424 - Fix changing issue state columns in milestone view - Update health_check gem to version 2.1.0 - Add notification settings dropdown for groups @@ -73,22 +79,24 @@ v 8.10.0 (unreleased) - Wildcards for protected branches. !4665 - Allow importing from Github using Personal Access Tokens. (Eric K Idema) - API: Expose `due_date` for issues (Robert Schilling) - - API: Todos !3188 (Robert Schilling) - - API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling) - - API: Expose `developers_can_push` and `developers_can_merge` for branches !5208 (Robert Schilling) + - API: Todos. !3188 (Robert Schilling) + - API: Expose shared groups for projects and shared projects for groups. !5050 (Robert Schilling) + - API: Expose `developers_can_push` and `developers_can_merge` for branches. !5208 (Robert Schilling) + - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects - Add "Enabled Git access protocols" to Application Settings - Diffs will create button/diff form on demand no on server side - Reduce size of HTML used by diff comment forms - Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard) - - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) + - Fix user creation with stronger minimum password requirements. !4054 (nathan-pmt) - Only show New Snippet button to users that can create snippets. - PipelinesFinder uses git cache data - Track a user who created a pipeline - Actually render old and new sections of parallel diff next to each other - Throttle the update of `project.pushes_since_gc` to 1 minute. - - Allow expanding and collapsing files in diff view (!4990) + - Allow expanding and collapsing files in diff view. !4990 - Collapse large diffs by default (!4990) - Fix mentioned users list on diff notes + - Add support for inline videos in GitLab Flavored Markdown. !5215 (original implementation by Eric Hayes) - Fix creation of deployment on build that is retried, redeployed or rollback - Don't parse Rinku returned value to DocFragment when it didn't change the original html string. - Check for conflicts with existing Project's wiki path when creating a new project. @@ -101,8 +109,9 @@ v 8.10.0 (unreleased) - ObjectRenderer retrieve renderer content using Rails.cache.read_multi - Better caching of git calls on ProjectsController#show. - Avoid to retrieve MR closes_issues as much as possible. - - Add API endpoint for a group issues !4520 (mahcsig) - - Add Bugzilla integration !4930 (iamtjg) + - Hide project name in project activities. !5068 (winniehell) + - Add API endpoint for a group issues. !4520 (mahcsig) + - Add Bugzilla integration. !4930 (iamtjg) - Fix new snippet style bug (elliotec) - Instrument Rinku usage - Be explicit to define merge request discussion variables @@ -124,10 +133,10 @@ v 8.10.0 (unreleased) - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests - Add date when user joined the team on the member page - Fix 404 redirect after validation fails importing a GitLab project - - Added setting to set new users by default as external !4545 (Dravere) - - Add min value for project limit field on user's form !3622 (jastkand) + - Added setting to set new users by default as external. !4545 (Dravere) + - Add min value for project limit field on user's form. !3622 (jastkand) - Reset project pushes_since_gc when we enqueue the git gc call - - Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt) + - Add reminder to not paste private SSH keys. !4399 (Ingo Blechschmidt) - Collapsed diffs lines/size don't acumulate to overflow diffs. - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel) - Style of import project buttons were fixed in the new project page. !5183 (rdemirbay) @@ -141,7 +150,7 @@ v 8.10.0 (unreleased) - Fix last update timestamp on issues not preserved on gitlab.com and project imports - Fix issues importing projects from EE to CE - Fix creating group with space in group path - - Improve cron_jobs loading error messages !5318 / !5360 + - Improve cron_jobs loading error messages. !5318 / !5360 - Prevent toggling sidebar when clipboard icon clicked - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) - Limit the number of retries on error to 3 for exporting projects @@ -153,6 +162,7 @@ v 8.10.0 (unreleased) - Add builds badge to Pipelines settings page - Export and import avatar as part of project import/export - Fix migration corrupting import data for old version upgrades + - Show tooltip on GitLab export link in new project page v 8.9.6 - Fix importing of events under notes for GitLab projects. !5154 @@ -255,6 +265,7 @@ v 8.9.1 - 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 + - Change Retry to Re-deploy on Deployments page - 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. @@ -52,7 +52,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.3.2' +gem 'gitlab_git', '~> 10.4.1' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes @@ -347,8 +347,5 @@ gem 'paranoia', '~> 2.0' gem 'health_check', '~> 2.1.0' # System information -gem 'vmstat', '~> 2.1.0' +gem 'vmstat', '~> 2.1.1' gem 'sys-filesystem', '~> 1.1.6' - -# Secure headers for Content Security Policy -gem 'secure_headers', '~> 3.3' diff --git a/Gemfile.lock b/Gemfile.lock index 363904a4baa..195516d1bf1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -274,7 +274,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.3.2) + gitlab_git (10.4.1) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -645,8 +645,6 @@ GEM sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) - secure_headers (3.3.2) - useragent seed-fu (2.3.6) activerecord (>= 3.1) activesupport (>= 3.1) @@ -769,7 +767,6 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.9.0) - useragent (0.16.7) uuid (2.3.8) macaddr (~> 1.0) version_sorter (2.0.0) @@ -778,7 +775,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) - vmstat (2.1.0) + vmstat (2.1.1) warden (1.2.6) rack (>= 1.0) web-console (2.3.0) @@ -864,7 +861,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.3.2) + gitlab_git (~> 10.4.1) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) @@ -947,7 +944,6 @@ DEPENDENCIES sass-rails (~> 5.0.0) scss_lint (~> 0.47.0) sdoc (~> 0.3.20) - secure_headers (~> 3.3) seed-fu (~> 2.3.5) select2-rails (~> 3.5.9) sentry-raven (~> 1.1.0) @@ -984,7 +980,7 @@ DEPENDENCIES unicorn-worker-killer (~> 0.4.2) version_sorter (~> 2.0.0) virtus (~> 1.0.1) - vmstat (~> 2.1.0) + vmstat (~> 2.1.1) web-console (~> 2.0) webmock (~> 1.21.0) wikicloth (= 0.8.1) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 951530e03a5..7086ece29b8 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -210,9 +210,22 @@ class GitLabDropdown data: => return @fullData callback: (data) => - currentIndex = -1 @parseData data + unless @filterInput.val() is '' + selector = '.dropdown-content li:not(.divider):visible' + + if @dropdown.find('.dropdown-toggle-page').length + selector = ".dropdown-page-one #{selector}" + + $(selector, @dropdown) + .first() + .find('a') + .addClass('is-focused') + + currentIndex = 0 + + # Event listeners @dropdown.on "shown.bs.dropdown", @opened diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 0ea54faae1a..d4de712f88c 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -162,7 +162,7 @@ class @Notes @last_fetched_at = data.last_fetched_at @setPollingInterval(data.notes.length) $.each notes, (i, note) => - if note.discussion_with_diff_html? + if note.discussion_html? @renderDiscussionNote(note) else @renderNote(note) @@ -251,7 +251,7 @@ class @Notes discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']") if discussionContainer.length is 0 # insert the note and the reply button after the temp row - row.after note.discussion_html + row.after note.diff_discussion_html # remove the note (will be added again below) row.next().find(".note").remove() @@ -265,7 +265,7 @@ class @Notes # Init discussion on 'Discussion' page if it is merge request page if $('body').attr('data-page').indexOf('projects:merge_request') is 0 $('ul.main-notes-list') - .append(note.discussion_with_diff_html) + .append(note.discussion_html) .syntaxHighlight() else # append new note to all matching discussions diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 5d3273ea64d..96565da1bc9 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -98,13 +98,30 @@ .md { &.md-preview-holder { - code { - white-space: pre-wrap; - word-break: keep-all; - } - + // 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; + word-break: keep-all; + } + + hr { + // Darken 'whitesmoke' a bit to make it more visible in note bodies + border-color: darken(#f5f5f5, 8%); + margin: 10px 0; + } + + // Border around images in issue and MR comments. + img:not(.emoji) { + border: 1px solid $table-border-gray; + padding: 5px; + margin: 5px 0; + // Ensure that image does not exceed viewport + max-height: calc(100vh - 100px); + } } .toolbar-group { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 99a2cd306cf..e26f8f7080d 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -53,6 +53,14 @@ left: 70px; } } + + .nav-links { + svg { + position: relative; + top: 2px; + margin-right: 3px; + } + } } .build-header { diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 35ab28b3fea..bbe0c6c5f1f 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -68,6 +68,12 @@ } } +.ci-status-link { + svg { + overflow: visible; + } +} + .commit-box { border-top: 1px solid $border-color; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index a2145956eb5..5c336bb1c7e 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -176,3 +176,11 @@ } } } + +// hide event scope (namespace + project) where it is not necessary +.project-activity { + .event-scope { + display: none; + } +} + diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index ded437625ee..7a50bc9c832 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -269,7 +269,7 @@ .issuable-header-btn { background: $gray-normal; border: 1px solid $border-gray-normal; - + &:hover { background: $gray-dark; border: 1px solid $border-gray-dark; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 4e806e60e7e..db295935b00 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -64,6 +64,7 @@ margin-right: 4px; position: relative; top: 1px; + overflow: visible; } &.ci-success { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index ac8c02b59dc..a2b5437e503 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -91,34 +91,11 @@ ul.notes { // 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; - } - ul.task-list { ul:not(.task-list) { padding-left: 1.3em; } } - - hr { - // Darken 'whitesmoke' a bit to make it more visible in note bodies - border-color: darken(#f5f5f5, 8%); - margin: 10px 0; - } - - 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/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a404f108dc4..c58e2ffe7f5 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -29,9 +29,18 @@ } } +.pipeline-holder { + width: 100%; + overflow: auto; +} + .table.builds { min-width: 1200px; + &.pipeline { + min-width: 650px; + } + tr { th { padding: 16px 8px; @@ -76,7 +85,7 @@ svg { height: 14px; - width: auto; + width: 14px; vertical-align: middle; fill: $table-text-gray; } @@ -93,7 +102,7 @@ .commit-title { margin-top: 4px; - max-width: 320px; + max-width: 300px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -138,6 +147,11 @@ height: 18px; width: 18px; vertical-align: middle; + overflow: visible; + } + + .light { + width: 3px; } } @@ -153,7 +167,7 @@ svg { width: 12px; - height: auto; + height: 12px; vertical-align: middle; margin-right: 4px; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 99f64b5e4cc..587f2d9f3c1 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -49,6 +49,7 @@ position: relative; top: 1px; margin: 0 3px; + overflow: visible; } } @@ -74,3 +75,11 @@ color: $gl-gray; } } + +.visible-xs-inline { + .ci-status-link { + position: relative; + top: 2px; + left: 5px; + } +} diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index d3dd98c8a4e..f7b44099b78 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -30,7 +30,7 @@ class HelpController < ApplicationController end # Allow access to images in the doc folder - format.any(:png, :gif, :jpeg) do + format.any(:png, :gif, :jpeg, :mp4) do # Note: We are purposefully NOT using `Rails.root.join` path = File.join(Rails.root, 'doc', "#{@path}.#{params[:format]}") diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 727e84b40a1..7ae034f9398 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -115,11 +115,11 @@ class Projects::CommitController < Projects::ApplicationController end def define_note_vars - @grouped_diff_notes = commit.notes.grouped_diff_notes + @grouped_diff_discussions = commit.notes.grouped_diff_discussions @notes = commit.notes.non_diff_notes.fresh Banzai::NoteRenderer.render( - @grouped_diff_notes.values.flatten + @notes, + @grouped_diff_discussions.values.flat_map(&:notes) + @notes, @project, current_user, ) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 10749d0fef8..8c004724f02 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -54,7 +54,7 @@ class Projects::CompareController < Projects::ApplicationController ) @diff_notes_disabled = true - @grouped_diff_notes = {} + @grouped_diff_discussions = {} end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 7beeb7d97d0..594a61464b9 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -97,7 +97,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController else build_merge_request @diff_notes_disabled = true - @grouped_diff_notes = {} + @grouped_diff_discussions = {} end define_commit_vars @@ -378,7 +378,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController # This is not executed lazily @notes = Banzai::NoteRenderer.render( - @discussions.flatten, + @discussions.flat_map(&:notes), @project, current_user, @path, @@ -404,10 +404,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController } @use_legacy_diff_notes = !@merge_request.support_new_diff_notes? - @grouped_diff_notes = @merge_request.notes.grouped_diff_notes + @grouped_diff_discussions = @merge_request.notes.grouped_diff_discussions Banzai::NoteRenderer.render( - @grouped_diff_notes.values.flatten, + @grouped_diff_discussions.values.flat_map(&:notes), @project, current_user, @path, diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 3eacdbbd067..766b7e9cf22 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -73,7 +73,7 @@ class Projects::NotesController < Projects::ApplicationController end alias_method :awardable, :note - def note_to_html(note) + def note_html(note) render_to_string( "projects/notes/_note", layout: false, @@ -82,20 +82,20 @@ class Projects::NotesController < Projects::ApplicationController ) end - def note_to_discussion_html(note) - return unless note.diff_note? + def diff_discussion_html(discussion) + return unless discussion.diff_discussion? if params[:view] == 'parallel' - template = "projects/notes/_diff_notes_with_reply_parallel" + template = "discussions/_parallel_diff_discussion" locals = if params[:line_type] == 'old' - { notes_left: [note], notes_right: [] } + { discussion_left: discussion, discussion_right: nil } else - { notes_left: [], notes_right: [note] } + { discussion_left: nil, discussion_right: discussion } end else - template = "projects/notes/_diff_notes_with_reply" - locals = { notes: [note] } + template = "discussions/_diff_discussion" + locals = { discussion: discussion } end render_to_string( @@ -106,14 +106,14 @@ class Projects::NotesController < Projects::ApplicationController ) end - def note_to_discussion_with_diff_html(note) - return unless note.diff_note? + def discussion_html(discussion) + return unless discussion.diff_discussion? render_to_string( - "projects/notes/_discussion", + "discussions/_discussion", layout: false, formats: [:html], - locals: { discussion_notes: [note] } + locals: { discussion: discussion } ) end @@ -132,26 +132,33 @@ class Projects::NotesController < Projects::ApplicationController valid: true, id: note.id, discussion_id: note.discussion_id, - html: note_to_html(note), + html: note_html(note), award: false, - note: note.note, - discussion_html: note_to_discussion_html(note), - discussion_with_diff_html: note_to_discussion_with_diff_html(note) + note: note.note } - # The discussion_id is used to add the comment to the correct discussion - # element on the merge request page. Among other things, the discussion_id - # contains the sha of head commit of the merge request. - # When new commits are pushed into the merge request after the initial - # load of the merge request page, the discussion elements will still have - # the old discussion_ids, with the old head commit sha. The new comment, - # however, will have the new discussion_id with the new commit sha. - # To ensure that these new comments will still end up in the correct - # discussion element, we also send the original discussion_id, with the - # old commit sha, along, and fall back on this value when no discussion - # element with the new discussion_id could be found. - if note.new_diff_note? && note.position != note.original_position - attrs[:original_discussion_id] = note.original_discussion_id + if note.diff_note? + discussion = Discussion.new([note]) + + attrs.merge!( + diff_discussion_html: diff_discussion_html(discussion), + discussion_html: discussion_html(discussion) + ) + + # The discussion_id is used to add the comment to the correct discussion + # element on the merge request page. Among other things, the discussion_id + # contains the sha of head commit of the merge request. + # When new commits are pushed into the merge request after the initial + # load of the merge request page, the discussion elements will still have + # the old discussion_ids, with the old head commit sha. The new comment, + # however, will have the new discussion_id with the new commit sha. + # To ensure that these new comments will still end up in the correct + # discussion element, we also send the original discussion_id, with the + # old commit sha, along, and fall back on this value when no discussion + # element with the new discussion_id could be found. + if note.new_diff_note? && note.position != note.original_position + attrs[:original_discussion_id] = note.original_discussion_id + end end attrs diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index caed064dfbc..e617be8f9fb 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -1,6 +1,6 @@ class Projects::UploadsController < Projects::ApplicationController skip_before_action :reject_blocked!, :project, - :repository, if: -> { action_name == 'show' && image? } + :repository, if: -> { action_name == 'show' && image_or_video? } before_action :authorize_upload_file!, only: [:create] @@ -24,7 +24,7 @@ class Projects::UploadsController < Projects::ApplicationController def show return render_404 if uploader.nil? || !uploader.file.exists? - disposition = uploader.image? ? 'inline' : 'attachment' + disposition = uploader.image_or_video? ? 'inline' : 'attachment' send_file uploader.file.path, disposition: disposition end @@ -49,7 +49,7 @@ class Projects::UploadsController < Projects::ApplicationController @uploader end - def image? - uploader && uploader.file.exists? && uploader.image? + def image_or_video? + uploader && uploader.file.exists? && uploader.image_or_video? end end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index ffac28f78a0..ea2f5f9281a 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -45,10 +45,10 @@ module CiStatusHelper custom_icon(icon_name) end - def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '') + def render_commit_status(commit, tooltip_placement: 'auto left') project = commit.project path = builds_namespace_project_commit_path(project.namespace, project, commit) - render_status_with_link('commit', commit.status, path, tooltip_placement, cssclass: cssclass) + render_status_with_link('commit', commit.status, path, tooltip_placement) end def render_pipeline_status(pipeline, tooltip_placement: 'auto left') diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 75b029365f9..4c031942793 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -54,18 +54,20 @@ module DiffHelper end end - def organize_comments(left, right) - notes_left = notes_right = nil + def parallel_diff_discussions(left, right, diff_file) + discussion_left = discussion_right = nil - unless left[:type].nil? && right[:type] == 'new' - notes_left = @grouped_diff_notes[left[:line_code]] + if left && (left.unchanged? || left.removed?) + line_code = diff_file.line_code(left) + discussion_left = @grouped_diff_discussions[line_code] end - unless left[:type].nil? && right[:type].nil? - notes_right = @grouped_diff_notes[right[:line_code]] + if right && right.added? + line_code = diff_file.line_code(right) + discussion_right = @grouped_diff_discussions[line_code] end - [notes_left, notes_right] + [discussion_left, discussion_right] end def inline_diff_btn diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 98143dcee9b..0f60dd828ab 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -1,9 +1,4 @@ module NotesHelper - # Helps to distinguish e.g. commit notes in mr notes list - def note_for_main_target?(note) - @noteable.class.name == note.noteable_type && !note.diff_note? - end - def note_target_fields(note) if note.noteable hidden_field_tag(:target_type, note.noteable.class.name.underscore) + @@ -44,8 +39,8 @@ module NotesHelper # If we didn't, diff notes that would show for the same line on the changes # tab, would show in different discussions on the discussion tab. use_legacy_diff_note ||= begin - line_diff_notes = @grouped_diff_notes[line_code] - line_diff_notes && line_diff_notes.any?(&:legacy_diff_note?) + discussion = @grouped_diff_discussions[line_code] + discussion && discussion.legacy_diff_discussion? end data = { @@ -81,22 +76,10 @@ module NotesHelper data end - def link_to_reply_discussion(note, line_type = nil) + def link_to_reply_discussion(discussion, line_type = nil) return unless current_user - data = { - noteable_type: note.noteable_type, - noteable_id: note.noteable_id, - commit_id: note.commit_id, - discussion_id: note.discussion_id, - line_type: line_type - } - - if note.diff_note? - data[:note_type] = note.type - - data.merge!(note.diff_attributes) - end + data = discussion.reply_attributes.merge(line_type: line_type) content_tag(:div, class: "discussion-reply-holder") do button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', @@ -114,13 +97,13 @@ module NotesHelper @max_access_by_user_id[full_key] end - def diff_note_path(note) - return unless note.diff_note? + def discussion_diff_path(discussion) + return unless discussion.diff_discussion? - if note.for_merge_request? && note.active? - diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) - elsif note.for_commit? - namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) + if discussion.for_merge_request? && discussion.active? + diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code) + elsif discussion.for_commit? + namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code) end end end diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index 1926f03af07..790001222f1 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -1,5 +1,6 @@ module TimeHelper def time_interval_in_words(interval_in_seconds) + interval_in_seconds = interval_in_seconds.to_i minutes = interval_in_seconds / 60 seconds = interval_in_seconds - minutes * 60 diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 58f25b20acf..cbfa14e81f1 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -12,7 +12,7 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } - scope :with_artifacts, ->() { where.not(artifacts_file: nil) } + scope :with_artifacts, ->() { where.not(artifacts_file: [nil, '']) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :manual_actions, ->() { where(when: :manual) } diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 6d3bbb03484..bce6a992af6 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -20,6 +20,11 @@ module Ci after_touch :update_state after_save :keep_around_commits + # ref can't be HEAD or SHA, can only be branch/tag name + scope :latest_successful_for, ->(ref = default_branch) do + where(ref: ref).success.order(id: :desc).limit(1) + end + def self.truncate_sha(sha) sha[0...8] end @@ -237,7 +242,7 @@ module Ci def keep_around_commits return unless project - + project.repository.keep_around(self.sha) project.repository.keep_around(self.before_sha) end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 535db26240a..2d185c28809 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -16,7 +16,11 @@ class CommitStatus < ActiveRecord::Base alias_attribute :author, :user - scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } + scope :latest, -> do + max_id = unscope(:select).select("max(#{quoted_table_name}.id)") + + where(id: max_id.group(:name, :commit_id)) + end scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb index 2785fbb21c9..4be6a2f621b 100644 --- a/app/models/concerns/note_on_diff.rb +++ b/app/models/concerns/note_on_diff.rb @@ -1,12 +1,6 @@ module NoteOnDiff extend ActiveSupport::Concern - NUMBER_OF_TRUNCATED_DIFF_LINES = 16 - - included do - delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true - end - def diff_note? true end @@ -30,23 +24,4 @@ module NoteOnDiff def can_be_award_emoji? false end - - # Returns an array of at most 16 highlighted lines above a diff note - def truncated_diff_lines - prev_lines = [] - - highlighted_diff_lines.each do |line| - if line.meta? - prev_lines.clear - else - prev_lines << line - - break if for_line?(line) - - prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES - end - end - - prev_lines - end end diff --git a/app/models/discussion.rb b/app/models/discussion.rb new file mode 100644 index 00000000000..74facfd1c9c --- /dev/null +++ b/app/models/discussion.rb @@ -0,0 +1,91 @@ +class Discussion + NUMBER_OF_TRUNCATED_DIFF_LINES = 16 + + attr_reader :first_note, :notes + + delegate :created_at, + :project, + :author, + + :noteable, + :for_commit?, + :for_merge_request?, + + :line_code, + :diff_file, + :for_line?, + :active?, + + to: :first_note + + delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true + + def self.for_notes(notes) + notes.group_by(&:discussion_id).values.map { |notes| new(notes) } + end + + def self.for_diff_notes(notes) + notes.group_by(&:line_code).values.map { |notes| new(notes) } + end + + def initialize(notes) + @first_note = notes.first + @notes = notes + end + + def id + first_note.discussion_id + end + + def diff_discussion? + first_note.diff_note? + end + + def legacy_diff_discussion? + notes.any?(&:legacy_diff_note?) + end + + def for_target?(target) + self.noteable == target && !diff_discussion? + end + + def expanded? + !diff_discussion? || active? + end + + def reply_attributes + data = { + noteable_type: first_note.noteable_type, + noteable_id: first_note.noteable_id, + commit_id: first_note.commit_id, + discussion_id: self.id, + } + + if diff_discussion? + data[:note_type] = first_note.type + + data.merge!(first_note.diff_attributes) + end + + data + end + + # Returns an array of at most 16 highlighted lines above a diff note + def truncated_diff_lines + prev_lines = [] + + highlighted_diff_lines.each do |line| + if line.meta? + prev_lines.clear + else + prev_lines << line + + break if for_line?(line) + + prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES + end + end + + prev_lines + end +end diff --git a/app/models/note.rb b/app/models/note.rb index 0ce10c77de9..9b0a7211b4e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -82,11 +82,12 @@ class Note < ActiveRecord::Base end def discussions - all.group_by(&:discussion_id).values + Discussion.for_notes(all) end - def grouped_diff_notes - diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code) + def grouped_diff_discussions + notes = diff_notes.fresh.select(&:active?) + Discussion.for_diff_notes(notes).map { |d| [d.line_code, d] }.to_h end # Searches for notes matching the given query. diff --git a/app/models/project.rb b/app/models/project.rb index d6191e80e62..f09d915f20e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -429,6 +429,17 @@ class Project < ActiveRecord::Base repository.commit(ref) end + # ref can't be HEAD, can only be branch/tag name or SHA + def latest_successful_builds_for(ref = default_branch) + pipeline = pipelines.latest_successful_for(ref).to_sql + join_sql = "INNER JOIN (#{pipeline}) pipelines" + + " ON pipelines.id = #{Ci::Build.quoted_table_name}.commit_id" + builds.joins(join_sql).latest.with_artifacts + # TODO: Whenever we dropped support for MySQL, we could change to: + # pipeline = pipelines.latest_successful_for(ref) + # builds.where(pipeline: pipeline).latest.with_artifacts + end + def merge_base_commit(first_commit_id, second_commit_id) sha = repository.merge_base(first_commit_id, second_commit_id) repository.commit(sha) if sha diff --git a/app/models/repository.rb b/app/models/repository.rb index 511df2d67c6..46a04eb80cd 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -11,16 +11,6 @@ class Repository attr_accessor :path_with_namespace, :project - def self.clean_old_archives - Gitlab::Metrics.measure(:clean_old_archives) do - repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path - - return unless File.directory?(repository_downloads_path) - - Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) - end - end - def initialize(path_with_namespace, project) @path_with_namespace = path_with_namespace @project = project @@ -80,7 +70,12 @@ class Repository def commit(ref = 'HEAD') return nil unless exists? - commit = Gitlab::Git::Commit.find(raw_repository, ref) + commit = + if ref.is_a?(Gitlab::Git::Commit) + ref + else + Gitlab::Git::Commit.find(raw_repository, ref) + end commit = ::Commit.new(commit, @project) if commit commit rescue Rugged::OdbError @@ -257,10 +252,10 @@ class Repository # Rugged seems to throw a `ReferenceError` when given branch_names rather # than SHA-1 hashes number_commits_behind = raw_repository. - count_commits_between(branch.target, root_ref_hash) + count_commits_between(branch.target.sha, root_ref_hash) number_commits_ahead = raw_repository. - count_commits_between(root_ref_hash, branch.target) + count_commits_between(root_ref_hash, branch.target.sha) { behind: number_commits_behind, ahead: number_commits_ahead } end @@ -684,9 +679,7 @@ class Repository end def local_branches - @local_branches ||= rugged.branches.each(:local).map do |branch| - Gitlab::Git::Branch.new(branch.name, branch.target) - end + @local_branches ||= raw_repository.local_branches end alias_method :branches, :local_branches @@ -827,7 +820,7 @@ class Repository end def revert(user, commit, base_branch, revert_tree_id = nil) - source_sha = find_branch(base_branch).target + source_sha = find_branch(base_branch).target.sha revert_tree_id ||= check_revert_content(commit, base_branch) return false unless revert_tree_id @@ -844,7 +837,7 @@ class Repository end def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil) - source_sha = find_branch(base_branch).target + source_sha = find_branch(base_branch).target.sha cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) return false unless cherry_pick_tree_id @@ -865,7 +858,7 @@ class Repository end def check_revert_content(commit, base_branch) - source_sha = find_branch(base_branch).target + source_sha = find_branch(base_branch).target.sha args = [commit.id, source_sha] args << { mainline: 1 } if commit.merge_commit? @@ -879,7 +872,7 @@ class Repository end def check_cherry_pick_content(commit, base_branch) - source_sha = find_branch(base_branch).target + source_sha = find_branch(base_branch).target.sha args = [commit.id, source_sha] args << 1 if commit.merge_commit? @@ -1044,7 +1037,7 @@ class Repository end def tags_sorted_by_committed_date - tags.sort_by { |tag| commit(tag.target).committed_date } + tags.sort_by { |tag| tag.target.committed_date } end def keep_around_ref_name(sha) diff --git a/app/models/service.rb b/app/models/service.rb index a8e1cc2f422..40cd9b861f0 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -26,7 +26,7 @@ class Service < ActiveRecord::Base scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) } scope :issue_trackers, -> { where(category: 'issue_tracker') } - scope :external_wikis, -> { where(type: 'ExternalWikiService') } + scope :external_wikis, -> { where(type: 'ExternalWikiService').active } scope :active, -> { where(active: true) } scope :without_defaults, -> { where(default: false) } diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index 332c55581a1..87f066edb6f 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -40,6 +40,6 @@ class DeleteBranchService < BaseService def build_push_data(branch) Gitlab::PushDataBuilder - .build(project, current_user, branch.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) + .build(project, current_user, branch.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) end end diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb index 1e41fbe34b6..32e0eed6b63 100644 --- a/app/services/delete_tag_service.rb +++ b/app/services/delete_tag_service.rb @@ -34,6 +34,6 @@ class DeleteTagService < BaseService def build_push_data(tag) Gitlab::PushDataBuilder - .build(project, current_user, tag.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", []) + .build(project, current_user, tag.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", []) end end diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 58573078048..969530c4fdc 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -26,8 +26,8 @@ class GitTagPushService < BaseService unless Gitlab::Git.blank_ref?(params[:newrev]) tag_name = Gitlab::Git.ref_name(params[:ref]) tag = project.repository.find_tag(tag_name) - - if tag && tag.target == params[:newrev] + + if tag && tag.object_sha == params[:newrev] commit = project.commit(tag.target) commits = [commit].compact message = tag.message diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb new file mode 100644 index 00000000000..0b56b09738d --- /dev/null +++ b/app/services/repository_archive_clean_up_service.rb @@ -0,0 +1,33 @@ +class RepositoryArchiveCleanUpService + LAST_MODIFIED_TIME_IN_MINUTES = 120 + + def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES) + @mmin = mmin + @path = Gitlab.config.gitlab.repository_downloads_path + end + + def execute + Gitlab::Metrics.measure(:repository_archive_clean_up) do + return unless File.directory?(path) + + clean_up_old_archives + clean_up_empty_directories + end + end + + private + + attr_reader :mmin, :path + + def clean_up_old_archives + run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete)) + end + + def clean_up_empty_directories + run(%W(find #{path} -not -path #{path} -type d -empty -name \*.git -maxdepth 1 -delete)) + end + + def run(cmd) + Gitlab::Popen.popen(cmd) + end +end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 1af9e9b0edb..2f5f49f7de7 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -33,16 +33,15 @@ class FileUploader < CarrierWave::Uploader::Base end def to_h - filename = image? ? self.file.basename : self.file.filename + filename = image_or_video? ? self.file.basename : self.file.filename escaped_filename = filename.gsub("]", "\\]") markdown = "[#{escaped_filename}](#{self.secure_url})" - markdown.prepend("!") if image? + markdown.prepend("!") if image_or_video? { alt: filename, url: self.secure_url, - is_image: image?, markdown: markdown } end diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb index 5ef440f3367..b10ad71d052 100644 --- a/app/uploaders/uploader_helper.rb +++ b/app/uploaders/uploader_helper.rb @@ -1,16 +1,37 @@ # Extra methods for uploader module UploaderHelper + IMAGE_EXT = %w[png jpg jpeg gif bmp tiff] + # We recommend using the .mp4 format over .mov. Videos in .mov format can + # still be used but you really need to make sure they are served with the + # proper MIME type video/mp4 and not video/quicktime or your videos won't play + # on IE >= 9. + # http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html + VIDEO_EXT = %w[mp4 m4v mov webm ogv] + def image? - img_ext = %w(png jpg jpeg gif bmp tiff) - if file.respond_to?(:extension) - img_ext.include?(file.extension.downcase) - else - # Not all CarrierWave storages respond to :extension - ext = file.path.split('.').last.downcase - img_ext.include?(ext) - end - rescue - false + extension_match?(IMAGE_EXT) + end + + def video? + extension_match?(VIDEO_EXT) + end + + def image_or_video? + image? || video? + end + + def extension_match?(extensions) + return false unless file + + extension = + if file.respond_to?(:extension) + file.extension + else + # Not all CarrierWave storages respond to :extension + File.extname(file.path).delete('.') + end + + extensions.include?(extension.downcase) end def file_storage? diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml new file mode 100644 index 00000000000..fa1ad9efa73 --- /dev/null +++ b/app/views/discussions/_diff_discussion.html.haml @@ -0,0 +1,6 @@ +%tr.notes_holder + %td.notes_line{ colspan: 2 } + %td.notes_content + %ul.notes{ data: { discussion_id: discussion.id } } + = render partial: "projects/notes/note", collection: discussion.notes, as: :note + = link_to_reply_discussion(discussion) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml new file mode 100644 index 00000000000..02b159ffd45 --- /dev/null +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -0,0 +1,14 @@ +- diff_file = discussion.diff_file +- blob = discussion.blob + +.diff-file.file-holder + .file-title + = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_diff_path(discussion) + + .diff-content.code.js-syntax-highlight + %table + - discussion.truncated_diff_lines.each do |line| + = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true + + - if discussion.for_line?(line) + = render "discussions/diff_discussion", discussion: discussion diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml new file mode 100644 index 00000000000..49702e048aa --- /dev/null +++ b/app/views/discussions/_discussion.html.haml @@ -0,0 +1,45 @@ +- expanded = discussion.expanded? +%li.note.note-discussion.timeline-entry + .timeline-entry-inner + .timeline-icon + = link_to user_path(discussion.author) do + = image_tag avatar_icon(discussion.author), class: "avatar s40" + .timeline-content + .discussion.js-toggle-container{ class: discussion.id } + .discussion-header + = link_to_member(@project, discussion.author, avatar: false) + + .inline.discussion-headline-light + = discussion.author.to_reference + started a discussion on + + - if discussion.for_commit? + - commit = discussion.noteable + - if commit + commit + = link_to commit.short_id, namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code), class: 'monospace' + - else + a deleted commit + - else + - if discussion.active? + = link_to diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code) do + the diff + - else + an outdated diff + + = time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago") + + .discussion-actions + = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do + - if expanded + = icon("chevron-up") + - else + = icon("chevron-down") + + Toggle discussion + + .discussion-body.js-toggle-content{ class: ("hide" unless expanded) } + - if discussion.diff_discussion? && discussion.diff_file + = render "discussions/diff_with_notes", discussion: discussion + - else + = render "discussions/notes", discussion: discussion diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml new file mode 100644 index 00000000000..a2642b839f6 --- /dev/null +++ b/app/views/discussions/_notes.html.haml @@ -0,0 +1,5 @@ +.panel.panel-default + .notes{ data: { discussion_id: discussion.id } } + %ul.notes.timeline + = render partial: "projects/notes/note", collection: discussion.notes, as: :note + = link_to_reply_discussion(discussion) diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml new file mode 100644 index 00000000000..a798c438ea0 --- /dev/null +++ b/app/views/discussions/_parallel_diff_discussion.html.haml @@ -0,0 +1,22 @@ +%tr.notes_holder + - if discussion_left + %td.notes_line.old + %td.notes_content.parallel.old + %ul.notes{ data: { discussion_id: discussion_left.id } } + = render partial: "projects/notes/note", collection: discussion_left.notes, as: :note + + = link_to_reply_discussion(discussion_left, 'old') + - else + %td.notes_line.old= "" + %td.notes_content.parallel.old= "" + + - if discussion_right + %td.notes_line.new + %td.notes_content.parallel.new + %ul.notes{ data: { discussion_id: discussion_right.id } } + = render partial: "projects/notes/note", collection: discussion_right.notes, as: :note + + = link_to_reply_discussion(discussion_right, 'new') + - else + %td.notes_line.new= "" + %td.notes_content.parallel.new= "" diff --git a/app/views/events/_event_scope.html.haml b/app/views/events/_event_scope.html.haml new file mode 100644 index 00000000000..8f7da7d8c4f --- /dev/null +++ b/app/views/events/_event_scope.html.haml @@ -0,0 +1,7 @@ +%span.event-scope + = event_preposition(event) + - if event.project + = link_to_project event.project + - else + = event.project_name + diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml index 2e2403347c1..bba6e0d2c20 100644 --- a/app/views/events/event/_common.html.haml +++ b/app/views/events/event/_common.html.haml @@ -1,6 +1,6 @@ .event-title %span.author_name= link_to_author event - %span.event_label{class: event.action_name} + %span{class: event.action_name} - if event.target = event.action_name %strong @@ -10,12 +10,7 @@ - else = event_action_name(event) - = event_preposition(event) - - - if event.project - = link_to_project event.project - - else - = event.project_name + = render "events/event_scope", event: event - if event.target.respond_to?(:title) .event-body diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml index 5a2a469ba62..aba64dd17d0 100644 --- a/app/views/events/event/_created_project.html.haml +++ b/app/views/events/event/_created_project.html.haml @@ -1,6 +1,6 @@ .event-title %span.author_name= link_to_author event - %span.event_label{class: event.action_name} + %span{class: event.action_name} = event_action_name(event) - if event.project diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index 830fec0b4ab..f08c96df309 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -1,14 +1,9 @@ .event-title %span.author_name= link_to_author event - %span.event_label - = event.action_name - = event_note_title_html(event) - at + = event.action_name + = event_note_title_html(event) - - if event.project - = link_to_project event.project - - else - = event.project_name + = render "events/event_scope", event: event .event-body .event-note diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index ea54ef226ec..44fff49d99c 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -2,14 +2,14 @@ .event-title %span.author_name= link_to_author event - %span.event_label.pushed #{event.action_name} #{event.ref_type} + %span.pushed #{event.action_name} #{event.ref_type} - if event.rm_ref? %strong= event.ref_name - else %strong = link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title) - at - = link_to_project project + + = render "events/event_scope", event: event - if event.push_with_commits? .event-body diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 21668698814..3a14751ea8e 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -30,7 +30,7 @@ %span Merge Requests %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) - = nav_link(controller: :snippets) do + = nav_link(controller: 'dashboard/snippets') do = link_to dashboard_snippets_path, title: 'Snippets' do %span Snippets diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 2049b204956..d03d5e2ca6a 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -6,7 +6,7 @@ - content_for :scripts_body_top do - project = @target_project || @project - if @project_wiki && @page - - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, params[:id]) + - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, @page.title) - else - markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project) - if current_user diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 48b0dd6b121..ac50ce83f6a 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -5,7 +5,8 @@ %i.fa.fa-rss = render 'shared/event_filter' -.content_list{:"data-href" => activity_project_path(@project)} + +.content_list.project-activity{:"data-href" => activity_project_path(@project)} = spinner :javascript diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml index 9fe65cbb104..d54c76ff9c8 100644 --- a/app/views/projects/branches/_commit.html.haml +++ b/app/views/projects/branches/_commit.html.haml @@ -1,5 +1,5 @@ .branch-commit - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id monospace" + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-id monospace" · %span.str-truncated = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 9264289987d..a9fb3c58431 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -14,16 +14,19 @@ %span ##{build.id} - if build.stuck? - = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') + .icon-container + = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - if defined?(retried) && retried - = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') + .icon-container + = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') - if defined?(ref) && ref - if build.ref = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - else .light none - = custom_icon("icon_commit") + .icon-container + = custom_icon("icon_commit") - if defined?(commit_sha) && commit_sha = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" @@ -88,4 +91,3 @@ - elsif build.playable? = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = icon('play') - diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 41fd5459429..540689f4a61 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -35,8 +35,8 @@ .bs-callout.bs-callout-warning \.gitlab-ci.yml not found in this commit -.table-holder - %table.table.builds +.table-holder.pipeline-holder + %table.table.builds.pipeline %thead %tr %th Status diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index ab9afb06afb..fd888f41b1e 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -19,13 +19,14 @@ · = commit.short_id - if commit.status - = render_commit_status(commit, cssclass: 'visible-xs-inline') + .visible-xs-inline + = render_commit_status(commit) - 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') + = render_commit_status(commit) = clipboard_button(clipboard_text: commit.id) = 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) diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 65d68aa2985..f70dba224fa 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -17,6 +17,6 @@ - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do - if deployment.last? - Retry + Re-deploy - else Rollback diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml deleted file mode 100644 index b9c0d9dcdfd..00000000000 --- a/app/views/projects/diffs/_match_line_parallel.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%td.old_line.diff-line-num.empty-cell -%td.line_content.parallel.match= line -%td.new_line.diff-line-num.empty-cell -%td.line_content.parallel.match= line diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index d208fcee10b..7f30faa20d8 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -5,32 +5,35 @@ - left = line[:left] - right = line[:right] %tr.line_holder.parallel - - if left[:type] == 'match' - = render "projects/diffs/match_line_parallel", { line: left[:text] } - - elsif left[:type] == 'nonewline' - %td.old_line.diff-line-num.empty-cell - %td.line_content.parallel.match= left[:text] - %td.new_line.diff-line-num.empty-cell - %td.line_content.parallel.match= left[:text] + - if left + - if left.meta? + %td.old_line.diff-line-num.empty-cell + %td.line_content.parallel.match= left.text + - else + - left_line_code = diff_file.line_code(left) + - left_position = diff_file.position(left) + %td.old_line.diff-line-num{id: left_line_code, class: left.type, data: { linenumber: left.old_pos }} + %a{href: "##{left_line_code}" }= raw(left.old_pos) + %td.line_content.parallel.noteable_line{class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old')}= diff_line_content(left.text) - else - %td.old_line.diff-line-num{id: left[:line_code], class: [left[:type], ('empty-cell' unless left[:number])], data: { linenumber: left[:number] }} - %a{href: "##{left[:line_code]}" }= raw(left[:number]) - %td.line_content.parallel.noteable_line{class: [left[:type], ('empty-cell' if left[:text].empty?)], data: diff_view_line_data(left[:line_code], left[:position], 'old')}= diff_line_content(left[:text]) + %td.old_line.diff-line-num.empty-cell + %td.line_content.parallel - - if right[:type] == 'new' - - new_line_type = 'new' - - new_line_code = right[:line_code] - - new_position = right[:position] + - if right + - if right.meta? + %td.old_line.diff-line-num.empty-cell + %td.line_content.parallel.match= left.text - else - - new_line_type = nil - - new_line_code = left[:line_code] - - new_position = left[:position] - - %td.new_line.diff-line-num{id: new_line_code, class: [new_line_type, ('empty-cell' unless right[:number])], data: { linenumber: right[:number] }} - %a{href: "##{new_line_code}" }= raw(right[:number]) - %td.line_content.parallel.noteable_line{class: [new_line_type, ('empty-cell' if right[:text].empty?)], data: diff_view_line_data(new_line_code, new_position, 'new')}= diff_line_content(right[:text]) + - right_line_code = diff_file.line_code(right) + - right_position = diff_file.position(right) + %td.new_line.diff-line-num{id: right_line_code, class: right.type, data: { linenumber: right.new_pos }} + %a{href: "##{right_line_code}" }= raw(right.new_pos) + %td.line_content.parallel.noteable_line{class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new')}= diff_line_content(right.text) + - else + %td.old_line.diff-line-num.empty-cell + %td.line_content.parallel - unless @diff_notes_disabled - - notes_left, notes_right = organize_comments(left, right) - - if notes_left.present? || notes_right.present? - = render "projects/notes/diff_notes_with_reply_parallel", notes_left: notes_left, notes_right: notes_right + - discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file) + - if discussion_left || discussion_right + = render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index 196f8122db3..5970b9abf2b 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -11,9 +11,9 @@ - unless @diff_notes_disabled - line_code = diff_file.line_code(line) - - diff_notes = @grouped_diff_notes[line_code] if line_code - - if diff_notes - = render "projects/notes/diff_notes_with_reply", notes: diff_notes + - discussion = @grouped_diff_discussions[line_code] if line_code + - if discussion + = render "discussions/diff_discussion", discussion: discussion - if last_line > 0 = render "projects/diffs/match_line", { line: "", diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index c72d0140bb9..facdfcc9447 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -89,9 +89,9 @@ = link_to "#", class: 'btn js-toggle-button import_git' do %i.fa.fa-git %span Repo by URL - %div + %div{ class: 'import_gitlab_project' } - if gitlab_project_import_enabled? - = link_to new_import_gitlab_project_path, class: 'btn import_gitlab_project project-submit' do + = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do %i.fa.fa-gitlab %span GitLab export @@ -130,29 +130,29 @@ $(".modal").hide(); }); - $('.import_gitlab_project').bind('click', function() { - var _href = $("a.import_gitlab_project").attr("href"); - $(".import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val()); + $('.btn_import_gitlab_project').bind('click', function() { + var _href = $("a.btn_import_gitlab_project").attr("href"); + $(".btn_import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val()); }); - $('.import_gitlab_project').attr('disabled',true) - $('.import_gitlab_project').attr('title', 'Project path required.'); + $('.btn_import_gitlab_project').attr('disabled',true) + $('.import_gitlab_project').attr('title', 'Project path and name required.'); $('.import_gitlab_project').click(function( event ) { - if($('.import_gitlab_project').attr('disabled')) { + if($('.btn_import_gitlab_project').attr('disabled')) { event.preventDefault(); - new Flash("Please enter a path for the project to be imported to."); + new Flash("Please enter path and name for the project to be imported to."); } }); $('#project_path').keyup(function(){ if($(this).val().length !=0) { - $('.import_gitlab_project').attr('disabled', false); + $('.btn_import_gitlab_project').attr('disabled', false); $('.import_gitlab_project').attr('title',''); $(".flash-container").html("") } else { - $('.import_gitlab_project').attr('disabled',true); - $('.import_gitlab_project').attr('title', 'Project path required.'); + $('.btn_import_gitlab_project').attr('disabled',true); + $('.import_gitlab_project').attr('title', 'Project path and name required.'); } }); diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml deleted file mode 100644 index ec6c4938efc..00000000000 --- a/app/views/projects/notes/_diff_notes_with_reply.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -- note = notes.first -%tr.notes_holder - %td.notes_line{ colspan: 2 } - %td.notes_content - %ul.notes{ data: { discussion_id: note.discussion_id } } - = render partial: "projects/notes/note", collection: notes, as: :note - = link_to_reply_discussion(note) diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml deleted file mode 100644 index e50a4f86d03..00000000000 --- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml +++ /dev/null @@ -1,25 +0,0 @@ -- note_left = notes_left.present? ? notes_left.first : nil -- note_right = notes_right.present? ? notes_right.first : nil - -%tr.notes_holder - - if note_left - %td.notes_line.old - %td.notes_content.parallel.old - %ul.notes{ data: { discussion_id: note_left.discussion_id } } - = render partial: "projects/notes/note", collection: notes_left, as: :note - - = link_to_reply_discussion(note_left, 'old') - - else - %td.notes_line.old= "" - %td.notes_content.parallel.old= "" - - - if note_right - %td.notes_line.new - %td.notes_content.parallel.new - %ul.notes{ data: { discussion_id: note_right.discussion_id } } - = render partial: "projects/notes/note", collection: notes_right, as: :note - - = link_to_reply_discussion(note_right, 'new') - - else - %td.notes_line.new= "" - %td.notes_content.parallel.new= "" diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml deleted file mode 100644 index 7869d6413d8..00000000000 --- a/app/views/projects/notes/_discussion.html.haml +++ /dev/null @@ -1,46 +0,0 @@ -- note = discussion_notes.first -- expanded = !note.diff_note? || note.active? -%li.note.note-discussion.timeline-entry - .timeline-entry-inner - .timeline-icon - = link_to user_path(note.author) do - = image_tag avatar_icon(note.author), class: "avatar s40" - .timeline-content - .discussion.js-toggle-container{ class: note.discussion_id } - .discussion-header - = link_to_member(@project, note.author, avatar: false) - - .inline.discussion-headline-light - = note.author.to_reference - started a discussion on - - - if note.for_commit? - - commit = note.noteable - - if commit - commit - = link_to commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code), class: 'monospace' - - else - a deleted commit - - else - - if note.active? - = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do - the diff - - else - an outdated diff - - = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "note-created-ago") - - .discussion-actions - = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do - - if expanded - = icon("chevron-up") - - else - = icon("chevron-down") - - Toggle discussion - - .discussion-body.js-toggle-content{ class: ("hide" unless expanded) } - - if note.diff_note? - = render "projects/notes/discussions/diff_with_notes", discussion_notes: discussion_notes - - else - = render "projects/notes/discussions/notes", discussion_notes: discussion_notes diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index af0046886fb..71da8ac9d7c 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -30,7 +30,7 @@ = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do = icon('trash-o') .note-body{class: note_editable ? 'js-task-list-container' : ''} - .note-text + .note-text.md = preserve do = note.note_html = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml index ebf7e8a9cb3..022578bd6db 100644 --- a/app/views/projects/notes/_notes.html.haml +++ b/app/views/projects/notes/_notes.html.haml @@ -1,10 +1,8 @@ - if @discussions.present? - - @discussions.each do |discussion_notes| - - note = discussion_notes.first - - if note_for_main_target?(note) - = render partial: "projects/notes/note", object: note, as: :note + - @discussions.each do |discussion| + - if discussion.for_target?(@noteable) + = render partial: "projects/notes/note", object: discussion.first_note, as: :note - else - = render 'projects/notes/discussion', discussion_notes: discussion_notes + = render 'discussions/discussion', discussion: discussion - else - - @notes.each do |note| - = render partial: "projects/notes/note", object: note, as: :note + = render partial: "projects/notes/note", collection: @notes, as: :note diff --git a/app/views/projects/notes/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml deleted file mode 100644 index 4a69b8f8840..00000000000 --- a/app/views/projects/notes/discussions/_diff_with_notes.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -- note = discussion_notes.first -- diff_file = note.diff_file -- return unless diff_file - -- blob = note.blob - -.diff-file.file-holder - .file-title - = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: note.project, url: diff_note_path(note) - - .diff-content.code.js-syntax-highlight - %table - - note.truncated_diff_lines.each do |line| - = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true - - - if note.for_line?(line) - = render "projects/notes/diff_notes_with_reply", notes: discussion_notes diff --git a/app/views/projects/notes/discussions/_notes.html.haml b/app/views/projects/notes/discussions/_notes.html.haml deleted file mode 100644 index a785149549d..00000000000 --- a/app/views/projects/notes/discussions/_notes.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -- note = discussion_notes.first -.panel.panel-default - .notes{ data: { discussion_id: note.discussion_id } } - %ul.notes.timeline - = render partial: "projects/notes/note", collection: discussion_notes, as: :note - = link_to_reply_discussion(note) diff --git a/app/views/shared/icons/_icon_fork.svg b/app/views/shared/icons/_icon_fork.svg index 420ffe3a55b..a21f8f3a951 100644 --- a/app/views/shared/icons/_icon_fork.svg +++ b/app/views/shared/icons/_icon_fork.svg @@ -1 +1,3 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="4" cy="4" r="4"/><mask id="d" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><circle id="b" cx="20" cy="4" r="4"/><mask id="e" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><circle id="c" cx="12" cy="30" r="4"/><mask id="f" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(8 3)"><path fill="#7E7E7E" d="M10 19.667c-4.14-1.29-7.389-5.878-7.389-5.878C2.274 13.353 2 12.545 2 12.01V6h4v5.509c0 .276.166.65.367.831 0 0 1.136 1.028 1.746 1.574C9.617 15.261 11.048 16 12.09 16c1.028 0 2.41-.723 3.858-2.048.588-.54 1.84-1.742 1.84-1.742a.784.784 0 0 0 .211-.502V6h4v6.008c0 .548-.259 1.349-.601 1.795 0 0-3.21 4.707-7.399 5.916V27h-4v-7.333z"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#d)" xlink:href="#a"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#e)" xlink:href="#b"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#f)" xlink:href="#c"/></g></svg>
\ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"> + <path fill="#7E7E7E" fill-rule="evenodd" d="M22,29.5351288 L22,22.7193602 C26.1888699,21.5098039 29.3985457,16.802989 29.3985457,16.802989 C29.740988,16.3567547 30,15.5559546 30,15.0081969 L30,10.4648712 C31.1956027,9.77325238 32,8.48056471 32,7 C32,4.790861 30.209139,3 28,3 C25.790861,3 24,4.790861 24,7 C24,8.48056471 24.8043973,9.77325238 26,10.4648712 L26,14.7083871 C26,14.8784435 25.9055559,15.0987329 25.7890533,15.2104147 C25.7890533,15.2104147 24.5373893,16.4126202 23.9488702,16.9515733 C22.5015398,18.2770075 21.1191354,19 20.090554,19 C19.0477772,19 17.6172728,18.2608988 16.1128852,16.9142923 C15.5030182,16.3683886 14.3672121,15.3403307 14.3672121,15.3403307 C14.1659605,15.1583364 14.0000086,14.7846305 14.0000192,14.5088473 C14.0000192,14.5088473 14.0000932,12.7539451 14.0001308,10.4647956 C15.1956614,9.77315812 16,8.48051074 16,7 C16,4.790861 14.209139,3 12,3 C9.790861,3 8,4.790861 8,7 C8,8.48056471 8.80439726,9.77325238 10,10.4648712 L10,15.0081969 C10,15.5446944 10.2736352,16.3534183 10.6111812,16.7893819 C10.6111812,16.7893819 13.8599776,21.3779363 18,22.6668724 L18,29.5351288 C16.8043973,30.2267476 16,31.5194353 16,33 C16,35.209139 17.790861,37 20,37 C22.209139,37 24,35.209139 24,33 C24,31.5194353 23.1956027,30.2267476 22,29.5351288 Z M14,7 C14,5.8954305 13.1045695,5 12,5 C10.8954305,5 10,5.8954305 10,7 C10,8.1045695 10.8954305,9 12,9 C13.1045695,9 14,8.1045695 14,7 Z M30,7 C30,5.8954305 29.1045695,5 28,5 C26.8954305,5 26,5.8954305 26,7 C26,8.1045695 26.8954305,9 28,9 C29.1045695,9 30,8.1045695 30,7 Z M22,33 C22,31.8954305 21.1045695,31 20,31 C18.8954305,31 18,31.8954305 18,33 C18,34.1045695 18.8954305,35 20,35 C21.1045695,35 22,34.1045695 22,33 Z"/> +</svg> diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 8551288e2f2..0b6a01a3200 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -28,12 +28,12 @@ class EmailsOnPushWorker :push end - merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha) - diff_refs = nil compare = nil reverse_compare = false + if action == :push + merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha) compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) diff_refs = Gitlab::Diff::DiffRefs.new( diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb index 47c5a670ed4..a2e49c61f59 100644 --- a/app/workers/repository_archive_cache_worker.rb +++ b/app/workers/repository_archive_cache_worker.rb @@ -4,6 +4,6 @@ class RepositoryArchiveCacheWorker sidekiq_options queue: :default def perform - Repository.clean_old_archives + RepositoryArchiveCleanUpService.new.execute end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 325eca72862..1470a6e2550 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -106,8 +106,8 @@ production: &base ## Repository downloads directory # When a user clicks e.g. 'Download zip' on a project, a temporary zip file is created in the following directory. - # The default is 'tmp/repositories' relative to the root of the Rails app. - # repository_downloads_path: tmp/repositories + # The default is 'shared/cache/archive/' relative to the root of the Rails app. + # repository_downloads_path: shared/cache/archive/ ## Reply by email # Allow users to comment on issues and merge requests by replying to notification emails. diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 693507e0bec..86f55210487 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -211,7 +211,6 @@ Settings.gitlab.default_projects_features['snippets'] = false if Setti Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil? Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil? 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['domain_whitelist'] ||= [] Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project] Settings.gitlab['trusted_proxies'] ||= [] @@ -316,6 +315,21 @@ Settings.repositories['storages'] ||= {} Settings.repositories.storages['default'] ||= Settings.gitlab_shell['repos_path'] || Settings.gitlab['user_home'] + '/repositories/' # +# The repository_downloads_path is used to remove outdated repository +# archives, if someone has it configured incorrectly, and it points +# to the path where repositories are stored this can cause some +# data-integrity issue. In this case, we sets it to the default +# repository_downloads_path value. +# +repositories_storages_path = Settings.repositories.storages.values +repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(/\/$/, '') +repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home']) + +if repository_downloads_path.blank? || repositories_storages_path.any? { |path| [repository_downloads_path, repository_downloads_full_path].include?(path.gsub(/\/$/, '')) } + Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') +end + +# # Backup # Settings['backup'] ||= Settingslogic.new({}) diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb index 3ba9e36c567..37746968675 100644 --- a/config/initializers/6_validations.rb +++ b/config/initializers/6_validations.rb @@ -3,22 +3,27 @@ def storage_name_valid?(name) end def find_parent_path(name, path) + parent = Pathname.new(path).realpath.parent Gitlab.config.repositories.storages.detect do |n, p| - name != n && path.chomp('/').start_with?(p.chomp('/')) + name != n && Pathname.new(p).realpath == parent end end -def error(message) +def storage_validation_error(message) raise "#{message}. Please fix this in your gitlab.yml before starting GitLab." end -error('No repository storage path defined') if Gitlab.config.repositories.storages.empty? +def validate_storages + storage_validation_error('No repository storage path defined') if Gitlab.config.repositories.storages.empty? -Gitlab.config.repositories.storages.each do |name, path| - error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name) + Gitlab.config.repositories.storages.each do |name, path| + storage_validation_error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name) - parent_name, _parent_path = find_parent_path(name, path) - if parent_name - error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages") + parent_name, _parent_path = find_parent_path(name, path) + if parent_name + storage_validation_error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages") + end end end + +validate_storages unless Rails.env.test? diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index ca58ae92d1b..3e553120205 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -6,5 +6,9 @@ Mime::Type.register_alias "text/plain", :diff Mime::Type.register_alias "text/plain", :patch -Mime::Type.register_alias 'text/html', :markdown -Mime::Type.register_alias 'text/html', :md +Mime::Type.register_alias "text/html", :markdown +Mime::Type.register_alias "text/html", :md + +Mime::Type.register "video/mp4", :mp4, [], [:m4v, :mov] +Mime::Type.register "video/webm", :webm +Mime::Type.register "video/ogg", :ogv diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb deleted file mode 100644 index 253e3cf7410..00000000000 --- a/config/initializers/secure_headers.rb +++ /dev/null @@ -1,99 +0,0 @@ -# CSP headers have to have single quotes, so failures relating to quotes -# inside Ruby string arrays are irrelevant. -# rubocop:disable Lint/PercentStringArray -require 'gitlab/current_settings' -include Gitlab::CurrentSettings - -CSP_REPORT_URI = '' - -# Content Security Policy Headers -# For more information on CSP see: -# - https://gitlab.com/gitlab-org/gitlab-ce/issues/18231 -# - https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives -SecureHeaders::Configuration.default do |config| - # Mark all cookies as "Secure", "HttpOnly", and "SameSite=Strict". - config.cookies = { - secure: true, - httponly: true, - samesite: { - strict: true - } - } - config.x_content_type_options = "nosniff" - config.x_xss_protection = "1; mode=block" - config.x_download_options = "noopen" - config.x_permitted_cross_domain_policies = "none" - config.referrer_policy = "origin-when-cross-origin" - config.csp = { - # "Meta" values. - report_only: true, - preserve_schemes: true, - - # "Directive" values. - # Default source allows nothing, more permissive values are set per-policy. - default_src: %w('none'), - # (Deprecated) Don't allow iframes. - frame_src: %w('none'), - # Only allow XMLHTTPRequests from the GitLab instance itself. - connect_src: %w('self'), - # Only load local fonts. - font_src: %w('self'), - # Load local images, any external image available over HTTPS. - img_src: %w(* 'self' data:), - # Audio and video can't be played on GitLab currently, so it's disabled. - media_src: %w('none'), - # Don't allow <object>, <embed>, or <applet> elements. - object_src: %w('none'), - # Allow local scripts and inline scripts. - script_src: %w('unsafe-inline' 'unsafe-eval' 'self'), - # Allow local stylesheets and inline styles. - style_src: %w('unsafe-inline' 'self'), - # The URIs that a user agent may use as the document base URL. - base_uri: %w('self'), - # Only allow local iframes and service workers - child_src: %w('self'), - # Only submit form information to the GitLab instance. - form_action: %w('self'), - # Disallow any parents from embedding a page in an iframe. - frame_ancestors: %w('none'), - # Don't allow any plugins (Flash, Shockwave, etc.) - plugin_types: %w(), - # Blocks all mixed (HTTP) content. - block_all_mixed_content: true, - # Upgrades insecure requests to HTTPS when possible. - upgrade_insecure_requests: true - } - - config.csp[:report_uri] = %W(#{CSP_REPORT_URI}) - - # Allow Bootstrap Linter in development mode. - if Rails.env.development? - config.csp[:script_src] << "maxcdn.bootstrapcdn.com" - end - - # reCAPTCHA - if current_application_settings.recaptcha_enabled - config.csp[:script_src] << "https://www.google.com/recaptcha/" - config.csp[:script_src] << "https://www.gstatic.com/recaptcha/" - config.csp[:frame_src] << "https://www.google.com/recaptcha/" - config.x_frame_options = "SAMEORIGIN" - end - - # Gravatar - if current_application_settings.gravatar_enabled? - config.csp[:img_src] << "www.gravatar.com" - config.csp[:img_src] << "secure.gravatar.com" - config.csp[:img_src] << Gitlab.config.gravatar.host - end - - # Piwik - if Gitlab.config.extra.has_key?('piwik_url') && Gitlab.config.extra.has_key?('piwik_site_id') - config.csp[:script_src] << Gitlab.config.extra.piwik_url - config.csp[:img_src] << Gitlab.config.extra.piwik_url - end - - # Google Analytics - if Gitlab.config.extra.has_key?('google_analytics_id') - config.csp[:script_src] << "https://www.google-analytics.com" - end -end diff --git a/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb b/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb new file mode 100644 index 00000000000..1eb99feb40c --- /dev/null +++ b/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb @@ -0,0 +1,15 @@ +class DropAndReaddHasExternalWikiInProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def up + update_column_in_batches(:projects, :has_external_wiki, nil) do |table, query| + query.where(table[:has_external_wiki].not_eq(nil)) + end + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index c7876426424..d541e1cccb7 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: 20160718153603) do +ActiveRecord::Schema.define(version: 20160721081015) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md index b44f8cfd628..796b3680a75 100644 --- a/doc/api/award_emoji.md +++ b/doc/api/award_emoji.md @@ -67,9 +67,9 @@ Example Response: ] ``` -### Get single issue note +### Get single award emoji -Gets a single award emoji +Gets a single award emoji from an issue or merge request. ``` GET /projects/:id/issues/:issue_id/award_emoji/:award_id diff --git a/doc/api/projects.md b/doc/api/projects.md index dceee7b4ea7..0ba0bffb4ac 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -850,7 +850,6 @@ Parameters: { "alt": "dk", "url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png", - "is_image": true, "markdown": "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)" } ``` diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 31b4fd2669e..ea3fff1596e 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -13,34 +13,36 @@ 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) - - [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) + - [script](#script) + - [stage](#stage) + - [only and except](#only-and-except) + - [job variables](#job-variables) + - [tags](#tags) + - [allow_failure](#allow_failure) + - [when](#when) + - [Manual actions](#manual-actions) + - [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) - [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-ciyml) + - [Anchors](#anchors) +- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml) - [Skipping builds](#skipping-builds) - [Examples](#examples) @@ -473,6 +475,39 @@ job: The specification above, will make sure that `job` is built by a Runner that has both `ruby` AND `postgres` tags defined. +### allow_failure + +`allow_failure` is used when you want to allow a build to fail without impacting +the rest of the CI suite. Failed builds don't contribute to the commit status. + +When enabled and the build fails, the pipeline will be successful/green for all +intents and purposes, but a "CI build passed with warnings" message will be +displayed on the merge request or commit or build page. This is to be used by +builds that are allowed to fail, but where failure indicates some other (manual) +steps should be taken elsewhere. + +In the example below, `job1` and `job2` will run in parallel, but if `job1` +fails, it will not stop the next stage from running, since it's marked with +`allow_failure: true`: + +```yaml +job1: + stage: test + script: + - execute_script_that_will_fail + allow_failure: true + +job2: + stage: test + script: + - execute_script_that_will_succeed + +job3: + stage: deploy + script: + - deploy_to_staging +``` + ### when `when` is used to implement jobs that are run in case of failure or despite the @@ -485,7 +520,8 @@ failure. 1. `on_failure` - execute build only when at least one build from prior stages fails. 1. `always` - execute build regardless of the status of builds from prior stages. -1. `manual` - execute build manually. +1. `manual` - execute build manually (added in GitLab 8.10). Read about + [manual actions](#manual-actions) below. For example: @@ -528,21 +564,22 @@ cleanup_job: The above script will: -1. Execute `cleanup_build_job` only when `build_job` fails -2. Always execute `cleanup_job` as the last step in pipeline -3. Allow you to manually execute `deploy_job` from GitLab +1. Execute `cleanup_build_job` only when `build_job` fails. +2. Always execute `cleanup_job` as the last step in pipeline regardless of + success or failure. +3. Allow you to manually execute `deploy_job` from GitLab's UI. #### Manual actions >**Note:** Introduced in GitLab 8.10. -Manual actions are special type of jobs that are not executed automatically in pipeline. -They need to be explicitly started by the user. -Manual actions can be started from pipelines, builds, environments and deployments views. -You can execute the same manual action multiple times. +Manual actions are a special type of job that are not executed automatically; +they need to be explicitly started by a user. Manual actions can be started +from pipeline, build, environment, and deployment views. You can execute the +same manual action multiple times. -Example usage of manual actions is deployment, ex. promote a staging environment to production. +An example usage of manual actions is deployment to production. ### environment @@ -645,9 +682,10 @@ be available for download in the GitLab UI. Introduced in GitLab 8.6 and GitLab Runner v1.1.0. The `name` directive allows you to define the name of the created artifacts -archive. That way, you can have a unique name of every archive which could be +archive. That way, you can have a unique name for every archive which could be useful when you'd like to download the archive from GitLab. The `artifacts:name` variable can make use of any of the [predefined variables](../variables/README.md). +The default name is `artifacts`, which becomes `artifacts.zip` when downloaded. --- diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md index 1b465434498..55077197ff9 100644 --- a/doc/container_registry/README.md +++ b/doc/container_registry/README.md @@ -1,7 +1,8 @@ # GitLab Container Registry > **Note:** -This feature was [introduced][ce-4040] in GitLab 8.8. +This feature was [introduced][ce-4040] in GitLab 8.8. Docker Registry manifest +v1 support was added in GitLab 8.9 to support Docker versions earlier than 1.10. > **Note:** This document is about the user guide. To learn how to enable GitLab Container diff --git a/doc/install/installation.md b/doc/install/installation.md index 19d083d580d..9bc0dbb5e2a 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -398,7 +398,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.7 + sudo -u git -H git checkout v0.7.8 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/markdown/img/video.mp4 b/doc/markdown/img/video.mp4 Binary files differnew file mode 100644 index 00000000000..1fc478842f5 --- /dev/null +++ b/doc/markdown/img/video.mp4 diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index fb2dd582754..2bbe4b2a36e 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -13,6 +13,7 @@ * [Emoji](#emoji) * [Special GitLab references](#special-gitlab-references) * [Task Lists](#task-lists) +* [Videos](#videos) **[Standard Markdown](#standard-markdown)** @@ -281,6 +282,20 @@ You can add task lists to issues, merge requests and comments. To create a task Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes. +## Videos + +Image tags with a video extension are automatically converted to a video player. + +The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`. + + Here's a sample video: + + ![Sample Video](img/video.mp4) + +Here's a sample video: + +![Sample Video](img/video.mp4) + # Standard Markdown ## Headers diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md index 84065a84e50..71cbe5c8ac6 100644 --- a/doc/update/8.9-to-8.10.md +++ b/doc/update/8.9-to-8.10.md @@ -58,7 +58,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.7.7 +sudo -u git -H git checkout v0.7.8 sudo -u git -H make ``` diff --git a/features/project/wiki.feature b/features/project/wiki.feature index d4811b1ff54..63ce3ccb536 100644 --- a/features/project/wiki.feature +++ b/features/project/wiki.feature @@ -8,6 +8,12 @@ Feature: Project Wiki Given I create the Wiki Home page Then I should see the newly created wiki page + Scenario: Add new page with errors + Given I create the Wiki Home page with no content + Then I should see a "Content can't be blank" error message + When I create the Wiki Home page + Then I should see the newly created wiki page + Scenario: Pressing Cancel while editing a brand new Wiki Given I click on the Cancel button Then I should be redirected back to the Edit Home Wiki page diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 3cbf832c728..5ad82a9b3c1 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -19,6 +19,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps click_on "Create page" end + step 'I create the Wiki Home page with no content' do + fill_in "wiki_content", with: '' + click_on "Create page" + end + step 'I should see the newly created wiki page' do expect(page).to have_content "Home" expect(page).to have_content "link test" @@ -173,6 +178,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps find('a[href*="?version_id"]') end + step 'I should see a "Content can\'t be blank" error message' do + expect(page).to have_content('The form contains the following error:') + expect(page).to have_content('Content can\'t be blank') + end + def wiki @project_wiki = ProjectWiki.new(project, current_user) end diff --git a/lib/api/builds.rb b/lib/api/builds.rb index d36047acd1f..657d421fe97 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -52,8 +52,7 @@ module API get ':id/builds/:build_id' do authorize_read_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) present build, with: Entities::Build, user_can_download_artifacts: can?(current_user, :read_build, user_project) @@ -69,18 +68,27 @@ module API get ':id/builds/:build_id/artifacts' do authorize_read_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) - artifacts_file = build.artifacts_file + present_artifacts!(build.artifacts_file) + end - unless artifacts_file.file_storage? - return redirect_to build.artifacts_file.url - end + # Download the artifacts file from ref_name and job + # + # Parameters: + # id (required) - The ID of a project + # ref_name (required) - The ref from repository + # job (required) - The name for the build + # Example Request: + # GET /projects/:id/artifacts/:ref_name/download?job=name + get ':id/builds/artifacts/:ref_name/download', + requirements: { ref_name: /.+/ } do + authorize_read_builds! - return not_found! unless artifacts_file.exists? + builds = user_project.latest_successful_builds_for(params[:ref_name]) + latest_build = builds.find_by!(name: params[:job]) - present_file!(artifacts_file.path, artifacts_file.filename) + present_artifacts!(latest_build.artifacts_file) end # Get a trace of a specific build of a project @@ -97,8 +105,7 @@ module API get ':id/builds/:build_id/trace' do authorize_read_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) header 'Content-Disposition', "infile; filename=\"#{build.id}.log\"" content_type 'text/plain' @@ -118,8 +125,7 @@ module API post ':id/builds/:build_id/cancel' do authorize_update_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) build.cancel @@ -137,8 +143,7 @@ module API post ':id/builds/:build_id/retry' do authorize_update_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) return forbidden!('Build is not retryable') unless build.retryable? build = Ci::Build.retry(build, current_user) @@ -157,8 +162,7 @@ module API post ':id/builds/:build_id/erase' do authorize_update_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) return forbidden!('Build is not erasable!') unless build.erasable? build.erase(erased_by: current_user) @@ -176,8 +180,8 @@ module API post ':id/builds/:build_id/artifacts/keep' do authorize_update_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build && build.artifacts? + build = get_build!(params[:build_id]) + return not_found!(build) unless build.artifacts? build.keep_artifacts! @@ -192,6 +196,20 @@ module API user_project.builds.find_by(id: id.to_i) end + def get_build!(id) + get_build(id) || not_found! + end + + def present_artifacts!(artifacts_file) + if !artifacts_file.file_storage? + redirect_to(build.artifacts_file.url) + elsif artifacts_file.exists? + present_file!(artifacts_file.path, artifacts_file.filename) + else + not_found! + end + end + def filter_builds(builds, scope) return builds if scope.nil? || scope.empty? diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb new file mode 100644 index 00000000000..fd8b9a6f0cc --- /dev/null +++ b/lib/banzai/filter/video_link_filter.rb @@ -0,0 +1,59 @@ +module Banzai + module Filter + + # Find every image that isn't already wrapped in an `a` tag, and that has + # a `src` attribute ending with a video extension, add a new video node and + # a "Download" link in the case the video cannot be played. + class VideoLinkFilter < HTML::Pipeline::Filter + + def call + doc.xpath(query).each do |el| + el.replace(video_node(doc, el)) + end + + doc + end + + private + + def query + @query ||= begin + src_query = UploaderHelper::VIDEO_EXT.map do |ext| + "'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})" + end + + "descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]" + end + end + + def video_node(doc, element) + container = doc.document.create_element( + 'div', + class: 'video-container' + ) + + video = doc.document.create_element( + 'video', + src: element['src'], + width: '400', + controls: true, + 'data-setup' => '{}') + + link = doc.document.create_element( + 'a', + element['title'] || element['alt'], + href: element['src'], + target: '_blank', + title: "Download '#{element['title'] || element['alt']}'") + download_paragraph = doc.document.create_element('p') + download_paragraph.children = link + + container.add_child(video) + container.add_child(download_paragraph) + + container + end + end + + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index b27ecf3c923..8d94b199c66 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -7,6 +7,7 @@ module Banzai Filter::SanitizationFilter, Filter::UploadLinkFilter, + Filter::VideoLinkFilter, Filter::ImageLinkFilter, Filter::EmojiFilter, Filter::TableOfContentsFilter, diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb index bf366962aef..b26a41a1f3b 100644 --- a/lib/banzai/reference_extractor.rb +++ b/lib/banzai/reference_extractor.rb @@ -2,11 +2,11 @@ module Banzai # Extract possible GFM references from an arbitrary String for further processing. class ReferenceExtractor def initialize - @texts = [] + @texts_and_contexts = [] end def analyze(text, context = {}) - @texts << Renderer.render(text, context) + @texts_and_contexts << { text: text, context: context } end def references(type, project, current_user = nil) @@ -21,9 +21,10 @@ module Banzai def html_documents # This ensures that we don't memoize anything until we have a number of # text blobs to parse. - return [] if @texts.empty? + return [] if @texts_and_contexts.empty? - @html_documents ||= @texts.map { |html| Nokogiri::HTML.fragment(html) } + @html_documents ||= Renderer.cache_collection_render(@texts_and_contexts) + .map { |html| Nokogiri::HTML.fragment(html) } end end end diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb index b069afdd28c..481536a380b 100644 --- a/lib/gitlab/diff/parallel_diff.rb +++ b/lib/gitlab/diff/parallel_diff.rb @@ -8,72 +8,35 @@ module Gitlab end def parallelize - i = 0 free_right_index = nil lines = [] highlighted_diff_lines = diff_file.highlighted_diff_lines highlighted_diff_lines.each do |line| - line_code = diff_file.line_code(line) - position = diff_file.position(line) - - case line.type - when 'match', nil + if line.meta? || line.unchanged? # line in the right panel is the same as in the left one lines << { - left: { - type: line.type, - number: line.old_pos, - text: line.text, - line_code: line_code, - position: position - }, - right: { - type: line.type, - number: line.new_pos, - text: line.text, - line_code: line_code, - position: position - } + left: line, + right: line } free_right_index = nil i += 1 - when 'old' + elsif line.removed? lines << { - left: { - type: line.type, - number: line.old_pos, - text: line.text, - line_code: line_code, - position: position - }, - right: { - type: nil, - number: nil, - text: "", - line_code: line_code, - position: position - } + left: line, + right: nil } # Once we come upon a new line it can be put on the right of this old line free_right_index ||= i i += 1 - when 'new' - data = { - type: line.type, - number: line.new_pos, - text: line.text, - line_code: line_code, - position: position - } - + elsif line.added? if free_right_index # If an old line came before this without a line on the right, this # line can be put to the right of it. - lines[free_right_index][:right] = data + lines[free_right_index][:right] = line # If there are any other old lines on the left that don't yet have # a new counterpart on the right, update the free_right_index @@ -81,14 +44,8 @@ module Gitlab free_right_index = next_free_right_index < i ? next_free_right_index : nil else lines << { - left: { - type: nil, - number: nil, - text: "", - line_code: line_code, - position: position - }, - right: data + left: nil, + right: line } free_right_index = nil diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 0893ee89f6a..71d0e4be834 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -14,9 +14,9 @@ describe Projects::UploadsController do context "without params['file']" do it "returns an error" do - post :create, + post :create, namespace_id: project.namespace.to_param, - project_id: project.to_param, + project_id: project.to_param, format: :json expect(response).to have_http_status(422) end @@ -34,23 +34,21 @@ describe Projects::UploadsController do it 'returns a content with original filename, new link, and correct type.' do expect(response.body).to match '\"alt\":\"rails_sample\"' expect(response.body).to match "\"url\":\"/uploads" - expect(response.body).to match '\"is_image\":true' end end context 'with valid non-image file' do before do - post :create, + post :create, namespace_id: project.namespace.to_param, - project_id: project.to_param, - file: txt, + project_id: project.to_param, + file: txt, format: :json end it 'returns a content with original filename, new link, and correct type.' do expect(response.body).to match '\"alt\":\"doc_sample.txt\"' expect(response.body).to match "\"url\":\"/uploads" - expect(response.body).to match '\"is_image\":false' end end end diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index e72aa9479b7..2c0a2dd94ca 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -18,5 +18,15 @@ FactoryGirl.define do factory :closed_issue, traits: [:closed] factory :reopened_issue, traits: [:reopened] + + factory :labeled_issue do + transient do + labels [] + end + + after(:create) do |issue, evaluator| + issue.update_attributes(labels: evaluator.labels) + end + end end end diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 9c018be14b7..a7d9f2a0c72 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -92,8 +92,8 @@ feature 'Environments', feature: true do expect(page).to have_link(deployment.short_sha) end - scenario 'does not show a retry button for deployment without build' do - expect(page).not_to have_link('Retry') + scenario 'does not show a re-deploy button for deployment without build' do + expect(page).not_to have_link('Re-deploy') end context 'with build' do @@ -105,8 +105,8 @@ feature 'Environments', feature: true do expect(page).to have_link("#{build.name} (##{build.id})") end - scenario 'does show retry button' do - expect(page).to have_link('Retry') + scenario 'does show re-deploy button' do + expect(page).to have_link('Re-deploy') end context 'with manual action' do diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 09ccc77c101..32159559c37 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -236,6 +236,14 @@ describe 'GitLab Markdown', feature: true do it 'includes TaskListFilter' do expect(doc).to parse_task_lists end + + it 'includes InlineDiffFilter' do + expect(doc).to parse_inline_diffs + end + + it 'includes VideoLinkFilter' do + expect(doc).to parse_video_links + end end context 'wiki pipeline' do @@ -293,6 +301,10 @@ describe 'GitLab Markdown', feature: true do it 'includes InlineDiffFilter' do expect(doc).to parse_inline_diffs end + + it 'includes VideoLinkFilter' do + expect(doc).to parse_video_links + end end # Fake a `current_user` helper diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index bc3bf53fe9d..2d1e3bbebe5 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -59,6 +59,21 @@ feature 'project import', feature: true, js: true do end end + scenario 'project with no name' do + create(:project, namespace_id: 2) + + visit new_project_path + + select2('2', from: '#project_namespace_id') + + # click on disabled element + find(:link, 'GitLab export').trigger('click') + + page.within('.flash-container') do + expect(page).to have_content('Please enter path and name') + end + end + def wiki_exists? wiki = ProjectWiki.new(project) File.exist?(wiki.repository.path_to_repo) && !wiki.repository.empty? diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index c75d28d9801..f3e7c2d1a9f 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -256,3 +256,7 @@ However the wrapping tags can not be mixed as such - - [+ additions +} - {- delletions -] - [- delletions -} + +### Videos + +![My Video](/assets/videos/gitlab-demo.mp4) diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml deleted file mode 100644 index 37066c8e930..00000000000 --- a/spec/fixtures/parallel_diff_result.yml +++ /dev/null @@ -1,800 +0,0 @@ ---- -- :left: - :type: match - :number: 6 - :text: "@@ -6,12 +6,18 @@ module Popen" - :line_code: - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: match - :number: 6 - :text: "@@ -6,12 +6,18 @@ module Popen" - :line_code: - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 6 - :text: |2 - <span id="LC6" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 6 - :new_line: 6 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 6 - :text: |2 - <span id="LC6" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 6 - :new_line: 6 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 7 - :text: |2 - <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 7 - :new_line: 7 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 7 - :text: |2 - <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 7 - :new_line: 7 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 8 - :text: |2 - <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 8 - :new_line: 8 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 8 - :text: |2 - <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 8 - :new_line: 8 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: old - :number: 9 - :text: | - -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 9 - :new_line: - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 9 - :text: | - +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 9 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 10 - :text: |2 - <span id="LC10" class="line"> <span class="k">end</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 10 - :new_line: 10 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 10 - :text: |2 - <span id="LC10" class="line"> <span class="k">end</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 10 - :new_line: 10 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 11 - :text: |2 - <span id="LC11" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 11 - :new_line: 11 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 11 - :text: |2 - <span id="LC11" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 11 - :new_line: 11 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 12 - :text: |2 - <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 12 - :new_line: 12 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 12 - :text: |2 - <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 12 - :new_line: 12 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: old - :number: 13 - :text: | - -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 13 - :new_line: - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 13 - :text: | - +<span id="LC13" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 13 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: old - :number: 14 - :text: | - -<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 14 - :new_line: - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 14 - :text: | - +<span id="LC14" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 14 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: - :text: '' - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 15 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 15 - :text: | - +<span id="LC15" class="line"> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 15 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: - :text: '' - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 16 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 16 - :text: | - +<span id="LC16" class="line"> <span class="p">}</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 16 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: - :text: '' - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 17 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 17 - :text: | - +<span id="LC17" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 17 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: - :text: '' - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 18 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 18 - :text: | - +<span id="LC18" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 18 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: - :text: '' - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 19 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 19 - :text: | - +<span id="LC19" class="line"> <span class="ss">chdir: </span><span class="n">path</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 19 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: - :text: '' - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 20 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 20 - :text: | - +<span id="LC20" class="line"> <span class="p">}</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 20 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 15 - :text: |2 - <span id="LC21" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 15 - :new_line: 21 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 21 - :text: |2 - <span id="LC21" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 15 - :new_line: 21 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 16 - :text: |2 - <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 16 - :new_line: 22 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 22 - :text: |2 - <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 16 - :new_line: 22 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 17 - :text: |2 - <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 17 - :new_line: 23 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 23 - :text: |2 - <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 17 - :new_line: 23 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: match - :number: 19 - :text: "@@ -19,6 +25,7 @@ module Popen" - :line_code: - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: match - :number: 25 - :text: "@@ -19,6 +25,7 @@ module Popen" - :line_code: - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 19 - :text: |2 - <span id="LC25" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 19 - :new_line: 25 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 25 - :text: |2 - <span id="LC25" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 19 - :new_line: 25 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 20 - :text: |2 - <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 20 - :new_line: 26 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 26 - :text: |2 - <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 20 - :new_line: 26 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 21 - :text: |2 - <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 21 - :new_line: 27 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 27 - :text: |2 - <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 21 - :new_line: 27 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: - :text: '' - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 28 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: new - :number: 28 - :text: | - +<span id="LC28" class="line"></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: - :new_line: 28 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 22 - :text: |2 - <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 22 - :new_line: 29 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 29 - :text: |2 - <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 22 - :new_line: 29 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 23 - :text: |2 - <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 23 - :new_line: 30 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 30 - :text: |2 - <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 23 - :new_line: 30 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- :left: - :type: - :number: 24 - :text: |2 - <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 24 - :new_line: 31 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :right: - :type: - :number: 31 - :text: |2 - <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span> - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31 - :position: !ruby/object:Gitlab::Diff::Position - attributes: - :old_path: files/ruby/popen.rb - :new_path: files/ruby/popen.rb - :old_line: 24 - :new_line: 31 - :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d diff --git a/spec/fixtures/video_sample.mp4 b/spec/fixtures/video_sample.mp4 Binary files differnew file mode 100644 index 00000000000..acd45190998 --- /dev/null +++ b/spec/fixtures/video_sample.mp4 diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb index 413ead944b9..bf3ed5c094c 100644 --- a/spec/helpers/time_helper_spec.rb +++ b/spec/helpers/time_helper_spec.rb @@ -5,6 +5,7 @@ describe TimeHelper do it "returns minutes and seconds" do intervals_in_words = { 100 => "1 minute 40 seconds", + 100.32 => "1 minute 40 seconds", 121 => "2 minutes 1 second", 3721 => "62 minutes 1 second", 0 => "0 seconds" diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb index 5178bd130f4..baab30f482f 100644 --- a/spec/initializers/6_validations_spec.rb +++ b/spec/initializers/6_validations_spec.rb @@ -1,41 +1,58 @@ require 'spec_helper' +require_relative '../../config/initializers/6_validations.rb' describe '6_validations', lib: true do + before :all do + FileUtils.mkdir_p('tmp/tests/paths/a/b/c/d') + FileUtils.mkdir_p('tmp/tests/paths/a/b/c2') + FileUtils.mkdir_p('tmp/tests/paths/a/b/d') + end + + after :all do + FileUtils.rm_rf('tmp/tests/paths') + end + context 'with correct settings' do before do - mock_storages('foo' => '/a/b/c', 'bar' => 'a/b/d') + mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/d') end it 'passes through' do - expect { load_validations }.not_to raise_error + expect { validate_storages }.not_to raise_error end end context 'with invalid storage names' do before do - mock_storages('name with spaces' => '/a/b/c') + mock_storages('name with spaces' => 'tmp/tests/paths/a/b/c') end it 'throws an error' do - expect { load_validations }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.') + expect { validate_storages }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.') end end context 'with nested storage paths' do before do - mock_storages('foo' => '/a/b/c', 'bar' => '/a/b/c/d') + mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c/d') end it 'throws an error' do - expect { load_validations }.to raise_error('bar is a nested path of foo. Nested paths are not supported for repository storages. Please fix this in your gitlab.yml before starting GitLab.') + expect { validate_storages }.to raise_error('bar is a nested path of foo. Nested paths are not supported for repository storages. Please fix this in your gitlab.yml before starting GitLab.') end end - def mock_storages(storages) - allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + context 'with similar but un-nested storage paths' do + before do + mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c2') + end + + it 'passes through' do + expect { validate_storages }.not_to raise_error + end end - def load_validations - load File.join(__dir__, '../../config/initializers/6_validations.rb') + def mock_storages(storages) + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) end end diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb new file mode 100644 index 00000000000..cc4349f80ba --- /dev/null +++ b/spec/lib/banzai/filter/video_link_filter_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Banzai::Filter::VideoLinkFilter, lib: true do + def filter(doc, contexts = {}) + contexts.reverse_merge!({ + project: project + }) + + described_class.call(doc, contexts) + end + + def link_to_image(path) + %(<img src="#{path}" />) + end + + let(:project) { create(:project) } + + context 'when the element src has a video extension' do + UploaderHelper::VIDEO_EXT.each do |ext| + it "replaces the image tag 'path/video.#{ext}' with a video tag" do + container = filter(link_to_image("/path/video.#{ext}")).children.first + + expect(container.name).to eq 'div' + expect(container['class']).to eq 'video-container' + + video, paragraph = container.children + + expect(video.name).to eq 'video' + expect(video['src']).to eq "/path/video.#{ext}" + + expect(paragraph.name).to eq 'p' + + link = paragraph.children.first + + expect(link.name).to eq 'a' + expect(link['href']).to eq "/path/video.#{ext}" + expect(link['target']).to eq '_blank' + end + end + end + + context 'when the element src is an image' do + it 'leaves the document unchanged' do + element = filter(link_to_image('/path/my_image.jpg')).children.first + + expect(element.name).to eq 'img' + expect(element['src']).to eq '/path/my_image.jpg' + end + end + +end diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb index 5f76b70c6f5..2aa5ae44f54 100644 --- a/spec/lib/gitlab/diff/parallel_diff_spec.rb +++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb @@ -11,11 +11,51 @@ describe Gitlab::Diff::ParallelDiff, lib: true do let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) } subject { described_class.new(diff_file) } - let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") } - describe '#parallelize' do it 'should return an array of arrays containing the parsed diff' do - expect(subject.parallelize).to match_array(parallel_diff_result_array) + diff_lines = diff_file.highlighted_diff_lines + expected = [ + # Unchanged lines + { left: diff_lines[0], right: diff_lines[0] }, + { left: diff_lines[1], right: diff_lines[1] }, + { left: diff_lines[2], right: diff_lines[2] }, + { left: diff_lines[3], right: diff_lines[3] }, + { left: diff_lines[4], right: diff_lines[5] }, + { left: diff_lines[6], right: diff_lines[6] }, + { left: diff_lines[7], right: diff_lines[7] }, + { left: diff_lines[8], right: diff_lines[8] }, + + # Changed lines + { left: diff_lines[9], right: diff_lines[11] }, + { left: diff_lines[10], right: diff_lines[12] }, + + # Added lines + { left: nil, right: diff_lines[13] }, + { left: nil, right: diff_lines[14] }, + { left: nil, right: diff_lines[15] }, + { left: nil, right: diff_lines[16] }, + { left: nil, right: diff_lines[17] }, + { left: nil, right: diff_lines[18] }, + + # Unchanged lines + { left: diff_lines[19], right: diff_lines[19] }, + { left: diff_lines[20], right: diff_lines[20] }, + { left: diff_lines[21], right: diff_lines[21] }, + { left: diff_lines[22], right: diff_lines[22] }, + { left: diff_lines[23], right: diff_lines[23] }, + { left: diff_lines[24], right: diff_lines[24] }, + { left: diff_lines[25], right: diff_lines[25] }, + + # Added line + { left: nil, right: diff_lines[26] }, + + # Unchanged lines + { left: diff_lines[27], right: diff_lines[27] }, + { left: diff_lines[28], right: diff_lines[28] }, + { left: diff_lines[29], right: diff_lines[29] } + ] + + expect(subject.parallelize).to eq(expected) end end end diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb index 476a21bf996..08b2577ecc4 100644 --- a/spec/lib/gitlab/email/attachment_uploader_spec.rb +++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb @@ -11,7 +11,6 @@ describe Gitlab::Email::AttachmentUploader, lib: true do link = links.first expect(link).not_to be_nil - expect(link[:is_image]).to be_truthy expect(link[:alt]).to eq("bricks") expect(link[:url]).to include("bricks.png") end diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 36267faeb93..84d2584a791 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -115,7 +115,6 @@ describe Gitlab::Email::Receiver, lib: true do [ { url: "uploads/image.png", - is_image: true, alt: "image", markdown: markdown } diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 2de305738cc..978ad9c52d5 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -5,7 +5,9 @@ describe Ci::Build, models: true do let(:pipeline) do create(:ci_pipeline, project: project, - sha: project.commit.id) + sha: project.commit.id, + ref: project.default_branch, + status: 'success') end let(:build) { create(:ci_build, pipeline: pipeline) } @@ -720,7 +722,7 @@ describe Ci::Build, models: true do describe '#erasable?' do subject { build.erasable? } - it { is_expected.to eq true } + it { is_expected.to be_truthy } end describe '#erased?' do @@ -728,7 +730,7 @@ describe Ci::Build, models: true do subject { build.erased? } context 'build has not been erased' do - it { is_expected.to be false } + it { is_expected.to be_falsey } end context 'build has been erased' do @@ -736,12 +738,13 @@ describe Ci::Build, models: true do build.erase end - it { is_expected.to be true } + it { is_expected.to be_truthy } end end context 'metadata and build trace are not available' do let!(:build) { create(:ci_build, :success, :artifacts) } + before do build.remove_artifacts_metadata! end @@ -763,19 +766,19 @@ describe Ci::Build, models: true do describe '#retryable?' do context 'when build is running' do - before { build.run! } - - it 'should return false' do - expect(build.retryable?).to be false + before do + build.run! end + + it { expect(build).not_to be_retryable } end context 'when build is finished' do - before { build.success! } - - it 'should return true' do - expect(build.retryable?).to be true + before do + build.success! end + + it { expect(build).to be_retryable } end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 7d0697dab42..1243f5420a7 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -135,22 +135,30 @@ describe Note, models: true do let!(:note2) { create(:note_on_issue) } it "reads the rendered note body from the cache" do - expect(Banzai::Renderer).to receive(:render). - with(note1.note, - pipeline: :note, - cache_key: [note1, "note"], - project: note1.project, - author: note1.author) - - expect(Banzai::Renderer).to receive(:render). - with(note2.note, - pipeline: :note, - cache_key: [note2, "note"], - project: note2.project, - author: note2.author) - - note1.all_references - note2.all_references + expect(Banzai::Renderer).to receive(:cache_collection_render). + with([{ + text: note1.note, + context: { + pipeline: :note, + cache_key: [note1, "note"], + project: note1.project, + author: note1.author + } + }]).and_call_original + + expect(Banzai::Renderer).to receive(:cache_collection_render). + with([{ + text: note2.note, + context: { + pipeline: :note, + cache_key: [note2, "note"], + project: note2.project, + author: note2.author + } + }]).and_call_original + + note1.all_references.users + note2.all_references.users end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a2b089c51e2..9b017288488 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -377,7 +377,7 @@ describe Project, models: true do describe '#repository' do let(:project) { create(:project) } - it 'should return valid repo' do + it 'returns valid repo' do expect(project.repository).to be_kind_of(Repository) end end @@ -458,44 +458,54 @@ describe Project, models: true do end end - describe "#cache_has_external_wiki" do + describe '#external_wiki' do let(:project) { create(:project) } - it "stores true if there is an external wiki" do - services = double(:service, external_wikis: [ExternalWikiService.new]) - expect(project).to receive(:services).and_return(services) + context 'with an active external wiki' do + before do + create(:service, project: project, type: 'ExternalWikiService', active: true) + project.external_wiki + end - expect do - project.cache_has_external_wiki - end.to change { project.has_external_wiki }.to(true) - end + it 'sets :has_external_wiki as true' do + expect(project.has_external_wiki).to be(true) + end - it "stores false if there is no external wiki" do - services = double(:service, external_wikis: []) - expect(project).to receive(:services).and_return(services) + it 'sets :has_external_wiki as false if an external wiki service is destroyed later' do + expect(project.has_external_wiki).to be(true) - expect do - project.cache_has_external_wiki - end.to change { project.has_external_wiki }.to(false) + project.services.external_wikis.first.destroy + + expect(project.has_external_wiki).to be(false) + end end - it "changes to true if an external wiki service is created later" do - expect do - project.cache_has_external_wiki - end.to change { project.has_external_wiki }.to(false) + context 'with an inactive external wiki' do + before do + create(:service, project: project, type: 'ExternalWikiService', active: false) + end - expect do - create(:service, type: "ExternalWikiService", project: project) - end.to change { project.has_external_wiki }.to(true) + it 'sets :has_external_wiki as false' do + expect(project.has_external_wiki).to be(false) + end end - it "changes to false if an external wiki service is destroyed later" do - service = create(:service, type: "ExternalWikiService", project: project) - expect(project.has_external_wiki).to be_truthy + context 'with no external wiki' do + before do + project.external_wiki + end - expect do - service.destroy - end.to change { project.has_external_wiki }.to(false) + it 'sets :has_external_wiki as false' do + expect(project.has_external_wiki).to be(false) + end + + it 'sets :has_external_wiki as true if an external wiki service is created later' do + expect(project.has_external_wiki).to be(false) + + create(:service, project: project, type: 'ExternalWikiService', active: true) + + expect(project.has_external_wiki).to be(true) + end end end @@ -1155,6 +1165,85 @@ describe Project, models: true do end end + describe '#latest_successful_builds_for' do + def create_pipeline(status = 'success') + create(:ci_pipeline, project: project, + sha: project.commit.sha, + ref: project.default_branch, + status: status) + end + + def create_build(new_pipeline = pipeline, name = 'test') + create(:ci_build, :success, :artifacts, + pipeline: new_pipeline, + status: new_pipeline.status, + name: name) + end + + let(:project) { create(:project) } + let(:pipeline) { create_pipeline } + + context 'with many builds' do + it 'gives the latest builds from latest pipeline' do + pipeline1 = create_pipeline + pipeline2 = create_pipeline + build1_p2 = create_build(pipeline2, 'test') + create_build(pipeline1, 'test') + create_build(pipeline1, 'test2') + build2_p2 = create_build(pipeline2, 'test2') + + latest_builds = project.latest_successful_builds_for + + expect(latest_builds).to contain_exactly(build2_p2, build1_p2) + end + end + + context 'with succeeded pipeline' do + let!(:build) { create_build } + + context 'standalone pipeline' do + it 'returns builds for ref for default_branch' do + builds = project.latest_successful_builds_for + + expect(builds).to contain_exactly(build) + end + + it 'returns empty relation if the build cannot be found' do + builds = project.latest_successful_builds_for('TAIL') + + expect(builds).to be_kind_of(ActiveRecord::Relation) + expect(builds).to be_empty + end + end + + context 'with some pending pipeline' do + before do + create_build(create_pipeline('pending')) + end + + it 'gives the latest build from latest pipeline' do + latest_build = project.latest_successful_builds_for + + expect(latest_build).to contain_exactly(build) + end + end + end + + context 'with pending pipeline' do + before do + pipeline.update(status: 'pending') + create_build(pipeline) + end + + it 'returns empty relation' do + builds = project.latest_successful_builds_for + + expect(builds).to be_kind_of(ActiveRecord::Relation) + expect(builds).to be_empty + end + end + end + describe '.where_paths_in' do context 'without any paths' do it 'returns an empty relation' do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 59c5732c075..3e133143bbc 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -50,8 +50,9 @@ describe Repository, models: true do double_first = double(committed_date: Time.now) double_last = double(committed_date: Time.now - 1.second) - allow(repository).to receive(:commit).with(tag_a.target).and_return(double_first) - allow(repository).to receive(:commit).with(tag_b.target).and_return(double_last) + allow(tag_a).to receive(:target).and_return(double_first) + allow(tag_b).to receive(:target).and_return(double_last) + allow(repository).to receive(:tags).and_return([tag_a, tag_b]) end it { is_expected.to eq(['v1.0.0', 'v1.1.0']) } @@ -64,8 +65,9 @@ describe Repository, models: true do double_first = double(committed_date: Time.now - 1.second) double_last = double(committed_date: Time.now) - allow(repository).to receive(:commit).with(tag_a.target).and_return(double_last) - allow(repository).to receive(:commit).with(tag_b.target).and_return(double_first) + allow(tag_a).to receive(:target).and_return(double_last) + allow(tag_b).to receive(:target).and_return(double_first) + allow(repository).to receive(:tags).and_return([tag_a, tag_b]) end it { is_expected.to eq(['v1.1.0', 'v1.0.0']) } @@ -1161,41 +1163,6 @@ describe Repository, models: true do end end - describe '#local_branches' do - it 'returns the local branches' do - masterrev = repository.find_branch('master').target - create_remote_branch('joe', 'remote_branch', masterrev) - repository.add_branch(user, 'local_branch', masterrev) - - expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false) - expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true) - end - end - - describe '.clean_old_archives' do - let(:path) { Gitlab.config.gitlab.repository_downloads_path } - - context 'when the downloads directory does not exist' do - it 'does not remove any archives' do - expect(File).to receive(:directory?).with(path).and_return(false) - - expect(Gitlab::Popen).not_to receive(:popen) - - described_class.clean_old_archives - end - end - - context 'when the downloads directory exists' do - it 'removes old archives' do - expect(File).to receive(:directory?).with(path).and_return(true) - - expect(Gitlab::Popen).to receive(:popen) - - described_class.clean_old_archives - end - end - end - describe "#keep_around" do it "stores a reference to the specified commit sha so it isn't garbage collected" do repository.keep_around(sample_commit.id) @@ -1203,9 +1170,4 @@ describe Repository, models: true do expect(repository.kept_around?(sample_commit.id)).to be_truthy end end - - def create_remote_branch(remote_name, branch_name, target) - rugged = repository.rugged - rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target) - end end diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index f5b39c3d698..86a7b242fbe 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -1,15 +1,15 @@ require 'spec_helper' -describe API::API, api: true do +describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } let(:api_user) { user } - let(:user2) { create(:user) } 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, sha: project.commit.id) } + let(:reporter) { create(:project_member, :reporter, project: project) } + let(:guest) { create(:project_member, :guest, project: project) } + let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) } let!(:build) { create(:ci_build, pipeline: pipeline) } describe 'GET /projects/:id/builds ' do @@ -172,10 +172,104 @@ describe API::API, api: true do end end + describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do + let(:api_user) { reporter.user } + let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } + + def path_for_ref(ref = pipeline.ref, job = build.name) + api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", api_user) + end + + context 'when not logged in' do + let(:api_user) { nil } + + before do + get path_for_ref + end + + it 'gives 401' do + expect(response).to have_http_status(401) + end + end + + context 'when logging as guest' do + let(:api_user) { guest.user } + + before do + get path_for_ref + end + + it 'gives 403' do + expect(response).to have_http_status(403) + end + end + + context 'non-existing build' do + shared_examples 'not found' do + it { expect(response).to have_http_status(:not_found) } + end + + context 'has no such ref' do + before do + get path_for_ref('TAIL', build.name) + end + + it_behaves_like 'not found' + end + + context 'has no such build' do + before do + get path_for_ref(pipeline.ref, 'NOBUILD') + end + + it_behaves_like 'not found' + end + end + + context 'find proper build' do + shared_examples 'a valid file' do + let(:download_headers) do + { 'Content-Transfer-Encoding' => 'binary', + 'Content-Disposition' => + "attachment; filename=#{build.artifacts_file.filename}" } + end + + it { expect(response).to have_http_status(200) } + it { expect(response.headers).to include(download_headers) } + end + + context 'with regular branch' do + before do + pipeline.update(ref: 'master', + sha: project.commit('master').sha) + + get path_for_ref('master') + end + + it_behaves_like 'a valid file' + end + + context 'with branch name containing slash' do + before do + pipeline.update(ref: 'improve/awesome', + sha: project.commit('improve/awesome').sha) + end + + before do + get path_for_ref('improve/awesome') + end + + it_behaves_like 'a valid file' + end + 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) } + before do + get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) + end context 'authorized user' do it 'should return specific build trace' do @@ -205,7 +299,7 @@ describe API::API, api: true do end context 'user without :update_build permission' do - let(:api_user) { user2 } + let(:api_user) { reporter.user } it 'should not cancel build' do expect(response).to have_http_status(403) @@ -237,7 +331,7 @@ describe API::API, api: true do end context 'user without :update_build permission' do - let(:api_user) { user2 } + let(:api_user) { reporter.user } it 'should not retry build' do expect(response).to have_http_status(403) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index afa0599807f..8c6a7e6529d 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -396,7 +396,6 @@ describe API::API, api: true do expect(json_response['alt']).to eq("dk") expect(json_response['url']).to start_with("/uploads/") expect(json_response['url']).to end_with("/dk.png") - expect(json_response['is_image']).to eq(true) end end diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb index ba3a4dfc048..321b54ac39d 100644 --- a/spec/services/issues/bulk_update_service_spec.rb +++ b/spec/services/issues/bulk_update_service_spec.rb @@ -1,118 +1,106 @@ require 'spec_helper' describe Issues::BulkUpdateService, services: true do - let(:user) { create(:user) } - let(:project) { Projects::CreateService.new(user, namespace: user.namespace, name: 'test').execute } + let(:user) { create(:user) } + let(:project) { create(:empty_project, namespace: user.namespace) } - let!(:result) { Issues::BulkUpdateService.new(project, user, params).execute } + def bulk_update(issues, extra_params = {}) + bulk_update_params = extra_params + .reverse_merge(issues_ids: Array(issues).map(&:id).join(',')) - describe :close_issue do - let(:issues) { create_list(:issue, 5, project: project) } - let(:params) do - { - state_event: 'close', - issues_ids: issues.map(&:id).join(',') - } - end + Issues::BulkUpdateService.new(project, user, bulk_update_params).execute + end + + describe 'close issues' do + let(:issues) { create_list(:issue, 2, project: project) } it 'succeeds and returns the correct number of issues updated' do + result = bulk_update(issues, state_event: 'close') + expect(result[:success]).to be_truthy expect(result[:count]).to eq(issues.count) end it 'closes all the issues passed' do + bulk_update(issues, state_event: 'close') + expect(project.issues.opened).to be_empty expect(project.issues.closed).not_to be_empty end end - describe :reopen_issues do - let(:issues) { create_list(:closed_issue, 5, project: project) } - let(:params) do - { - state_event: 'reopen', - issues_ids: issues.map(&:id).join(',') - } - end + describe 'reopen issues' do + let(:issues) { create_list(:closed_issue, 2, project: project) } it 'succeeds and returns the correct number of issues updated' do + result = bulk_update(issues, state_event: 'reopen') + expect(result[:success]).to be_truthy expect(result[:count]).to eq(issues.count) end it 'reopens all the issues passed' do + bulk_update(issues, state_event: 'reopen') + expect(project.issues.closed).to be_empty expect(project.issues.opened).not_to be_empty end end describe 'updating assignee' do - let(:issue) do - create(:issue, project: project) { |issue| issue.update_attributes(assignee: user) } - end - - let(:params) do - { - assignee_id: assignee_id, - issues_ids: issue.id.to_s - } - end + let(:issue) { create(:issue, project: project, assignee: user) } context 'when the new assignee ID is a valid user' do - let(:new_assignee) { create(:user) } - let(:assignee_id) { new_assignee.id } - it 'succeeds' do + result = bulk_update(issue, assignee_id: create(:user).id) + expect(result[:success]).to be_truthy expect(result[:count]).to eq(1) end it 'updates the assignee to the use ID passed' do - expect(issue.reload.assignee).to eq(new_assignee) + assignee = create(:user) + + expect { bulk_update(issue, assignee_id: assignee.id) } + .to change { issue.reload.assignee }.from(user).to(assignee) end end context 'when the new assignee ID is -1' do - let(:assignee_id) { -1 } - it 'unassigns the issues' do - expect(issue.reload.assignee).to be_nil + expect { bulk_update(issue, assignee_id: -1) } + .to change { issue.reload.assignee }.to(nil) end end context 'when the new assignee ID is not present' do - let(:assignee_id) { nil } - it 'does not unassign' do - expect(issue.reload.assignee).to eq(user) + expect { bulk_update(issue, assignee_id: nil) } + .not_to change { issue.reload.assignee } end end end describe 'updating milestones' do - let(:issue) { create(:issue, project: project) } + let(:issue) { create(:issue, project: project) } let(:milestone) { create(:milestone, project: project) } - let(:params) do - { - issues_ids: issue.id.to_s, - milestone_id: milestone.id - } - end - it 'succeeds' do + result = bulk_update(issue, milestone_id: milestone.id) + expect(result[:success]).to be_truthy expect(result[:count]).to eq(1) end it 'updates the issue milestone' do - expect(project.issues.first.milestone).to eq(milestone) + expect { bulk_update(issue, milestone_id: milestone.id) } + .to change { issue.reload.milestone }.from(nil).to(milestone) end end describe 'updating labels' do def create_issue_with_labels(labels) - create(:issue, project: project) { |issue| issue.update_attributes(labels: labels) } + create(:labeled_issue, project: project, labels: labels) end let(:bug) { create(:label, project: project) } @@ -129,15 +117,18 @@ describe Issues::BulkUpdateService, services: true do let(:add_labels) { [] } let(:remove_labels) { [] } - let(:params) do + let(:bulk_update_params) do { - label_ids: labels.map(&:id), - add_label_ids: add_labels.map(&:id), + label_ids: labels.map(&:id), + add_label_ids: add_labels.map(&:id), remove_label_ids: remove_labels.map(&:id), - issues_ids: issues.map(&:id).join(',') } end + before do + bulk_update(issues, bulk_update_params) + end + context 'when label_ids are passed' do let(:issues) { [issue_all_labels, issue_no_labels] } let(:labels) { [bug, regression] } @@ -263,40 +254,28 @@ describe Issues::BulkUpdateService, services: true do end end - describe :subscribe_issues do - let(:issues) { create_list(:issue, 5, project: project) } - let(:params) do - { - subscription_event: 'subscribe', - issues_ids: issues.map(&:id).join(',') - } - end + describe 'subscribe to issues' do + let(:issues) { create_list(:issue, 2, project: project) } it 'subscribes the given user' do - issues.each do |issue| - expect(issue.subscribed?(user)).to be_truthy - end - end - end + bulk_update(issues, subscription_event: 'subscribe') - describe :unsubscribe_issues do - let(:issues) { create_list(:closed_issue, 5, project: project) } - let(:params) do - { - subscription_event: 'unsubscribe', - issues_ids: issues.map(&:id).join(',') - } + expect(issues).to all(be_subscribed(user)) end + end - before do - issues.each do |issue| + describe 'unsubscribe from issues' do + let(:issues) do + create_list(:closed_issue, 2, project: project) do |issue| issue.subscriptions.create(user: user, subscribed: true) end end it 'unsubscribes the given user' do + bulk_update(issues, subscription_event: 'unsubscribe') + issues.each do |issue| - expect(issue.subscribed?(user)).to be_falsey + expect(issue).not_to be_subscribed(user) end end end diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index f252e2c5902..122a7cea2a1 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -35,8 +35,6 @@ describe Projects::DownloadService, services: true do it { expect(@link_to_file).to have_key(:alt) } it { expect(@link_to_file).to have_key(:url) } - it { expect(@link_to_file).to have_key(:is_image) } - it { expect(@link_to_file[:is_image]).to be true } it { expect(@link_to_file[:url]).to match('rails_sample.jpg') } it { expect(@link_to_file[:alt]).to eq('rails_sample') } end @@ -49,8 +47,6 @@ describe Projects::DownloadService, services: true do it { expect(@link_to_file).to have_key(:alt) } it { expect(@link_to_file).to have_key(:url) } - it { expect(@link_to_file).to have_key(:is_image) } - it { expect(@link_to_file[:is_image]).to be false } it { expect(@link_to_file[:url]).to match('doc_sample.txt') } it { expect(@link_to_file[:alt]).to eq('doc_sample.txt') } end diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb index 9268a9fb1a2..c42eeba4b9c 100644 --- a/spec/services/projects/upload_service_spec.rb +++ b/spec/services/projects/upload_service_spec.rb @@ -15,9 +15,7 @@ describe Projects::UploadService, services: true do it { expect(@link_to_file).to have_key(:alt) } it { expect(@link_to_file).to have_key(:url) } - it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('banana_sample') } - it { expect(@link_to_file[:is_image]).to equal(true) } it { expect(@link_to_file[:url]).to match('banana_sample.gif') } end @@ -31,8 +29,6 @@ describe Projects::UploadService, services: true do it { expect(@link_to_file).to have_key(:alt) } it { expect(@link_to_file).to have_key(:url) } it { expect(@link_to_file).to have_value('dk') } - it { expect(@link_to_file).to have_key(:is_image) } - it { expect(@link_to_file[:is_image]).to equal(true) } it { expect(@link_to_file[:url]).to match('dk.png') } end @@ -44,9 +40,7 @@ describe Projects::UploadService, services: true do it { expect(@link_to_file).to have_key(:alt) } it { expect(@link_to_file).to have_key(:url) } - it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('rails_sample') } - it { expect(@link_to_file[:is_image]).to equal(true) } it { expect(@link_to_file[:url]).to match('rails_sample.jpg') } end @@ -58,9 +52,7 @@ describe Projects::UploadService, services: true do it { expect(@link_to_file).to have_key(:alt) } it { expect(@link_to_file).to have_key(:url) } - it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('doc_sample.txt') } - it { expect(@link_to_file[:is_image]).to equal(false) } it { expect(@link_to_file[:url]).to match('doc_sample.txt') } end diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb new file mode 100644 index 00000000000..842585f9e54 --- /dev/null +++ b/spec/services/repository_archive_clean_up_service_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +describe RepositoryArchiveCleanUpService, services: true do + describe '#execute' do + subject(:service) { described_class.new } + + context 'when the downloads directory does not exist' do + it 'does not remove any archives' do + path = '/invalid/path/' + stub_repository_downloads_path(path) + + expect(File).to receive(:directory?).with(path).and_return(false) + expect(service).not_to receive(:clean_up_old_archives) + expect(service).not_to receive(:clean_up_empty_directories) + + service.execute + end + end + + context 'when the downloads directory exists' do + shared_examples 'invalid archive files' do |dirname, extensions, mtime| + it 'does not remove files and directoy' do + in_directory_with_files(dirname, extensions, mtime) do |dir, files| + service.execute + + files.each { |file| expect(File.exist?(file)).to eq true } + expect(File.directory?(dir)).to eq true + end + end + end + + it 'removes files older than 2 hours that matches valid archive extensions' do + in_directory_with_files('sample.git', %w[tar tar.bz2 tar.gz zip], 2.hours) do |dir, files| + service.execute + + files.each { |file| expect(File.exist?(file)).to eq false } + expect(File.directory?(dir)).to eq false + end + end + + context 'with files older than 2 hours that does not matches valid archive extensions' do + it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb], 2.hours + end + + context 'with files older than 2 hours inside invalid directories' do + it_behaves_like 'invalid archive files', 'john_doe/sample.git', %w[conf rb tar tar.gz], 2.hours + end + + context 'with files newer than 2 hours that matches valid archive extensions' do + it_behaves_like 'invalid archive files', 'sample.git', %w[tar tar.bz2 tar.gz zip], 1.hour + end + + context 'with files newer than 2 hours that does not matches valid archive extensions' do + it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb], 1.hour + end + + context 'with files newer than 2 hours inside invalid directories' do + it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb tar tar.gz], 1.hour + end + end + + def in_directory_with_files(dirname, extensions, mtime) + Dir.mktmpdir do |tmpdir| + stub_repository_downloads_path(tmpdir) + dir = File.join(tmpdir, dirname) + files = create_temporary_files(dir, extensions, mtime) + + yield(dir, files) + end + end + + def stub_repository_downloads_path(path) + allow(Gitlab.config.gitlab).to receive(:repository_downloads_path).and_return(path) + end + + def create_temporary_files(dir, extensions, mtime) + FileUtils.mkdir_p(dir) + FileUtils.touch(extensions.map { |ext| File.join(dir, "sample.#{ext}") }, mtime: Time.now - mtime) + end + end +end diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index e005058ba5b..8c98b1f988c 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -178,6 +178,17 @@ module MarkdownMatchers expect(actual).to have_selector('span.idiff.deletion', count: 2) end end + + # VideoLinkFilter + matcher :parse_video_links do + set_default_markdown_messages + + match do |actual| + video = actual.at_css('video') + + expect(video['src']).to end_with('/assets/videos/gitlab-demo.mp4') + end + end end # Monkeypatch the matcher DSL so that we can reduce some noisy duplication for diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb new file mode 100644 index 00000000000..e8300abed5d --- /dev/null +++ b/spec/uploaders/file_uploader_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe FileUploader do + let(:project) { create(:project) } + + before do + @previous_enable_processing = FileUploader.enable_processing + FileUploader.enable_processing = false + @uploader = FileUploader.new(project) + end + + after do + FileUploader.enable_processing = @previous_enable_processing + @uploader.remove! + end + + describe '#image_or_video?' do + context 'given an image file' do + before do + @uploader.store!(File.new(Rails.root.join('spec', 'fixtures', 'rails_sample.jpg'))) + end + + it 'detects an image based on file extension' do + expect(@uploader.image_or_video?).to be true + end + end + + context 'given an video file' do + before do + video_file = File.new(Rails.root.join('spec', 'fixtures', 'video_sample.mp4')) + @uploader.store!(video_file) + end + + it 'detects a video based on file extension' do + expect(@uploader.image_or_video?).to be true + end + end + + it 'does not return image_or_video? for other types' do + @uploader.store!(File.new(Rails.root.join('spec', 'fixtures', 'doc_sample.txt'))) + + expect(@uploader.image_or_video?).to be false + end + end +end diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb index 439da765c2c..796751efe8d 100644 --- a/spec/workers/emails_on_push_worker_spec.rb +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -12,6 +12,42 @@ describe EmailsOnPushWorker do subject { EmailsOnPushWorker.new } describe "#perform" do + context "when push is a new branch" do + let(:email) { ActionMailer::Base.deliveries.last } + + before do + data_new_branch = data.stringify_keys.merge("before" => Gitlab::Git::BLANK_SHA) + + subject.perform(project.id, recipients, data_new_branch) + end + + it "sends a mail with the correct subject" do + expect(email.subject).to include("Pushed new branch") + end + + it "sends the mail to the correct recipient" do + expect(email.to).to eq([user.email]) + end + end + + context "when push is a deleted branch" do + let(:email) { ActionMailer::Base.deliveries.last } + + before do + data_deleted_branch = data.stringify_keys.merge("after" => Gitlab::Git::BLANK_SHA) + + subject.perform(project.id, recipients, data_deleted_branch) + end + + it "sends a mail with the correct subject" do + expect(email.subject).to include("Deleted branch") + end + + it "sends the mail to the correct recipient" do + expect(email.to).to eq([user.email]) + end + end + context "when there are no errors in sending" do let(:email) { ActionMailer::Base.deliveries.last } |