diff options
190 files changed, 2850 insertions, 1166 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index cd13f581517..3aac8401848 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -284,7 +284,7 @@ Style/IfWithSemicolon: # Checks that conditional statements do not have an identical line at the # end of each branch, which can validly be moved out of the conditional. Style/IdenticalConditionalBranches: - Enabled: false + Enabled: true # Checks the indentation of the first line of the right-hand-side of a # multi-line assignment. diff --git a/CHANGELOG b/CHANGELOG index b0969de7d61..d5052df2cbe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,51 +1,70 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.10.0 (unreleased) + - Expose {should,force}_remove_source_branch (Ben Boeckel) - Add the functionality to be able to rename a file. !5049 (tiagonbotelho) - Fix commit builds API, return all builds for all pipelines for given commit. !4849 - Replace Haml with Hamlit to make view rendering faster. !3666 + - Expire the branch cache after `git gc` runs - Refactor repository paths handling to allow multiple git mount points + - Optimize system note visibility checking by memoizing the visible reference count !5070 - Add Application Setting to configure default Repository Path for new projects + - Remove pinTo from Flash and make inline flash messages look nicer !4854 (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) - Display last commit of deleted branch in push events !4699 (winniehell) - Escape file extension when parsing search results !5141 (winniehell) - Apply the trusted_proxies config to the rack request object for use with rack_attack - Add Sidekiq queue duration to transaction metrics. - Add a new column `artifacts_size` to table `ci_builds` !4964 - Let Workhorse serve format-patch diffs + - Added day name to contribution calendar tooltips - Make images fit to the size of the viewport !4810 - Fix check for New Branch button on Issue page !4630 (winniehell) - Fix MR-auto-close text added to description. !4836 - Fix issue, preventing users w/o push access to sort tags !5105 (redetection) - Add Spring EmojiOne updates. + - Add syntax for multiline blockquote using `>>>` fence !3954 - Fix viewing notification settings when a project is pending deletion - 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 - Updated project header design - Exclude email check from the standard health check - 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 - Wildcards for protected branches. !4665 - Allow importing from Github using Personal Access Tokens. (Eric K Idema) - API: Todos !3188 (Robert Schilling) + - API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling) - 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 - 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 - Throttle the update of `project.pushes_since_gc` to 1 minute. + - Allow expanding and collapsing files in diff view (!4990) + - Collapse large diffs by default (!4990) - Check for conflicts with existing Project's wiki path when creating a new project. - Show last push widget in upstream after push to fork - Don't instantiate a git tree on Projects show default view - Bump Rinku to 2.0.0 - Remove unused front-end variable -> default_issues_tracker - 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) - Instrument Rinku usage + - Be explicit to define merge request discussion variables - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - Set import_url validation to be more strict + - Memoize MR merged/closed events retrieval + - Don't render discussion notes when requesting diff tab through AJAX - Add basic system information like memory and disk usage to the admin panel - Don't garbage collect commits that have related DB records like comments - More descriptive message for git hooks and file locks @@ -53,8 +72,16 @@ v 8.10.0 (unreleased) - Allow '?', or '&' for label names - 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 - 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) + - Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt) + - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel) + - Style of import project buttons were fixed in the new project page. !5183 (rdemirbay) + +v 8.9.6 (unreleased) + - Fix importing of events under notes for GitLab projects v 8.9.5 - Add more debug info to import/export and memory killer. !5108 @@ -99,7 +126,7 @@ v 8.9.3 - Removed fade when filtering results. !4932 - Fix missing avatar on system notes. !4954 - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973 - - Use update_columns to by_pass all the dirty code on active_record. !4985 + - Use update_columns to bypass all the dirty code on active_record. !4985 - Fix restore Rake task warning message output !4980 v 8.9.2 @@ -2603,13 +2630,13 @@ v 6.5.0 - Files API supports base64 encoded content (sponsored by O'Reilly Media) - Added support for Go's repository retrieval (Bruno Albuquerque) -v6.4.3 +v 6.4.3 - Don't use unicorn worker killer if PhusionPassenger is defined -v6.4.2 +v 6.4.2 - Fixed wrong behaviour of script/upgrade.rb -v6.4.1 +v 6.4.1 - Fixed bug with repository rename - Fixed bug with project transfer @@ -1,4 +1,4 @@ -source "https://rubygems.org" +source 'https://rubygems.org' gem 'rails', '4.2.6' gem 'rails-deprecated_sanitizer', '~> 1.0.3' @@ -11,11 +11,11 @@ gem 'responders', '~> 2.0' gem 'sprockets', '~> 3.6.0' # Default values for AR models -gem "default_value_for", "~> 3.0.0" +gem 'default_value_for', '~> 3.0.0' # Supported DBs -gem "mysql2", '~> 0.3.16', group: :mysql -gem "pg", '~> 0.18.2', group: :postgres +gem 'mysql2', '~> 0.3.16', group: :mysql +gem 'pg', '~> 0.18.2', group: :postgres # Authentication libraries gem 'devise', '~> 4.0' @@ -28,7 +28,7 @@ gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-facebook', '~> 3.0.0' gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-gitlab', '~> 1.0.0' -gem 'omniauth-google-oauth2', '~> 0.2.0' +gem 'omniauth-google-oauth2', '~> 0.4.1' gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-saml', '~> 1.6.0' gem 'omniauth-shibboleth', '~> 1.2.0' @@ -48,7 +48,7 @@ gem 'attr_encrypted', '~> 3.0.0' gem 'u2f', '~> 0.2.1' # Browser detection -gem "browser", '~> 2.2' +gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library @@ -57,7 +57,7 @@ gem "gitlab_git", '~> 10.2' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master -gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap" +gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: 'omniauth-ldap' # Git Wiki # Required manually in config/initializers/gollum.rb to control load order @@ -65,7 +65,7 @@ gem 'gollum-lib', '~> 4.1.0', require: false gem 'gollum-rugged_adapter', '~> 0.4.2', require: false # Language detection -gem "github-linguist", "~> 4.7.0", require: "linguist" +gem 'github-linguist', '~> 4.7.0', require: 'linguist' # API gem 'grape', '~> 0.13.0' @@ -73,13 +73,13 @@ gem 'grape-entity', '~> 0.4.2' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' # Pagination -gem "kaminari", "~> 0.17.0" +gem 'kaminari', '~> 0.17.0' # HAML gem 'hamlit', '~> 2.5' # Files attachments -gem "carrierwave", '~> 0.10.0' +gem 'carrierwave', '~> 0.10.0' # Drag and Drop UI gem 'dropzonejs-rails', '~> 0.7.1' @@ -94,13 +94,13 @@ gem 'fog-openstack', '~> 0.1' gem 'fog-rackspace', '~> 0.1.1' # for aws storage -gem "unf", '~> 0.1.4' +gem 'unf', '~> 0.1.4' # Authorization -gem "six", '~> 0.2.0' +gem 'six', '~> 0.2.0' # Seed data -gem "seed-fu", '~> 2.3.5' +gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' @@ -124,29 +124,29 @@ gem 'diffy', '~> 3.0.3' # Application server group :unicorn do - gem "unicorn", '~> 4.9.0' + gem 'unicorn', '~> 4.9.0' gem 'unicorn-worker-killer', '~> 0.4.2' end # State machine -gem "state_machines-activerecord", '~> 0.4.0' +gem 'state_machines-activerecord', '~> 0.4.0' # Run events after state machine commits -gem 'after_commit_queue' +gem 'after_commit_queue', '~> 1.3.0' # Issue tags gem 'acts-as-taggable-on', '~> 3.4' # Background jobs -gem 'sinatra', '~> 1.4.4', require: nil +gem 'sinatra', '~> 1.4.4', require: false gem 'sidekiq', '~> 4.0' gem 'sidekiq-cron', '~> 0.4.0' -gem 'redis-namespace' +gem 'redis-namespace', '~> 1.5.2' # HTTP requests -gem "httparty", '~> 0.13.3' +gem 'httparty', '~> 0.13.3' # Colored output to console -gem "rainbow", '~> 2.1.0' +gem 'rainbow', '~> 2.1.0' # GitLab settings gem 'settingslogic', '~> 2.0.9' @@ -156,7 +156,7 @@ gem 'settingslogic', '~> 2.0.9' gem 'version_sorter', '~> 2.0.0' # Cache -gem "redis-rails", '~> 4.0.0' +gem 'redis-rails', '~> 4.0.0' # Redis gem 'redis', '~> 3.2' @@ -169,13 +169,13 @@ gem 'tinder', '~> 1.10.0' gem 'hipchat', '~> 1.5.0' # Flowdock integration -gem "gitlab-flowdock-git-hook", "~> 1.0.1" +gem 'gitlab-flowdock-git-hook', '~> 1.0.1' # Gemnasium integration -gem "gemnasium-gitlab-service", "~> 0.2" +gem 'gemnasium-gitlab-service', '~> 0.2' # Slack integration -gem "slack-notifier", "~> 1.2.0" +gem 'slack-notifier', '~> 1.2.0' # Asana integration gem 'asana', '~> 0.4.0' @@ -187,20 +187,20 @@ gem 'ruby-fogbugz', '~> 0.2.1' gem 'd3_rails', '~> 3.5.0' # underscore-rails -gem "underscore-rails", "~> 1.8.0" +gem 'underscore-rails', '~> 1.8.0' # Sanitize user input -gem "sanitize", '~> 2.0' +gem 'sanitize', '~> 2.0' gem 'babosa', '~> 1.0.2' # Sanitizes SVG input -gem "loofah", "~> 2.0.3" +gem 'loofah', '~> 2.0.3' # Working with license gem 'licensee', '~> 8.0.0' # Protect against bruteforcing -gem "rack-attack", '~> 4.3.1' +gem 'rack-attack', '~> 4.3.1' # Ace editor gem 'ace-rails-ap', '~> 4.0.2' @@ -214,9 +214,9 @@ gem 'charlock_holmes', '~> 0.7.3' # Parse duration gem 'chronic_duration', '~> 0.10.6' -gem "sass-rails", '~> 5.0.0' -gem "coffee-rails", '~> 4.1.0' -gem "uglifier", '~> 2.7.2' +gem 'sass-rails', '~> 5.0.0' +gem 'coffee-rails', '~> 4.1.0' +gem 'uglifier', '~> 2.7.2' gem 'turbolinks', '~> 2.5.0' gem 'jquery-turbolinks', '~> 2.1.0' @@ -247,13 +247,13 @@ group :metrics do end group :development do - gem "foreman" + gem 'foreman', '~> 0.78.0' gem 'brakeman', '~> 3.3.0', require: false gem 'letter_opener_web', '~> 1.3.0' gem 'rerun', '~> 0.11.0' - gem 'bullet', require: false - gem 'rblineprof', platform: :mri, require: false + gem 'bullet', '~> 5.0.0', require: false + gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false gem 'web-console', '~> 2.0' # Better errors handler @@ -261,15 +261,15 @@ group :development do gem 'binding_of_caller', '~> 0.7.2' # Docs generator - gem "sdoc", '~> 0.3.20' + gem 'sdoc', '~> 0.3.20' # thin instead webrick gem 'thin', '~> 1.7.0' end group :development, :test do - gem 'byebug', platform: :mri - gem 'pry-rails' + gem 'byebug', '~> 8.2.1', platform: :mri + gem 'pry-rails', '~> 0.3.4' gem 'awesome_print', '~> 1.2.0', require: false gem 'fuubar', '~> 2.0.0' @@ -277,7 +277,7 @@ group :development, :test do gem 'database_cleaner', '~> 1.4.0' gem 'factory_girl_rails', '~> 4.6.0' gem 'rspec-rails', '~> 3.5.0' - gem 'rspec-retry' + gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' @@ -303,14 +303,14 @@ group :development, :test do gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'scss_lint', '~> 0.47.0', require: false gem 'simplecov', '~> 0.11.0', require: false - gem 'flog', require: false - gem 'flay', require: false - gem 'bundler-audit', require: false + gem 'flog', '~> 4.3.2', require: false + gem 'flay', '~> 2.6.1', require: false + gem 'bundler-audit', '~> 0.5.0', require: false - gem 'benchmark-ips', require: false + gem 'benchmark-ips', '~> 2.3.0', require: false - gem "license_finder", require: false - gem 'knapsack' + gem 'license_finder', '~> 2.1.0', require: false + gem 'knapsack', '~> 1.11.0' end group :test do @@ -318,33 +318,33 @@ group :test do gem 'email_spec', '~> 1.6.0' gem 'webmock', '~> 1.21.0' gem 'test_after_commit', '~> 0.4.2' - gem 'sham_rack' + gem 'sham_rack', '~> 1.3.6' end group :production do - gem "gitlab_meta", '7.0' + gem 'gitlab_meta', '7.0' end -gem "newrelic_rpm", '~> 3.14' +gem 'newrelic_rpm', '~> 3.14' gem 'octokit', '~> 4.3.0' -gem "mail_room", "~> 0.8" +gem 'mail_room', '~> 0.8' gem 'email_reply_parser', '~> 0.5.8' ## CI gem 'activerecord-session_store', '~> 1.0.0' -gem "nested_form", '~> 0.3.2' +gem 'nested_form', '~> 0.3.2' # OAuth gem 'oauth2', '~> 1.2.0' # Soft deletion -gem "paranoia", "~> 2.0" +gem 'paranoia', '~> 2.0' # Health check -gem 'health_check', '~> 1.5.1' +gem 'health_check', '~> 2.1.0' # System information gem 'vmstat', '~> 2.1.0' diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 20fe5a5cc27..c98763d6271 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -47,14 +47,12 @@ #= require date.format #= require_directory ./behaviors #= require_directory ./blob -#= require_directory ./ci #= require_directory ./commit #= require_directory ./extensions #= require_directory ./lib/utils #= require_directory ./u2f #= require_directory . #= require fuzzaldrin-plus -#= require cropper #= require u2f window.slugify = (text) -> diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/build.coffee index 74691b2c1b5..cf203ea43a0 100644 --- a/app/assets/javascripts/ci/build.coffee +++ b/app/assets/javascripts/build.coffee @@ -1,9 +1,9 @@ -class @CiBuild +class @Build @interval: null @state: null constructor: (@page_url, @build_url, @build_status, @state) -> - clearInterval(CiBuild.interval) + clearInterval(Build.interval) # Init breakpoint checker @bp = Breakpoints.get() @@ -40,7 +40,7 @@ class @CiBuild # Check for new build output if user still watching build page # Only valid for runnig build when output changes during time # - CiBuild.interval = setInterval => + Build.interval = setInterval => if window.location.href.split("#").first() is @page_url @getBuildTrace() , 4000 diff --git a/app/assets/javascripts/ci/application.js.coffee b/app/assets/javascripts/ci/application.js.coffee deleted file mode 100644 index ca24c1d759f..00000000000 --- a/app/assets/javascripts/ci/application.js.coffee +++ /dev/null @@ -1,12 +0,0 @@ -#= require pager -#= require jquery_nested_form -#= require_tree . - -$(document).on 'click', '.assign-all-runner', -> - $(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..') - -window.unbindEvents = -> - $(document).unbind('scroll') - $(document).off('scroll') - -document.addEventListener("page:fetch", unbindEvents) diff --git a/app/assets/javascripts/ci/projects.js.coffee b/app/assets/javascripts/ci/projects.js.coffee deleted file mode 100644 index e6406011d11..00000000000 --- a/app/assets/javascripts/ci/projects.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -$(document).on 'click', '.badge-codes-toggle', -> - $('.badge-codes-block').toggleClass("hide") - return false diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee index 6d9b364cb8d..c132cc8c542 100644 --- a/app/assets/javascripts/diff.js.coffee +++ b/app/assets/javascripts/diff.js.coffee @@ -1,6 +1,9 @@ class @Diff UNFOLD_COUNT = 20 constructor: -> + $('.files .diff-file').singleFileDiff() + @filesCommentButton = $('.files .diff-file').filesCommentButton() + $(document).off('click', '.js-unfold') $(document).on('click', '.js-unfold', (event) => target = $(event.target) @@ -36,7 +39,7 @@ class @Diff # see https://gitlab.com/gitlab-org/gitlab-ce/issues/707 indent: 1 - $.get(link, params, (response) => + $.get(link, params, (response) -> target.parent().replaceWith(response) ) ) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index a39df421832..74fd77cf7ab 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -131,7 +131,6 @@ class Dispatcher when 'dashboard', 'root' shortcut_handler = new ShortcutsDashboardNavigation() when 'profiles' - new Profile() new NotificationsForm() new NotificationsDropdown() when 'projects' diff --git a/app/assets/javascripts/files_comment_button.js.coffee b/app/assets/javascripts/files_comment_button.js.coffee new file mode 100644 index 00000000000..db0bf7082a9 --- /dev/null +++ b/app/assets/javascripts/files_comment_button.js.coffee @@ -0,0 +1,97 @@ +class @FilesCommentButton + COMMENT_BUTTON_CLASS = '.add-diff-note' + COMMENT_BUTTON_TEMPLATE = _.template '<button name="button" type="submit" class="btn <%- COMMENT_BUTTON_CLASS %> js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>' + LINE_HOLDER_CLASS = '.line_holder' + LINE_NUMBER_CLASS = 'diff-line-num' + LINE_CONTENT_CLASS = 'line_content' + UNFOLDABLE_LINE_CLASS = 'js-unfold' + EMPTY_CELL_CLASS = 'empty-cell' + OLD_LINE_CLASS = 'old_line' + NEW_CLASS = 'new' + LINE_COLUMN_CLASSES = ".#{LINE_NUMBER_CLASS}, .line_content" + TEXT_FILE_SELECTOR = '.text-file' + DEBOUNCE_TIMEOUT_DURATION = 100 + + constructor: (@filesContainerElement) -> + @VIEW_TYPE = $('input#view[type=hidden]').val() + + debounce = _.debounce @render, DEBOUNCE_TIMEOUT_DURATION + + $(document) + .on 'mouseover', LINE_COLUMN_CLASSES, debounce + .on 'mouseleave', LINE_COLUMN_CLASSES, @destroy + + render: (e) => + $currentTarget = $(e.currentTarget) + buttonParentElement = @getButtonParent $currentTarget + return unless @shouldRender e, buttonParentElement + + textFileElement = @getTextFileElement $currentTarget + lineContentElement = @getLineContent $currentTarget + + buttonParentElement.append @buildButton + noteableType: textFileElement.attr 'data-noteable-type' + noteableID: textFileElement.attr 'data-noteable-id' + commitID: textFileElement.attr 'data-commit-id' + noteType: lineContentElement.attr 'data-note-type' + position: lineContentElement.attr 'data-position' + lineType: lineContentElement.attr 'data-line-type' + discussionID: lineContentElement.attr 'data-discussion-id' + lineCode: lineContentElement.attr 'data-line-code' + return + + destroy: (e) => + return if @isMovingToSameType e + $(COMMENT_BUTTON_CLASS, @getButtonParent $(e.currentTarget)).remove() + return + + buildButton: (buttonAttributes) -> + initializedButtonTemplate = COMMENT_BUTTON_TEMPLATE + COMMENT_BUTTON_CLASS: COMMENT_BUTTON_CLASS.substr 1 + $(initializedButtonTemplate).attr + 'data-noteable-type': buttonAttributes.noteableType + 'data-noteable-id': buttonAttributes.noteableID + 'data-commit-id': buttonAttributes.commitID + 'data-note-type': buttonAttributes.noteType + 'data-line-code': buttonAttributes.lineCode + 'data-position': buttonAttributes.position + 'data-discussion-id': buttonAttributes.discussionID + 'data-line-type': buttonAttributes.lineType + + getTextFileElement: (hoveredElement) -> + $(hoveredElement.closest TEXT_FILE_SELECTOR) + + getLineContent: (hoveredElement) -> + return hoveredElement if hoveredElement.hasClass LINE_CONTENT_CLASS + + $(".#{LINE_CONTENT_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent()) + + getButtonParent: (hoveredElement) -> + if @VIEW_TYPE is 'inline' + return hoveredElement if hoveredElement.hasClass OLD_LINE_CLASS + + $(".#{OLD_LINE_CLASS}", hoveredElement.parent()) + else + return hoveredElement if hoveredElement.hasClass LINE_NUMBER_CLASS + + $(".#{LINE_NUMBER_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent()) + + diffTypeClass: (hoveredElement) -> + if hoveredElement.hasClass(NEW_CLASS) then '.new' else '.old' + + isMovingToSameType: (e) -> + newButtonParent = @getButtonParent $(e.toElement) + return false unless newButtonParent + newButtonParent.is @getButtonParent $(e.currentTarget) + + shouldRender: (e, buttonParentElement) -> + (not buttonParentElement.hasClass(EMPTY_CELL_CLASS) and \ + not buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) and \ + $(COMMENT_BUTTON_CLASS, buttonParentElement).length is 0) + +$.fn.filesCommentButton = -> + return unless this and @parent().data('can-create-note')? + + @each -> + unless $.data this, 'filesCommentButton' + $.data this, 'filesCommentButton', new FilesCommentButton $(this) diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee index b76d214790a..5a493041538 100644 --- a/app/assets/javascripts/flash.js.coffee +++ b/app/assets/javascripts/flash.js.coffee @@ -1,24 +1,28 @@ class @Flash - constructor: (message, type = 'alert')-> - @flash = $(".flash-container") - @flash.html("") + hideFlash = -> $(@).fadeOut() - innerDiv = $('<div/>', + constructor: (message, type = 'alert', parent = null)-> + if parent + @flashContainer = parent.find('.flash-container') + else + @flashContainer = $('.flash-container-page') + + @flashContainer.html('') + + flash = $('<div/>', class: "flash-#{type}" ) - innerDiv.appendTo(".flash-container") + flash.on 'click', hideFlash - textDiv = $("<div/>", - class: "flash-text", + textDiv = $('<div/>', + class: 'flash-text', text: message ) - textDiv.appendTo(innerDiv) + textDiv.appendTo(flash) - if @flash.parent().hasClass('content-wrapper') + if @flashContainer.parent().hasClass('content-wrapper') textDiv.addClass('container-fluid container-limited') - @flash.click -> $(@).fadeOut() - @flash.show() + flash.appendTo(@flashContainer) + @flashContainer.show() - pinTo: (selector) -> - @flash.detach().appendTo(selector) diff --git a/app/assets/javascripts/lib/cropper.js.coffee b/app/assets/javascripts/lib/cropper.js.coffee new file mode 100644 index 00000000000..32536d23fe3 --- /dev/null +++ b/app/assets/javascripts/lib/cropper.js.coffee @@ -0,0 +1 @@ +#= require cropper diff --git a/app/assets/javascripts/lib/utils/common_utils.js.coffee b/app/assets/javascripts/lib/utils/common_utils.js.coffee index e39dcb2daa9..d4dd3dc329a 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.coffee +++ b/app/assets/javascripts/lib/utils/common_utils.js.coffee @@ -5,12 +5,12 @@ w.gl.utils.isInGroupsPage = -> - return $('body').data('page').split(':')[0] is 'groups' + return gl.utils.getPagePath() is 'groups' w.gl.utils.isInProjectPage = -> - return $('body').data('page').split(':')[0] is 'projects' + return gl.utils.getPagePath() is 'projects' w.gl.utils.getProjectSlug = -> @@ -40,6 +40,9 @@ e.stopImmediatePropagation() return false + gl.utils.getPagePath = -> + return $('body').data('page').split(':')[0] + jQuery.timefor = (time, suffix, expiredLabel) -> diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js.coffee b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee index 948d6dbf07e..178963fe0aa 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js.coffee +++ b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee @@ -2,10 +2,14 @@ w.gl ?= {} w.gl.utils ?= {} + w.gl.utils.days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] w.gl.utils.formatDate = (datetime) -> dateFormat(datetime, 'mmm d, yyyy h:MMtt Z') + w.gl.utils.getDayName = (date) -> + this.days[date.getDay()] + w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) -> $timeagoEls.each( -> $el = $(@) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 894f80586f1..86539e0d725 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -153,17 +153,18 @@ class @MergeRequestTabs loadDiff: (source) -> return if @diffsLoaded - @_get url: "#{source}.json" + @_location.search success: (data) => $('#diffs').html data.html gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')) $('#diffs .js-syntax-highlight').syntaxHighlight() + $('#diffs .diff-file').singleFileDiff() @expandViewContainer() if @diffViewType() is 'parallel' @diffsLoaded = true @scrollToElement("#diffs") @highlighSelectedLine() + @filesCommentButton = $('.files .diff-file').filesCommentButton() $(document) .off 'click', '.diff-line-num a' diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 0b7d8f64456..0ea54faae1a 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -194,8 +194,7 @@ class @Notes renderNote: (note) -> unless note.valid if note.award - flash = new Flash('You have already awarded this emoji!', 'alert') - flash.pinTo('.header-content') + new Flash('You have already awarded this emoji!', 'alert') return if note.award @@ -325,6 +324,8 @@ class @Notes form.find("#note_position").remove() form.find("#note_type").remove() + @parentTimeline = form.parents('.timeline') + ### General note form setup. @@ -357,8 +358,7 @@ class @Notes @renderNote(note) addNoteError: (xhr, note, status) => - flash = new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert') - flash.pinTo('.md-area') + new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', @parentTimeline) ### Called in response to the new note form being submitted diff --git a/app/assets/javascripts/profile/application.js.coffee b/app/assets/javascripts/profile/application.js.coffee new file mode 100644 index 00000000000..91cacfece46 --- /dev/null +++ b/app/assets/javascripts/profile/application.js.coffee @@ -0,0 +1,2 @@ +# +#= require_tree . diff --git a/app/assets/javascripts/gl_crop.js.coffee b/app/assets/javascripts/profile/gl_crop.js.coffee index df9bfdfa6cc..df9bfdfa6cc 100644 --- a/app/assets/javascripts/gl_crop.js.coffee +++ b/app/assets/javascripts/profile/gl_crop.js.coffee diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile/profile.js.coffee index 1583d1ba6f9..f3b05f2c646 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile/profile.js.coffee @@ -78,3 +78,6 @@ $ -> if comment && comment.length > 1 && $title.val() == '' $title.val(comment[1]).change() + + if gl.utils.getPagePath() == 'profiles' + new Profile() diff --git a/app/assets/javascripts/single_file_diff.js.coffee b/app/assets/javascripts/single_file_diff.js.coffee new file mode 100644 index 00000000000..f3e225c3728 --- /dev/null +++ b/app/assets/javascripts/single_file_diff.js.coffee @@ -0,0 +1,54 @@ +class @SingleFileDiff + + WRAPPER = '<div class="diff-content diff-wrap-lines"></div>' + LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>' + ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>' + COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>' + + constructor: (@file) -> + @content = $('.diff-content', @file) + @diffForPath = @content.find('[data-diff-for-path]').data 'diff-for-path' + @isOpen = !@diffForPath + + if @diffForPath + @collapsedContent = @content + @loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide() + @content = null + @collapsedContent.after(@loadingContent) + else + @collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide() + @content.after(@collapsedContent) + + @collapsedContent.on 'click', @toggleDiff + + $('.file-title > a', @file).on 'click', @toggleDiff + + toggleDiff: (e) => + @isOpen = !@isOpen + if not @isOpen and not @hasError + @content.hide() + @collapsedContent.show() + else if @content + @collapsedContent.hide() + @content.show() + else + @getContentHTML() + + getContentHTML: -> + @collapsedContent.hide() + @loadingContent.show() + $.get @diffForPath, (data) => + @loadingContent.hide() + if data.html + @content = $(data.html) + @content.syntaxHighlight() + else + @hasError = true + @content = $(ERROR_HTML) + @collapsedContent.after(@content) + return + +$.fn.singleFileDiff = -> + return @each -> + if not $.data this, 'singleFileDiff' + $.data this, 'singleFileDiff', new SingleFileDiff this diff --git a/app/assets/javascripts/users/calendar.js.coffee b/app/assets/javascripts/users/calendar.js.coffee index c081f023b04..c49ba5186f2 100644 --- a/app/assets/javascripts/users/calendar.js.coffee +++ b/app/assets/javascripts/users/calendar.js.coffee @@ -87,14 +87,15 @@ class @Calendar .attr 'width', @daySize .attr 'height', @daySize .attr 'title', (stamp) => + date = new Date(stamp.date) contribText = 'No contributions' if stamp.count > 0 contribText = "#{stamp.count} contribution#{if stamp.count > 1 then 's' else ''}" - date = dateFormat(stamp.date, 'mmm d, yyyy') + dateText = dateFormat(date, 'mmm d, yyyy') - "#{contribText}<br />#{date}" + "#{contribText}<br />#{gl.utils.getDayName(date)} #{dateText}" .attr 'class', 'user-contrib-cell js-tooltip' .attr 'fill', (stamp) => if stamp.count isnt 0 diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 41e77a4ac68..24b1ebab4b0 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -16,6 +16,9 @@ font-weight: normal; font-size: 16px; line-height: 36px; + &.diff-collapsed { + cursor: pointer; + } } .row-content-block { diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index a951a2b97fe..0c21d0240b3 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -1,8 +1,8 @@ .flash-container { cursor: pointer; margin: 0; + margin-bottom: $gl-padding; font-size: 14px; - width: 100%; z-index: 100; .flash-notice { @@ -18,9 +18,27 @@ } .flash-notice, .flash-alert { - .container-fluid.flash-text { + border-radius: $border-radius-default; + + .container-fluid.container-limited.flash-text { background: transparent; } } + + &.flash-container-page { + margin-bottom: 0; + + .flash-notice, .flash-alert { + border-radius: 0; + } + } +} + +@media (max-width: $screen-md-min) { + ul.notes { + .flash-container.timeline-content { + margin-left: 0; + } + } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index aed0b44d91b..2c40ec430ca 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -175,6 +175,12 @@ ul.content-list { .panel > .content-list > li { padding: $gl-padding-top $gl-padding; + + &.commit { + @media (min-width: $screen-sm-min) { + padding-left: 46px + $gl-padding; + } + } } ul.controls { diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 02ea98e9d94..364952d3b4a 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -77,10 +77,10 @@ &.sub-nav { text-align: center; - background-color: $background-color; + background-color: $dark-background-color; .container-fluid { - background-color: $background-color; + background-color: $dark-background-color; margin-bottom: 0; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 188823054fd..1a2220f3b40 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -3,6 +3,12 @@ padding-bottom: 25px; transition: padding $sidebar-transition-duration; + &.page-sidebar-pinned { + .sidebar-wrapper { + @include box-shadow(none); + } + } + .sidebar-wrapper { position: fixed; top: 0; @@ -11,6 +17,7 @@ height: 100%; overflow: hidden; transition: width $sidebar-transition-duration; + @include box-shadow(2px 0 16px 0 #bbb); } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 211a9af2348..4337fab5d87 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,10 +12,11 @@ $sidebar-breakpoint: 1024px; /* * UI elements */ -$border-color: #e5e5e5; -$focus-border-color: #3aabf0; -$table-border-color: #f0f0f0; -$background-color: #fafafa; +$border-color: #e5e5e5; +$focus-border-color: #3aabf0; +$table-border-color: #f0f0f0; +$background-color: #fafafa; +$dark-background-color: #f7f7f7; /* * Text @@ -153,9 +154,6 @@ $warning-message-bg: #fbf2d9; $warning-message-color: #9e8e60; $warning-message-border: #f0e2bb; -/* header */ -$light-grey-header: #faf9f9; - /* tanuki logo colors */ $tanuki-red: #e24329; $tanuki-orange: #fc6d26; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 85bbf70e188..0298577c494 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -61,7 +61,7 @@ font-size: 0; } - .btn-transparent { + .btn-clipboard, .btn-transparent { padding-left: 0; padding-right: 0; } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 4e35ca329e4..0e4d8c140aa 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -63,8 +63,8 @@ form.edit-issue { .merge-request, .issue { &.today { - background: #efe; - border-color: #cec; + background: #f8feef; + border-color: #e1e8d5; } &.closed { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index bce4aac3334..5be911dc562 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -340,23 +340,30 @@ a.deploy-project-label { .project-import { .form-group { - margin-bottom: 0; + margin-bottom: 5px; } + .import-buttons { padding-left: 0; display: -webkit-flex; display: flex; -webkit-flex-wrap: wrap; flex-wrap: wrap; + .btn { - margin-right: 10px; - padding: 8px 12px; + margin: 0 10px 10px 0; + padding: 8px; } - &> div { - margin-bottom: 14px; + + > div { padding-left: 0; + &:last-child { margin-bottom: 0; + + .btn { + margin-right: 0; + } } } } diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb new file mode 100644 index 00000000000..e09b8789eb2 --- /dev/null +++ b/app/controllers/concerns/diff_for_path.rb @@ -0,0 +1,25 @@ +module DiffForPath + extend ActiveSupport::Concern + + def render_diff_for_path(diffs, diff_refs, project) + diff_file = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository).find do |diff| + diff.old_path == params[:old_path] && diff.new_path == params[:new_path] + end + + return render_404 unless diff_file + + diff_commit = commit_for_diff(diff_file) + blob = diff_file.blob(diff_commit) + @expand_all_diffs = true + + locals = { + diff_file: diff_file, + diff_commit: diff_commit, + diff_refs: diff_refs, + blob: blob, + project: project + } + + render json: { html: view_to_html_string('projects/diffs/_content', locals) } + end +end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 9b5c43b17e2..d3dd98c8a4e 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -12,13 +12,12 @@ class HelpController < ApplicationController end def show - @category = clean_path_info(path_params[:category]) - @file = path_params[:file] + @path = clean_path_info(path_params[:path]) respond_to do |format| format.any(:markdown, :md, :html) do # Note: We are purposefully NOT using `Rails.root.join` - path = File.join(Rails.root, 'doc', @category, "#{@file}.md") + path = File.join(Rails.root, 'doc', "#{@path}.md") if File.exist?(path) @markdown = File.read(path) @@ -33,7 +32,7 @@ class HelpController < ApplicationController # Allow access to images in the doc folder format.any(:png, :gif, :jpeg) do # Note: We are purposefully NOT using `Rails.root.join` - path = File.join(Rails.root, 'doc', @category, "#{@file}.#{params[:format]}") + path = File.join(Rails.root, 'doc', "#{@path}.#{params[:format]}") if File.exist?(path) send_file(path, disposition: 'inline') @@ -57,8 +56,7 @@ class HelpController < ApplicationController private def path_params - params.require(:category) - params.require(:file) + params.require(:path) params end diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index f11c8321464..7241949393b 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -23,10 +23,9 @@ class Projects::ArtifactsController < Projects::ApplicationController entry = build.artifacts_metadata_entry(params[:path]) if entry.exists? - render json: { archive: build.artifacts_file.path, - entry: Base64.encode64(entry.path) } + send_artifacts_entry(build, entry) else - render json: {}, status: 404 + render_404 end end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 37d6521026c..727e84b40a1 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -3,6 +3,7 @@ # Not to be confused with CommitsController, plural. class Projects::CommitController < Projects::ApplicationController include CreatesCommit + include DiffForPath include DiffHelper # Authorize @@ -11,29 +12,14 @@ class Projects::CommitController < Projects::ApplicationController before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds] before_action :authorize_read_commit_status!, only: [:builds] before_action :commit - before_action :define_show_vars, only: [:show, :builds] + before_action :define_commit_vars, only: [:show, :diff_for_path, :builds] + before_action :define_status_vars, only: [:show, :builds] + before_action :define_note_vars, only: [:show, :diff_for_path] before_action :authorize_edit_tree!, only: [:revert, :cherry_pick] def show apply_diff_view_cookie! - @grouped_diff_notes = commit.notes.grouped_diff_notes - @notes = commit.notes.non_diff_notes.fresh - - Banzai::NoteRenderer.render( - @grouped_diff_notes.values.flatten + @notes, - @project, - current_user, - ) - - @note = @project.build_commit_note(commit) - - @noteable = @commit - @comments_target = { - noteable_type: 'Commit', - commit_id: @commit.id - } - respond_to do |format| format.html format.diff { render text: @commit.to_diff } @@ -41,6 +27,10 @@ class Projects::CommitController < Projects::ApplicationController end end + def diff_for_path + render_diff_for_path(@diffs, @commit.diff_refs, @project) + end + def builds end @@ -114,7 +104,7 @@ class Projects::CommitController < Projects::ApplicationController @ci_builds ||= Ci::Build.where(pipeline: pipelines) end - def define_show_vars + def define_commit_vars return git_not_found! unless commit opts = diff_options @@ -122,7 +112,28 @@ class Projects::CommitController < Projects::ApplicationController @diffs = commit.diffs(opts) @notes_count = commit.notes.count + end + + def define_note_vars + @grouped_diff_notes = commit.notes.grouped_diff_notes + @notes = commit.notes.non_diff_notes.fresh + + Banzai::NoteRenderer.render( + @grouped_diff_notes.values.flatten + @notes, + @project, + current_user, + ) + + @note = @project.build_commit_note(commit) + + @noteable = @commit + @comments_target = { + noteable_type: 'Commit', + commit_id: @commit.id + } + end + def define_status_vars @statuses = CommitStatus.where(pipeline: pipelines) @builds = Ci::Build.where(pipeline: pipelines) end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index d240b9fe989..5f3ee71444d 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -1,29 +1,51 @@ require 'addressable/uri' class Projects::CompareController < Projects::ApplicationController + include DiffForPath include DiffHelper # Authorize before_action :require_non_empty_project before_action :authorize_download_code! - before_action :assign_ref_vars, only: [:index, :show] + before_action :define_ref_vars, only: [:index, :show, :diff_for_path] + before_action :define_diff_vars, only: [:show, :diff_for_path] before_action :merge_request, only: [:index, :show] def index end def show - compare = CompareService.new. - execute(@project, @head_ref, @project, @start_ref, diff_options) + end + + def diff_for_path + return render_404 unless @compare + + render_diff_for_path(@diffs, @diff_refs, @project) + end + + def create + redirect_to namespace_project_compare_path(@project.namespace, @project, + params[:from], params[:to]) + end + + private - if compare - @commits = Commit.decorate(compare.commits, @project) + def define_ref_vars + @start_ref = Addressable::URI.unescape(params[:from]) + @ref = @head_ref = Addressable::URI.unescape(params[:to]) + end + + def define_diff_vars + @compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref) + + if @compare + @commits = Commit.decorate(@compare.commits, @project) @start_commit = @project.commit(@start_ref) @commit = @project.commit(@head_ref) @base_commit = @project.merge_base_commit(@start_ref, @head_ref) - @diffs = compare.diffs(diff_options) + @diffs = @compare.diffs(diff_options) @diff_refs = Gitlab::Diff::DiffRefs.new( base_sha: @base_commit.try(:sha), start_sha: @start_commit.try(:sha), @@ -35,18 +57,6 @@ class Projects::CompareController < Projects::ApplicationController end end - def create - redirect_to namespace_project_compare_path(@project.namespace, @project, - params[:from], params[:to]) - end - - private - - def assign_ref_vars - @start_ref = Addressable::URI.unescape(params[:from]) - @ref = @head_ref = Addressable::URI.unescape(params[:to]) - end - def merge_request @merge_request ||= @project.merge_requests.opened. find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index df1943dd9bb..df659bb8c3b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -1,5 +1,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController include ToggleSubscriptionAction + include DiffForPath include DiffHelper include IssuableActions include ToggleAwardEmoji @@ -9,10 +10,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check, :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip ] - before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds] before_action :define_show_vars, only: [:show, :diffs, :commits, :builds] before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check] + before_action :define_commit_vars, only: [:diffs] + before_action :define_diff_comment_vars, only: [:diffs] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds] # Allow read any merge_request @@ -53,12 +55,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def show - @note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)). - group(:commit_id).count - respond_to do |format| - format.html - + format.html { define_discussion_vars } + format.json do render json: @merge_request end @@ -80,49 +79,65 @@ class Projects::MergeRequestsController < Projects::ApplicationController def diffs apply_diff_view_cookie! - @commit = @merge_request.diff_head_commit - @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit - - @comments_target = { - noteable_type: 'MergeRequest', - noteable_id: @merge_request.id - } - - @use_legacy_diff_notes = !@merge_request.support_new_diff_notes? - @grouped_diff_notes = @merge_request.notes.grouped_diff_notes - - Banzai::NoteRenderer.render( - @grouped_diff_notes.values.flatten, - @project, - current_user, - @path, - @project_wiki, - @ref - ) + @merge_request_diff = @merge_request.merge_request_diff respond_to do |format| - format.html + format.html { define_discussion_vars } format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } } end end + # With an ID param, loads the MR at that ID. Otherwise, accepts the same params as #new + # and uses that (unsaved) MR. + # + def diff_for_path + if params[:id] + merge_request + define_diff_comment_vars + else + build_merge_request + @diff_notes_disabled = true + @grouped_diff_notes = {} + end + + define_commit_vars + diffs = @merge_request.diffs(diff_options) + + render_diff_for_path(diffs, @merge_request.diff_refs, @merge_request.project) + end + def commits respond_to do |format| - format.html { render 'show' } - format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } } + format.html do + define_discussion_vars + + render 'show' + end + format.json do + # Get commits from repository + # or from cache if already merged + @commits = @merge_request.commits + @note_counts = Note.where(commit_id: @commits.map(&:id)). + group(:commit_id).count + + render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } + end end end def builds respond_to do |format| - format.html { render 'show' } + format.html do + define_discussion_vars + + render 'show' + end format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } } end end def new - params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) - @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute + build_merge_request @noteable = @merge_request @target_branches = if @merge_request.target_project @@ -308,10 +323,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController alias_method :issuable, :merge_request alias_method :awardable, :merge_request - def closes_issues - @closes_issues ||= @merge_request.closes_issues - end - def authorize_update_merge_request! return render_404 unless can?(current_user, :update_merge_request, @merge_request) end @@ -340,14 +351,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_show_vars + @noteable = @merge_request + @commits_count = @merge_request.commits.count + + @pipeline = @merge_request.pipeline + @statuses = @pipeline.statuses if @pipeline + + if @merge_request.locked_long_ago? + @merge_request.unlock_mr + @merge_request.close + end + end + + # Discussion tab data is rendered on html responses of actions + # :show, :diff, :commits, :builds. but not when request the data through AJAX + def define_discussion_vars # Build a note object for comment form - @note = @project.notes.new(noteable: @merge_request) + @note = @project.notes.new(noteable: @noteable) - @discussions = @merge_request.mr_and_commit_notes. + @discussions = @noteable.mr_and_commit_notes. inc_author_project_award_emoji. fresh. discussions + # This is not executed lazily @notes = Banzai::NoteRenderer.render( @discussions.flatten, @project, @@ -356,28 +383,35 @@ class Projects::MergeRequestsController < Projects::ApplicationController @project_wiki, @ref ) - - @noteable = @merge_request - - # Get commits from repository - # or from cache if already merged - @commits = @merge_request.commits - - @merge_request_diff = @merge_request.merge_request_diff - - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses if @pipeline - - if @merge_request.locked_long_ago? - @merge_request.unlock_mr - @merge_request.close - end end def define_widget_vars @pipeline = @merge_request.pipeline @pipelines = [@pipeline].compact - closes_issues + end + + def define_commit_vars + @commit = @merge_request.diff_head_commit + @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit + end + + def define_diff_comment_vars + @comments_target = { + noteable_type: 'MergeRequest', + noteable_id: @merge_request.id + } + + @use_legacy_diff_notes = !@merge_request.support_new_diff_notes? + @grouped_diff_notes = @merge_request.notes.grouped_diff_notes + + Banzai::NoteRenderer.render( + @grouped_diff_notes.values.flatten, + @project, + current_user, + @path, + @project_wiki, + @ref + ) end def invalid_mr @@ -408,4 +442,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController params[:merge_when_build_succeeds].present? && @merge_request.pipeline && @merge_request.pipeline.active? end + + def build_merge_request + params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) + @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute + end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 12e0d5a8413..1803aa8eab4 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -53,11 +53,11 @@ class ProjectsController < Projects::ApplicationController notice: "Project '#{@project.name}' was successfully updated." ) end - format.js else format.html { render 'edit' } - format.js end + + format.js end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 0f097f86816..b478580978b 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -15,29 +15,13 @@ module ButtonHelper # # See http://clipboardjs.com/#usage def clipboard_button(data = {}) + data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), class: "btn btn-clipboard", data: data, - type: :button - end - - # Output a "Copy to Clipboard" button with a custom CSS class - # - # data - Data attributes passed to `content_tag` - # css_class - Class passed to the `content_tag` - # - # Examples: - # - # # Define the target element - # clipboard_button_with_class({clipboard_target: "div#foo"}, css_class: "btn-clipboard") - # # => "<button class='btn btn-clipboard' data-clipboard-target='div#foo'>...</button>" - def clipboard_button_with_class(data = {}, css_class: 'btn-clipboard') - content_tag :button, - icon('clipboard'), - class: "btn #{css_class}", - data: data, - type: :button + type: :button, + title: "Copy to Clipboard" end def http_clone_button(project, placement = 'right', append_link: true) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index eb57516247d..adab901700c 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -8,6 +8,10 @@ module DiffHelper [marked_old_line, marked_new_line] end + def expand_all_diffs? + @expand_all_diffs || params[:expand_all_diffs].present? + end + def diff_view diff_views = %w(inline parallel) @@ -18,16 +22,14 @@ module DiffHelper end end - def diff_hard_limit_enabled? - params[:force_show_diff].present? - end - def diff_options - options = { ignore_whitespace_change: hide_whitespace? } - if diff_hard_limit_enabled? - options.merge!(Commit.max_diff_options) + default_options = Commit.max_diff_options + + if action_name == 'diff_for_path' + default_options[:paths] = params.values_at(:old_path, :new_path) end - options + + default_options.merge(ignore_whitespace_change: hide_whitespace?) end def safe_diff_files(diffs, diff_refs: nil, repository: nil) @@ -35,7 +37,7 @@ module DiffHelper end def unfold_bottom_class(bottom) - bottom ? 'js-unfold-bottom' : '' + bottom ? 'js-unfold js-unfold-bottom' : '' end def unfold_class(unfold) @@ -90,7 +92,7 @@ module DiffHelper def commit_for_diff(diff_file) return diff_file.content_commit if diff_file.content_commit - + if diff_file.deleted_file @base_commit || @commit.parent || @commit else diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index c7dedfe9254..db6e731c744 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -55,6 +55,10 @@ module MergeRequestsHelper end.sort.to_sentence end + def mr_closes_issues + @mr_closes_issues ||= @merge_request.closes_issues + end + def mr_change_branches_path(merge_request) new_namespace_project_merge_request_path( @project.namespace, @project, @@ -92,4 +96,8 @@ module MergeRequestsHelper ["#{source_path}:#{source_branch}", "#{target_path}:#{target_branch}"] end end + + def merge_request_button_visibility(merge_request, closed) + return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) + end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 2302e65c537..98143dcee9b 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -24,7 +24,15 @@ module NotesHelper }.to_json end - def link_to_new_diff_note(line_code, position, line_type = nil) + def diff_view_data + return {} unless @comments_target + + @comments_target.slice(:noteable_id, :noteable_type, :commit_id) + end + + def diff_view_line_data(line_code, position, line_type) + return if @diff_notes_disabled + use_legacy_diff_note = @use_legacy_diff_notes # If the controller doesn't force the use of legacy diff notes, we # determine this on a line-by-line basis by seeing if there already exist @@ -41,11 +49,8 @@ module NotesHelper end data = { - noteable_type: @comments_target[:noteable_type], - noteable_id: @comments_target[:noteable_id], - commit_id: @comments_target[:commit_id], - line_type: line_type, - line_code: line_code + line_code: line_code, + line_type: line_type, } if use_legacy_diff_note @@ -73,11 +78,7 @@ module NotesHelper ) end - button_tag(class: 'btn add-diff-note js-add-diff-note-button', - data: data, - title: 'Add a comment to this line') do - icon('comment-o') - end + data end def link_to_reply_discussion(note, line_type = nil) diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index f9fc525df6f..b165b569372 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -43,15 +43,15 @@ module SearchHelper # Autocomplete results for internal help pages def help_autocomplete [ - { category: "Help", label: "API Help", url: help_page_path("api", "README") }, - { category: "Help", label: "Markdown Help", url: help_page_path("markdown", "markdown") }, - { category: "Help", label: "Permissions Help", url: help_page_path("permissions", "permissions") }, - { category: "Help", label: "Public Access Help", url: help_page_path("public_access", "public_access") }, - { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks", "README") }, - { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh", "README") }, - { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks", "system_hooks") }, - { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks", "web_hooks") }, - { category: "Help", label: "Workflow Help", url: help_page_path("workflow", "README") }, + { category: "Help", label: "API Help", url: help_page_path("api/README") }, + { category: "Help", label: "Markdown Help", url: help_page_path("markdown/markdown") }, + { category: "Help", label: "Permissions Help", url: help_page_path("permissions/permissions") }, + { category: "Help", label: "Public Access Help", url: help_page_path("public_access/public_access") }, + { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks/README") }, + { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh/README") }, + { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks/system_hooks") }, + { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks/web_hooks") }, + { category: "Help", label: "Workflow Help", url: help_page_path("workflow/README") }, ] end diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb index 65598ad9ed3..d887cdadc34 100644 --- a/app/helpers/workhorse_helper.rb +++ b/app/helpers/workhorse_helper.rb @@ -28,4 +28,10 @@ module WorkhorseHelper headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format)) head :ok end + + # Send an entry from artifacts through Workhorse + def send_artifacts_entry(build, entry) + headers.store(*Gitlab::Workhorse.send_artifacts_entry(build, entry)) + head :ok + end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 236b6ab00d8..e0af7081411 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -29,7 +29,8 @@ module Emails # used in notify layout @target_url = @message.target_url @project = Project.find(project_id) - + @diff_notes_disabled = true + add_project_headers headers['X-GitLab-Author'] = @message.author_username diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 4066958f67c..630ee9601e0 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -23,7 +23,7 @@ class CommitRange attr_reader :commit_from, :notation, :commit_to attr_reader :ref_from, :ref_to - # Optional Project model + # The Project model attr_accessor :project # The beginning and ending refs can be named or SHAs, and @@ -56,7 +56,7 @@ class CommitRange # Initialize a CommitRange # # range_string - The String commit range. - # project - An optional Project model. + # project - The Project model. # # Raises ArgumentError if `range_string` does not match `PATTERN`. def initialize(range_string, project) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 083e93f1ee7..157901378d3 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -19,7 +19,7 @@ class MergeRequest < ActiveRecord::Base after_create :create_merge_request_diff, unless: :importing? after_update :update_merge_request_diff - delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil + delegate :commits, :real_size, to: :merge_request_diff, prefix: nil # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests @@ -164,6 +164,10 @@ class MergeRequest < ActiveRecord::Base merge_request_diff ? merge_request_diff.first_commit : compare_commits.first end + def diffs(*args) + merge_request_diff ? merge_request_diff.diffs(*args) : compare.diffs(*args) + end + def diff_size merge_request_diff.size end @@ -318,11 +322,11 @@ class MergeRequest < ActiveRecord::Base end def merge_event - self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last + @merge_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last end def closed_event - self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last + @closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last end WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index ba235750aeb..feaba925bad 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -46,7 +46,8 @@ class MergeRequestDiff < ActiveRecord::Base compare.diffs(options) end else - @diffs ||= load_diffs(st_diffs, options) + @diffs ||= {} + @diffs[options] ||= load_diffs(st_diffs, options) end end @@ -144,6 +145,12 @@ class MergeRequestDiff < ActiveRecord::Base def load_diffs(raw, options) if raw.respond_to?(:each) + if paths = options[:paths] + raw = raw.select do |diff| + paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) + end + end + Gitlab::Git::DiffCollection.new(raw, options) else Gitlab::Git::DiffCollection.new([]) diff --git a/app/models/note.rb b/app/models/note.rb index ffffd0c0838..8dca2ef09a8 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -10,6 +10,10 @@ class Note < ActiveRecord::Base # Banzai::ObjectRenderer. attr_accessor :note_html + # An Array containing the number of visible references as generated by + # Banzai::ObjectRenderer + attr_accessor :user_visible_reference_count + default_value_for :system, false attr_mentionable :note, pipeline: :note @@ -193,7 +197,15 @@ class Note < ActiveRecord::Base end def cross_reference_not_visible_for?(user) - cross_reference? && referenced_mentionables(user).empty? + cross_reference? && !has_referenced_mentionables?(user) + end + + def has_referenced_mentionables?(user) + if user_visible_reference_count.present? + user_visible_reference_count > 0 + else + referenced_mentionables(user).any? + end end def award_emoji? diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 58cb720c3c1..ce7d1c5d5b1 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -112,15 +112,7 @@ class IrkerService < Service # Authorize both irc://domain.com/#chan and irc://domain.com/chan if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? - # Do not authorize irc://domain.com/ - if uri.fragment.nil? && uri.path.length > 1 - uri.to_s - else - # Authorize irc://domain.com/smthg#chan - # The irker daemon will deal with it by concatenating smthg and - # chan, thus sending messages on #smthgchan - uri.to_s - end + uri.to_s end end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 538d91a77d7..affd21de924 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -750,7 +750,6 @@ class Repository if previous_path options[:file][:previous_path] = previous_path - Gitlab::Git::Blob.rename(raw_repository, options) else Gitlab::Git::Blob.commit(raw_repository, options) diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 016172c6d7e..f4bcb49b34d 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -72,6 +72,19 @@ class SentNotification < ActiveRecord::Base end end + def position=(new_position) + if new_position.is_a?(String) + new_position = JSON.parse(new_position) rescue nil + end + + if new_position.is_a?(Hash) + new_position = new_position.with_indifferent_access + new_position = Gitlab::Diff::Position.new(new_position) + end + + super(new_position) + end + def to_param self.reply_key end diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index e2bccbdbcc3..149822aa647 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch, diff_options = {}) + def execute(source_project, source_branch, target_project, target_branch) source_commit = source_project.commit(source_branch) return unless source_commit diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 21490ac77ea..b11ecd97a57 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -61,19 +61,14 @@ module MergeRequests merge_requests.each do |merge_request| if merge_request.source_branch == @branch_name || force_push? merge_request.reload_diff - merge_request.mark_as_unchecked else mr_commit_ids = merge_request.commits.map(&:id) push_commit_ids = @commits.map(&:id) matches = mr_commit_ids & push_commit_ids - - if matches.any? - merge_request.reload_diff - merge_request.mark_as_unchecked - else - merge_request.mark_as_unchecked - end + merge_request.reload_diff if matches.any? end + + merge_request.mark_as_unchecked end end diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index 752c11d7ae6..c9ad710b7bf 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -7,8 +7,6 @@ # module Projects class HousekeepingService < BaseService - include Gitlab::ShellAdapter - LEASE_TIMEOUT = 3600 class LeaseTaken < StandardError @@ -24,7 +22,7 @@ module Projects def execute raise LeaseTaken unless try_obtain_lease - GitlabShellOneShotWorker.perform_async(:gc, @project.repository_storage_path, @project.path_with_namespace) + GitGarbageCollectWorker.perform_async(@project.id) ensure Gitlab::Metrics.measure(:reset_pushes_since_gc) do update_pushes_since_gc(0) diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index dc083e50178..92e2dae4842 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -13,7 +13,7 @@ .col-sm-10 = f.text_area :description, class: "form-control", rows: 10 .hint - Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown', 'markdown'), target: '_blank'}. + Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown/markdown'), target: '_blank'}. .form-group = f.label :logo, class: 'control-label' .col-sm-10 diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 8de28528cda..538d8176ce7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -38,11 +38,11 @@ = source %span.help-block#import-sources-help Enabled sources for code import during project creation. OmniAuth must be configured for GitHub - = link_to "(?)", help_page_path("integration", "github") + = link_to "(?)", help_page_path("integration/github") , Bitbucket - = link_to "(?)", help_page_path("integration", "bitbucket") + = link_to "(?)", help_page_path("integration/bitbucket") and GitLab.com - = link_to "(?)", help_page_path("integration", "gitlab") + = link_to "(?)", help_page_path("integration/gitlab") .form-group %label.control-label.col-sm-2 Enabled Git access protocols .col-sm-10 diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml index 15aa059c93d..5c410a695bf 100644 --- a/app/views/admin/deploy_keys/new.html.haml +++ b/app/views/admin/deploy_keys/new.html.haml @@ -14,7 +14,7 @@ .col-sm-10 %p.light Paste a machine public key here. Read more about how to generate it - = link_to "here", help_page_path("ssh", "README") + = link_to "here", help_page_path("ssh/README") = f.text_area :key, class: "form-control thin_area", rows: 5 .form-actions diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 522153b37e3..40c8169ad9d 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -79,7 +79,7 @@ .panel-body.form-holder %p.light Read more about project permissions - %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + %strong= link_to "here", help_page_path("permissions/permissions"), class: "vlink" = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do %div diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 7b388cf7862..c217490963f 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -3,7 +3,7 @@ System hooks %p.light - #{link_to "System hooks ", help_page_path("system_hooks", "system_hooks"), class: "vlink"} can be + #{link_to "System hooks ", help_page_path("system_hooks/system_hooks"), class: "vlink"} can be used for binding events when GitLab creates a User or Project. %hr diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 2c5aba71699..b2c607361b3 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -132,7 +132,7 @@ - else passed. - = link_to icon('question-circle'), help_page_path('administration', 'repository_checks') + = link_to icon('question-circle'), help_page_path('administration/repository_checks') .form-group = f.submit 'Trigger repository check', class: 'btn btn-primary' diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index fe0b9d3a491..3145212728f 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -44,7 +44,7 @@ %legend Access .form-group = f.label :projects_limit, class: 'control-label' - .col-sm-10= f.number_field :projects_limit, class: 'form-control' + .col-sm-10= f.number_field :projects_limit, min: 0, class: 'form-control' .form-group = f.label :can_create_group, class: 'control-label' diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index 012e9857642..2febeef99d3 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -3,4 +3,4 @@ %h3 Access Denied %hr %p You are not allowed to access this page. -%p Read more about project permissions #{link_to "here", help_page_path("permissions", "permissions"), class: "vlink"} +%p Read more about project permissions #{link_to "here", help_page_path("permissions/permissions"), class: "vlink"} diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 9b838b9f3b7..6306fe6d0bf 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -10,7 +10,6 @@ - if current_user .pull-right = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do - = icon('plus') New Snippet .oneline diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index e7ab4f2409b..13ded2bc455 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -12,7 +12,7 @@ = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2" .help-block Read more about role permissions - %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + %strong= link_to "here", help_page_path("permissions/permissions"), class: "vlink" .form-actions = f.submit 'Add users to group', class: "btn btn-create" diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 121a7de3ad7..a8fdbd8c426 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -6,7 +6,6 @@ .nav-controls - if can?(current_user, :admin_milestones, @group) = link_to new_group_milestone_path(@group), class: "btn btn-new" do - = icon('plus') New Milestone .row-content-block diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index c2f2d9912f7..33fee334d93 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -7,7 +7,6 @@ - if can? current_user, :admin_group, @group .controls = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do - = icon('plus') New Project %ul.well-list - @projects.each do |project| diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index 0398afb4c1d..be257b51b9e 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,3 +1,3 @@ -- page_title @file.humanize, *@category.split("/").reverse.map(&:humanize) +- page_title @path.split("/").reverse.map(&:humanize) .documentation.wiki = markdown @markdown.gsub('$your_email', current_user.try(:email) || "email@example.com") diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index d676bc28c89..431d312b4ca 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -549,4 +549,4 @@ %li wiki page %li help page - You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown", "markdown")}. + You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown/markdown")}. diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml index 435ed7bd4cb..4c6af0b7908 100644 --- a/app/views/import/github/new.html.haml +++ b/app/views/import/github/new.html.haml @@ -38,6 +38,6 @@ As an administrator you may like to configure - else Consider asking your GitLab administrator to configure - = link_to 'GitHub integration', help_page_path("integration", "github") + = link_to 'GitHub integration', help_page_path("integration/github") which will allow login via GitHub and allow importing projects without generating a Personal Access Token. diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index cc8ea066cb9..3612f1ce5c6 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -1,4 +1,4 @@ -.flash-container +.flash-container.flash-container-page - if alert .flash-alert = alert diff --git a/app/views/profiles/_head.html.haml b/app/views/profiles/_head.html.haml new file mode 100644 index 00000000000..003884a5bd9 --- /dev/null +++ b/app/views/profiles/_head.html.haml @@ -0,0 +1,3 @@ +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/cropper.js') + = page_specific_javascript_tag('profile/application.js') diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 8efe486e01b..57d16d29158 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,4 +1,5 @@ - page_title "Account" += render 'profiles/head' - if current_user.ldap_user? .alert.alert-info diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index 9c404b6935f..9fe86e6b291 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,4 +1,5 @@ - page_title "Audit Log" += render 'profiles/head' .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 6f7fefdb46d..dc499be885b 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,4 +1,5 @@ - page_title "Emails" += render 'profiles/head' .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml index b3ed59a1a4a..6ea358d9f63 100644 --- a/app/views/profiles/keys/_form.html.haml +++ b/app/views/profiles/keys/_form.html.haml @@ -4,7 +4,7 @@ .form-group = f.label :key, class: 'label-light' - = f.text_area :key, class: "form-control", rows: 8, required: true + = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the SSH key. Paste the public part, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'." .form-group = f.label :title, class: 'label-light' = f.text_field :title, class: "form-control", required: true diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 6a067a03535..a42b3b8eb38 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -11,7 +11,7 @@ Add an SSH key %p.profile-settings-content Before you can add an SSH key you need to - = link_to "generate it.", help_page_path("ssh", "README") + = link_to "generate it.", help_page_path("ssh/README") = render 'form' %hr %h5 diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml index 89f6f01581a..6283ceebf10 100644 --- a/app/views/profiles/keys/show.html.haml +++ b/app/views/profiles/keys/show.html.haml @@ -1,2 +1,3 @@ - page_title @key.title, "SSH Keys" += render 'profiles/head' = render "key_details" diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index f77738f97f5..844fce59704 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,4 +1,5 @@ - page_title "Notifications" += render 'profiles/head' %div - if @user.errors.any? diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index 1b45548bd02..71ac367830d 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -1,4 +1,5 @@ - page_title "Personal Access Tokens" += render 'profiles/head' .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 1b1b16d656f..2afa026847a 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1,4 +1,5 @@ - page_title 'Preferences' += render 'profiles/head' = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f| .col-lg-3.profile-settings-sidebar @@ -42,12 +43,12 @@ .form-group = f.label :dashboard, class: 'label-light' do Default Dashboard - = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank') + = link_to('(?)', help_page_path('profile/preferences') + '#default-dashboard', target: '_blank') = f.select :dashboard, dashboard_choices, {}, class: 'form-control' .form-group = f.label :project_view, class: 'label-light' do Project view - = link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank') + = link_to('(?)', help_page_path('profile/preferences') + '#default-project-view', target: '_blank') = f.select :project_view, project_view_choices, {}, class: 'form-control' .help-block Choose what content you want to see on a project's home page. diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index eef50d887c7..d9fa74fad90 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,3 +1,5 @@ += render 'profiles/head' + = form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f| = form_errors(@user) diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 593be2617c1..8780da1dec4 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,5 +1,6 @@ - page_title 'Two-Factor Authentication', 'Account' - header_title "Two-Factor Authentication", profile_two_factor_auth_path += render 'profiles/head' .row.prepend-top-default .col-lg-3 @@ -13,7 +14,7 @@ - else %p Download the Google Authenticator application from App Store or Google Play Store and scan this code. - More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}. + More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}. .row.append-bottom-10 .col-md-3 = raw @qr_code diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml index 2987f6b5b22..e74fd5b93ea 100644 --- a/app/views/projects/_bitbucket_import_modal.html.haml +++ b/app/views/projects/_bitbucket_import_modal.html.haml @@ -10,4 +10,4 @@ as administrator you need to configure - else ask your GitLab administrator to configure - == #{link_to 'OAuth integration', help_page_path("integration", "bitbucket")}. + == #{link_to 'OAuth integration', help_page_path("integration/bitbucket")}. diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml index 0568c2d305e..fff30f11d82 100644 --- a/app/views/projects/_builds_settings.html.haml +++ b/app/views/projects/_builds_settings.html.haml @@ -4,7 +4,7 @@ - unless @repository.gitlab_ci_yml .form-group %p Builds need to be configured before you can begin using Continuous Integration. - = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' + = link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info' .form-group %p Get recent application code using the following command: .radio diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml index 377cf0187b8..e9f39b16aa7 100644 --- a/app/views/projects/_gitlab_import_modal.html.haml +++ b/app/views/projects/_gitlab_import_modal.html.haml @@ -10,4 +10,4 @@ as administrator you need to configure - else ask your GitLab administrator to configure - == #{link_to 'OAuth integration', help_page_path("integration", "gitlab")}. + == #{link_to 'OAuth integration', help_page_path("integration/gitlab")}. diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index 771a2e0df7d..19b4249374b 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -8,4 +8,4 @@ %strong Only allow merge requests to be merged if the build succeeds .help-block Builds need to be configured to enable this feature. - = link_to icon('question-circle'), help_page_path('workflow', 'merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') + = link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index a131289ee97..381b3754cd5 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -31,7 +31,7 @@ data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post - unless @repository.gitlab_ci_yml - = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' + = link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info' = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 4e801cc72fe..4421f3b9562 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -67,4 +67,4 @@ = render "sidebar" :javascript - new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{trace_with_state[:state]}") + new Build("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{trace_with_state[:state]}") diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 929496f81d8..c8c7b858baa 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -25,7 +25,7 @@ .commit-actions.hidden-xs - if commit.status = render_commit_status(commit, cssclass: 'btn btn-transparent') - = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent') + = 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/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index 894c36a96df..901605f7ca3 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -9,5 +9,5 @@ .form-group %p.light.append-bottom-0 Paste a machine public key here. Read more about how to generate it - = link_to "here", help_page_path("ssh", "README") + = link_to "here", help_page_path("ssh/README") = f.submit "Add key", class: "btn-create btn" diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml new file mode 100644 index 00000000000..0c0424edffd --- /dev/null +++ b/app/views/projects/diffs/_content.html.haml @@ -0,0 +1,29 @@ +.diff-content.diff-wrap-lines + - # Skip all non non-supported blobs + - return unless blob.respond_to?(:text?) + - if diff_file.too_large? + .nothing-here-block This diff could not be displayed because it is too large. + - elsif blob.only_display_raw? + .nothing-here-block This file is too large to display. + - elsif blob_text_viewable?(blob) + - if !project.repository.diffable?(blob) + .nothing-here-block This diff was suppressed by a .gitattributes entry. + - elsif diff_file.diff_lines.length > 0 + - if diff_file.collapsed_by_default? && !expand_all_diffs? + - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path)) + .nothing-here-block.diff-collapsed{data: { diff_for_path: url } } + This diff is collapsed. Click to expand it. + - elsif diff_view == 'parallel' + = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob + - else + = render "projects/diffs/text_file", diff_file: diff_file + - else + - if diff_file.mode_changed? + .nothing-here-block File mode changed + - elsif diff_file.renamed_file + .nothing-here-block File moved + - elsif blob.image? + - old_blob = diff_file.old_blob(diff_commit) + = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob + - else + .nothing-here-block No preview for this file type diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 1975287faee..20aaab5accf 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -6,6 +6,8 @@ .content-block.oneline-block.files-changed .inline-parallel-buttons + - unless expand_all_diffs? + = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: 'html')), class: 'btn btn-default' - if show_whitespace_toggle - if current_controller?(:commit) = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs') @@ -21,7 +23,7 @@ - if diff_files.overflow? = render 'projects/diffs/warning', diff_files: diff_files -.files +.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, @project))}} - diff_files.each_with_index do |diff_file, index| - diff_commit = commit_for_diff(diff_file) - blob = diff_file.blob(diff_commit) diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 3b758a1ec4e..c306909fb1a 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -16,28 +16,4 @@ = view_file_btn(diff_commit.id, diff_file, project) - .diff-content.diff-wrap-lines - - # Skip all non non-supported blobs - - return unless blob.respond_to?(:text?) - - if diff_file.too_large? - .nothing-here-block This diff could not be displayed because it is too large. - - elsif blob.only_display_raw? - .nothing-here-block This file is too large to display. - - elsif blob_text_viewable?(blob) - - if !project.repository.diffable?(blob) - .nothing-here-block This diff was suppressed by a .gitattributes entry. - - elsif diff_file.diff_lines.length > 0 - - if diff_view == 'parallel' - = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i - - else - = render "projects/diffs/text_file", diff_file: diff_file, index: i - - else - - if diff_file.mode_changed? - .nothing-here-block File mode changed - - elsif diff_file.renamed_file - .nothing-here-block File moved - - elsif blob.image? - - old_blob = diff_file.old_blob(diff_commit) - = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob, index: i - - else - .nothing-here-block No preview for this file type + = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, diff_refs: diff_refs, blob: blob, project: project diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index 22cad00240a..5a8a131d10c 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -13,18 +13,15 @@ %td.line_content.match= line.text - else %td.old_line.diff-line-num{ class: type, data: { linenumber: line.old_pos } } - - link_text = type == "new" ? " ".html_safe : line.old_pos + - link_text = type == "new" ? " " : line.old_pos - if plain = link_text - else - = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text } - - - if !plain && !@diff_notes_disabled && can?(current_user, :create_note, @project) - = link_to_new_diff_note(line_code, position) + %a{href: "##{line_code}", data: { linenumber: link_text }} %td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } } - - link_text = type == "old" ? " ".html_safe : line.new_pos + - link_text = type == "old" ? " " : line.new_pos - if plain = link_text - else - = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text } - %td.line_content.noteable_line{ class: [type, line_code], data: { line_code: line_code, position: position.to_json } }= diff_line_content(line.text, type) + %a{href: "##{line_code}", data: { linenumber: link_text }} + %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, position, type) unless plain) }= diff_line_content(line.text, type) diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml index 0cd888876e0..b9c0d9dcdfd 100644 --- a/app/views/projects/diffs/_match_line_parallel.html.haml +++ b/app/views/projects/diffs/_match_line_parallel.html.haml @@ -1,4 +1,4 @@ -%td.old_line.diff-line-num +%td.old_line.diff-line-num.empty-cell %td.line_content.parallel.match= line -%td.new_line.diff-line-num +%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 51f207dce94..d208fcee10b 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -1,39 +1,34 @@ / Side-by-side diff view -%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight +%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ data: diff_view_data } %table - diff_file.parallel_diff_lines.each do |line| - left = line[:left] - right = line[:right] %tr.line_holder.parallel - if left[:type] == 'match' - = render "projects/diffs/match_line_parallel", { line: left[:text], - line_old: left[:number], line_new: right[:number] } + = render "projects/diffs/match_line_parallel", { line: left[:text] } - elsif left[:type] == 'nonewline' - %td.old_line.diff-line-num + %td.old_line.diff-line-num.empty-cell %td.line_content.parallel.match= left[:text] - %td.new_line.diff-line-num + %td.new_line.diff-line-num.empty-cell %td.line_content.parallel.match= left[:text] - else - %td.old_line.diff-line-num{id: left[:line_code], class: [left[:type], ('empty-cell' unless left[:number])]} - = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code] - - if !@diff_notes_disabled && can?(current_user, :create_note, @project) - = link_to_new_diff_note(left[:line_code], left[:position], 'old') - %td.line_content.parallel.noteable_line{class: [left[:type], left[:line_code], ('empty-cell' if left[:text].empty?)], data: { line_code: left[:line_code], position: left[:position].to_json }}= diff_line_content(left[:text]) + %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]) - if right[:type] == 'new' - - new_line_class = 'new' + - new_line_type = 'new' - new_line_code = right[:line_code] - new_position = right[:position] - else - - new_line_class = nil + - 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_class, ('empty-cell' unless right[:number])], data: { linenumber: right[:number] }} - = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code - - if !@diff_notes_disabled && can?(current_user, :create_note, @project) - = link_to_new_diff_note(new_line_code, new_position, 'new') - %td.line_content.parallel.noteable_line{class: [new_line_class, new_line_code, ('empty-cell' if right[:text].empty?)], data: { line_code: new_line_code, position: new_position.to_json }}= diff_line_content(right[:text]) + %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]) - unless @diff_notes_disabled - notes_left, notes_right = organize_comments(left, right) diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index 192093d1273..196f8122db3 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -3,7 +3,7 @@ .suppressed-container %a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show. -%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' } +%table.text-file.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' } - last_line = 0 - diff_file.highlighted_diff_lines.each do |line| - last_line = line.new_pos diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index 15536c17f8e..10fa1ddf2e5 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -2,9 +2,6 @@ %h4 Too many changes to show. .pull-right - - unless diff_hard_limit_enabled? - = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm" - - if current_controller?(:commit) or current_controller?(:merge_requests) - if current_controller?(:commit) = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 27a94fe02dc..57af167180b 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -23,7 +23,7 @@ .form-group.project-visibility-level-holder = f.label :visibility_level, class: 'label-light' do Visibility Level - = link_to "(?)", help_page_path("public_access", "public_access") + = link_to "(?)", help_page_path("public_access/public_access") - if can_change_visibility_level?(@project, current_user) = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project) - else diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 5242021243e..303d7c23d01 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -17,7 +17,7 @@ Environments are places where code gets deployed, such as staging or production. %br = succeed "." do - = link_to "Read more about environments", help_page_path("ci", "environments") + = link_to "Read more about environments", help_page_path("ci/environments") - if can?(current_user, :create_environment, @project) = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index da325efecd2..89e06567196 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -7,6 +7,6 @@ %p Environments allow you to track deployments of your application = succeed "." do - = link_to "Read more about environments", help_page_path("ci", "environments") + = link_to "Read more about environments", help_page_path("ci/environments") = render 'form' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 53c62ef234d..b17aba2431f 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -20,7 +20,7 @@ Define environments in the deploy stage(s) in %code .gitlab-ci.yml to track deployments here. - = link_to "Read more", help_page_path("ci", "environments"), class: "btn btn-success" + = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success" - else .table-holder %table.table.environments diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 2ec96308fd7..873ed9b59ee 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -42,7 +42,7 @@ = succeed '.' do = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - - if @commits.present? + - if @commits_count.nonzero? %ul.merge-request-tabs.nav-links.no-top.no-bottom %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do @@ -51,7 +51,7 @@ %li.commits-tab = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do Commits - %span.badge= @commits.size + %span.badge= @commits_count - if @pipeline %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index b3bea900d42..b727efaa6a6 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -8,7 +8,7 @@ %p %strong Step 1. Fetch and check out the branch for this merge request - = clipboard_button_with_class({clipboard_target: "pre#merge-info-1"}, css_class: "btn-clipboard") + = clipboard_button(clipboard_target: "pre#merge-info-1") %pre.dark#merge-info-1 - if @merge_request.for_fork? :preserve @@ -25,7 +25,7 @@ %p %strong Step 3. Merge the branch and fix any conflicts that come up - = clipboard_button_with_class({clipboard_target: "pre#merge-info-3"}, css_class: "btn-clipboard") + = clipboard_button(clipboard_target: "pre#merge-info-3") %pre.dark#merge-info-3 - if @merge_request.for_fork? :preserve @@ -38,7 +38,7 @@ %p %strong Step 4. Push the result of the merge to GitLab - = clipboard_button_with_class({clipboard_target: "pre#merge-info-4"}, css_class: "btn-clipboard") + = clipboard_button(clipboard_target: "pre#merge-info-4") %pre.dark#merge-info-4 :preserve git push origin #{h @merge_request.target_branch} diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 5bf5210aeab..b24bdf22ceb 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -19,13 +19,13 @@ Options .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul - %li{ class: issue_button_visibility(@merge_request, true) } + %li{ class: merge_request_button_visibility(@merge_request, true) } = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' - %li{ class: issue_button_visibility(@merge_request, false) } + %li{ class: merge_request_button_visibility(@merge_request, false) } = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' %li = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit' - = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request' - = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request' + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request' + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request' = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do Edit diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 0e0af57d76e..dc18f715f25 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -22,10 +22,10 @@ - elsif @merge_request.can_be_merged? = render 'projects/merge_requests/widget/open/accept' - - if @closes_issues.present? + - if mr_closes_issues.present? .mr-widget-footer %span %i.fa.fa-check - Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)} + Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)} = succeed '.' do - != markdown issues_sentence(@closes_issues), pipeline: :gfm, author: @merge_request.author + != markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 05f33b78a47..9b00bdedc27 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -107,7 +107,7 @@ .form-group.project-visibility-level-holder = f.label :visibility_level, class: 'label-light' do Visibility Level - = link_to "(?)", help_page_path("public_access", "public_access") + = link_to "(?)", help_page_path("public_access/public_access") = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project) = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml index 7d1cbc62e86..25466e7562e 100644 --- a/app/views/projects/notes/_hints.html.haml +++ b/app/views/projects/notes/_hints.html.haml @@ -1,7 +1,7 @@ .comment-toolbar.clearfix .toolbar-text Styling with - = link_to 'Markdown', help_page_path('markdown', 'markdown'), target: '_blank', tabindex: -1 + = link_to 'Markdown', help_page_path('markdown/markdown'), target: '_blank', tabindex: -1 is supported %button.toolbar-button.markdown-selector{ type: 'button', tabindex: '-1' } = icon('file-image-o', class: 'toolbar-button-icon') diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 1c39ce897a3..56d302fab82 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -2,6 +2,8 @@ = render "projects/notes/notes" %ul.notes.notes-form.timeline %li.timeline-entry + .flash-container.timeline-content + - if can? current_user, :create_note, @project .timeline-icon.hidden-xs.hidden-sm %a.author_link{ href: user_path(current_user) } diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 6a127afa410..7c225e2b282 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -31,7 +31,7 @@ New pipeline - unless @repository.gitlab_ci_yml - = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' + = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 82892a33358..ea3d82d858e 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -12,7 +12,7 @@ = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2" .help-block Read more about role permissions - %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + %strong= link_to "here", help_page_path("permissions/permissions"), class: "vlink" .form-actions = f.submit 'Add users to project', class: "btn btn-create" diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 5669713d9a1..3fab95751e0 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -8,10 +8,10 @@ %p.prepend-top-20 Protected branches are designed to: %ul - %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} + %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions/permissions"), class: "vlink"} %li prevent anyone from force pushing to the branch %li prevent anyone from deleting the branch - %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"} + %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("permissions/permissions"), class: "underlined-link"} .col-lg-9 %h5.prepend-top-0 Protect a branch @@ -23,7 +23,7 @@ = f.label :name, "Branch", class: "label-light" = render partial: "dropdown", locals: { f: f } %p.help-block - = link_to "Wildcards", help_page_path(category: 'workflow', file: 'protected_branches', format: 'md', anchor: "wildcard-protected-branches") + = link_to "Wildcards", help_page_path('workflow/protected_branches', anchor: "wildcard-protected-branches") such as %code *-stable or diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 58d8e068754..dd1cf680cfa 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -82,4 +82,4 @@ Archived project! Repository is read-only %div{class: "project-show-#{default_project_view}"} - = render default_project_view + = render default_project_view
\ No newline at end of file diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index bf57beb9d07..bdbf3e5f4d6 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -1,27 +1,29 @@ .hidden-xs - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do - = icon('plus') - New Snippet + - if can?(current_user, :create_project_snippet, @project) + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do + New Snippet - if can?(current_user, :update_project_snippet, @snippet) = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do Edit - if can?(current_user, :update_project_snippet, @snippet) = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do Delete -.visible-xs-block.dropdown - %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } - Options - %span.caret - .dropdown-menu.dropdown-menu-full-width - %ul - %li - = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do - New Snippet - - if can?(current_user, :update_project_snippet, @snippet) - %li - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do - Edit - - if can?(current_user, :update_project_snippet, @snippet) - %li - = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do - Delete +- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) + .visible-xs-block.dropdown + %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } + Options + %span.caret + .dropdown-menu.dropdown-menu-full-width + %ul + - if can?(current_user, :create_project_snippet, @project) + %li + = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do + New Snippet + - if can?(current_user, :update_project_snippet, @snippet) + %li + = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do + Edit + - if can?(current_user, :update_project_snippet, @snippet) + %li + = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do + Delete diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 96fee3b17b2..6c994ae486b 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -2,9 +2,9 @@ .row-content-block.top-block .pull-right - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do - = icon('plus') - New Snippet + - if can?(current_user, :create_project_snippet, @project) + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do + New Snippet .oneline Share code pastes with others out of git repository diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml index 1c6ec198d3d..107ad19177c 100644 --- a/app/views/shared/_visibility_level.html.haml +++ b/app/views/shared/_visibility_level.html.haml @@ -1,7 +1,7 @@ .form-group.project-visibility-level-holder = f.label :visibility_level, class: 'control-label' do Visibility Level - = link_to "(?)", help_page_path("public_access", "public_access") + = link_to "(?)", help_page_path("public_access/public_access") .col-sm-10 - if can_change_visibility_level = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml index 975c74f4ea6..dee2472fa79 100644 --- a/app/views/shared/milestones/_summary.html.haml +++ b/app/views/shared/milestones/_summary.html.haml @@ -26,7 +26,6 @@ %span.pull-right.tab-issues-buttons - if project && can?(current_user, :create_issue, project) = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn btn-grouped", title: "New Issue" do - %i.fa.fa-plus New Issue = link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped" %span.pull-right.tab-merge-requests-buttons.hidden diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index d1e861ca80c..2585ed9360b 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -6,7 +6,7 @@ %h4.prepend-top-0 = page_title %p - #{link_to "Webhooks", help_page_path("web_hooks", "web_hooks")} can be + #{link_to "Webhooks", help_page_path("web_hooks/web_hooks")} can be used for binding events when something is happening within the project. .col-lg-9.append-bottom-default = form_for hook, as: :hook, url: polymorphic_path(url_components + [:hooks]) do |f| diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index a7769654b61..160c6cd84da 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -1,27 +1,28 @@ .hidden-xs - = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do - = icon('plus') - New Snippet + - if current_user + = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do + New Snippet - if can?(current_user, :update_personal_snippet, @snippet) = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do Edit - if can?(current_user, :admin_personal_snippet, @snippet) - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do Delete -.visible-xs-block.dropdown - %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } - Options - %span.caret - .dropdown-menu.dropdown-menu-full-width - %ul - %li - = link_to new_snippet_path, title: "New Snippet" do - New Snippet - - if can?(current_user, :update_personal_snippet, @snippet) +- if current_user + .visible-xs-block.dropdown + %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } + Options + %span.caret + .dropdown-menu.dropdown-menu-full-width + %ul %li - = link_to edit_snippet_path(@snippet) do - Edit - - if can?(current_user, :admin_personal_snippet, @snippet) - %li - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do - Delete + = link_to new_snippet_path, title: "New Snippet" do + New Snippet + - if can?(current_user, :update_personal_snippet, @snippet) + %li + = link_to edit_snippet_path(@snippet) do + Edit + - if can?(current_user, :admin_personal_snippet, @snippet) + %li + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do + Delete diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb new file mode 100644 index 00000000000..2fa3c838f55 --- /dev/null +++ b/app/workers/git_garbage_collect_worker.rb @@ -0,0 +1,14 @@ +class GitGarbageCollectWorker + include Sidekiq::Worker + include Gitlab::ShellAdapter + + sidekiq_options queue: :gitlab_shell, retry: false + + def perform(project_id) + project = Project.find(project_id) + + gitlab_shell.gc(project.repository_storage_path, project.path_with_namespace) + # Expire the branch cache in case garbage collection caused a ref lookup to fail + project.repository.after_create_branch + end +end diff --git a/app/workers/gitlab_shell_one_shot_worker.rb b/app/workers/gitlab_shell_one_shot_worker.rb deleted file mode 100644 index 4ddbcf574d5..00000000000 --- a/app/workers/gitlab_shell_one_shot_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class GitlabShellOneShotWorker - include Sidekiq::Worker - include Gitlab::ShellAdapter - - sidekiq_options queue: :gitlab_shell, retry: false - - def perform(action, *arg) - gitlab_shell.send(action, *arg) - end -end diff --git a/config/application.rb b/config/application.rb index 2b0595ede2b..21e7cc7b6e8 100644 --- a/config/application.rb +++ b/config/application.rb @@ -84,6 +84,7 @@ module Gitlab config.assets.precompile << "graphs/application.js" config.assets.precompile << "users/application.js" config.assets.precompile << "network/application.js" + config.assets.precompile << "profile/application.js" config.assets.precompile << "lib/utils/*.js" config.assets.precompile << "lib/*.js" diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb index 6796407d4e6..4c91a61fb4a 100644 --- a/config/initializers/health_check.rb +++ b/config/initializers/health_check.rb @@ -1,16 +1,3 @@ -# Email forcibly included in the standard checks, but the email health check -# doesn't support the full range of SMTP options, which can result in failures -# for valid SMTP configurations. -# Overwrite the HealthCheck's detection of whether email is configured -# in order to avoid the email check during standard checks -module HealthCheck - class Utils - def self.mailer_configured? - false - end - end -end - HealthCheck.setup do |config| config.standard_checks = ['database', 'migrations', 'cache'] config.full_checks = ['database', 'migrations', 'cache'] diff --git a/config/routes.rb b/config/routes.rb index 18a4ead2b37..3160fd767b8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -89,8 +89,9 @@ Rails.application.routes.draw do mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put] # Help + get 'help' => 'help#index' - get 'help/:category/:file' => 'help#show', as: :help_page, constraints: { category: /.*/, file: /[^\/\.]+/ } + get 'help/*path' => 'help#show', as: :help_page get 'help/shortcuts' get 'help/ui' => 'help#ui' @@ -615,10 +616,18 @@ Rails.application.routes.draw do post :retry_builds post :revert post :cherry_pick + get :diff_for_path end end - resources :compare, only: [:index, :create] + resources :compare, only: [:index, :create] do + collection do + get :diff_for_path + end + end + + get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } + resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do @@ -629,9 +638,6 @@ Rails.application.routes.draw do end end - get '/compare/:from...:to' => 'compare#show', :as => 'compare', - :constraints => { from: /.+/, to: /.+/ } - resources :snippets, constraints: { id: /\d+/ } do member do get 'raw' @@ -706,12 +712,14 @@ Rails.application.routes.draw do post :toggle_subscription post :toggle_award_emoji post :remove_wip + get :diff_for_path end collection do get :branch_from get :branch_to get :update_branches + get :diff_for_path end end diff --git a/db/migrate/20160616102642_remove_duplicated_keys.rb b/db/migrate/20160616102642_remove_duplicated_keys.rb index 00a45d7fe73..180a75e0998 100644 --- a/db/migrate/20160616102642_remove_duplicated_keys.rb +++ b/db/migrate/20160616102642_remove_duplicated_keys.rb @@ -4,12 +4,12 @@ class RemoveDuplicatedKeys < ActiveRecord::Migration select_all("SELECT fingerprint FROM #{quote_table_name(:keys)} GROUP BY fingerprint HAVING COUNT(*) > 1").each do |row| fingerprint = connection.quote(row['fingerprint']) execute(%Q{ - DELETE FROM keys + DELETE FROM #{quote_table_name(:keys)} WHERE fingerprint = #{fingerprint} AND id != ( SELECT id FROM ( SELECT max(id) AS id - FROM keys + FROM #{quote_table_name(:keys)} WHERE fingerprint = #{fingerprint} ) max_ids ) diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb deleted file mode 100644 index a3f5073d511..00000000000 --- a/db/migrate/20160620110927_fix_no_validatable_import_url.rb +++ /dev/null @@ -1,106 +0,0 @@ -# Updates project records containing invalid URLs using the AddressableUrlValidator. -# This is optimized assuming the number of invalid records is low, but -# we still need to loop through all the projects with an +import_url+ -# so we use batching for the latter. -# -# This migration is non-reversible as we would have to keep the old data. - -class FixNoValidatableImportUrl < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - class SqlBatches - - attr_reader :results, :query - - def initialize(batch_size: 1000, query:) - @offset = 0 - @batch_size = batch_size - @query = query - @results = [] - end - - def next? - @results = ActiveRecord::Base.connection.exec_query(batched_sql) - @offset += @batch_size - @results.any? - end - - private - - def batched_sql - "#{@query} LIMIT #{@batch_size} OFFSET #{@offset}" - end - end - - # AddressableValidator - Snapshot of AddressableUrlValidator - module AddressableUrlValidatorSnap - extend self - - def valid_url?(value) - return false unless value - - valid_uri?(value) && valid_protocol?(value) - rescue Addressable::URI::InvalidURIError - false - end - - def valid_uri?(value) - Addressable::URI.parse(value).is_a?(Addressable::URI) - end - - def valid_protocol?(value) - value =~ /\A#{URI.regexp(%w(http https ssh git))}\z/ - end - end - - def up - unless defined?(Addressable::URI::InvalidURIError) - say('Skipping cleaning up invalid import URLs as class from Addressable is missing') - return - end - - say('Nullifying empty import URLs') - - nullify_empty_urls - - say('Cleaning up invalid import URLs... This may take a few minutes if we have a large number of imported projects.') - - process_invalid_import_urls - end - - def process_invalid_import_urls - batches = SqlBatches.new(query: "SELECT id, import_url FROM projects WHERE import_url IS NOT NULL") - - while batches.next? - project_ids = [] - - batches.results.each do |result| - project_ids << result['id'] unless valid_url?(result['import_url']) - end - - process_batch(project_ids) - end - - end - - def process_batch(project_ids) - Thread.new do - begin - project_ids.each { |project_id| cleanup_import_url(project_id) } - ensure - ActiveRecord::Base.connection.close - end - end.join - end - - def valid_url?(url) - AddressableUrlValidatorSnap.valid_url?(url) - end - - def cleanup_import_url(project_id) - execute("UPDATE projects SET import_url = NULL WHERE id = #{project_id}") - end - - def nullify_empty_urls - execute("UPDATE projects SET import_url = NULL WHERE import_url = ''") - end -end diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md index 9fd7b71d2dc..e3306c22d3f 100644 --- a/doc/administration/custom_hooks.md +++ b/doc/administration/custom_hooks.md @@ -48,7 +48,8 @@ as appropriate. This feature was [introduced][5073] in GitLab 8.10. If the commit is declined or an error occurs during the Git hook check, -the STDERR and/or SDOUT message of the hook will be present in GitLab's UI. +the STDERR or STDOUT message of the hook will be present in GitLab's UI. +STDERR takes precedence over STDOUT. ![Custom message from custom Git hook](img/custom_hooks_error_msg.png) diff --git a/doc/api/groups.md b/doc/api/groups.md index 1ccb9715e96..87480bebfc4 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -42,46 +42,49 @@ Parameters: ```json
[
{
- "id": 4,
- "description": null,
+ "id": 9,
+ "description": "foo",
"default_branch": "master",
+ "tag_list": [],
"public": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
- "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
- "web_url": "http://example.com/diaspora/diaspora-client",
- "tag_list": [
- "example",
- "disapora client"
- ],
- "owner": {
- "id": 3,
- "name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
- },
- "name": "Diaspora Client",
- "name_with_namespace": "Diaspora / Diaspora Client",
- "path": "diaspora-client",
- "path_with_namespace": "diaspora/diaspora-client",
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git",
+ "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "Experimental / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
"issues_enabled": true,
"merge_requests_enabled": true,
- "builds_enabled": true,
"wiki_enabled": true,
- "snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
- "creator_id": 3,
+ "builds_enabled": true,
+ "snippets_enabled": true,
+ "created_at": "2016-04-05T21:40:50.169Z",
+ "last_activity_at": "2016-04-06T16:52:08.432Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
- "description": "",
- "id": 3,
- "name": "Diaspora",
- "owner_id": 1,
- "path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "id": 5,
+ "name": "Experimental",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-04-05T21:40:49.152Z",
+ "updated_at": "2016-04-07T08:07:48.466Z",
+ "description": "foo",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 10
},
- "archived": false,
- "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
+ "avatar_url": null,
+ "star_count": 1,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
}
]
```
@@ -96,7 +99,180 @@ GET /groups/:id Parameters:
-- `id` (required) - The ID or path of a group
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or path of a group |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4
+```
+
+Example response:
+
+```json
+{
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "visibility_level": 20,
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/twitter",
+ "projects": [
+ {
+ "id": 7,
+ "description": "Voluptas veniam qui et beatae voluptas doloremque explicabo facilis.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": true,
+ "archived": false,
+ "visibility_level": 20,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/typeahead-js.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/typeahead-js.git",
+ "web_url": "https://gitlab.example.com/twitter/typeahead-js",
+ "name": "Typeahead.Js",
+ "name_with_namespace": "Twitter / Typeahead.Js",
+ "path": "typeahead-js",
+ "path_with_namespace": "twitter/typeahead-js",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:25.578Z",
+ "last_activity_at": "2016-06-17T07:47:25.881Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
+ },
+ {
+ "id": 6,
+ "description": "Aspernatur omnis repudiandae qui voluptatibus eaque.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/flight.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/flight.git",
+ "web_url": "https://gitlab.example.com/twitter/flight",
+ "name": "Flight",
+ "name_with_namespace": "Twitter / Flight",
+ "path": "flight",
+ "path_with_namespace": "twitter/flight",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:24.661Z",
+ "last_activity_at": "2016-06-17T07:47:24.838Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 8,
+ "public_builds": true,
+ "shared_with_groups": []
+ }
+ ],
+ "shared_projects": [
+ {
+ "id": 8,
+ "description": "Velit eveniet provident fugiat saepe eligendi autem.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "git@gitlab.example.com:h5bp/html5-boilerplate.git",
+ "http_url_to_repo": "https://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "https://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "H5bp / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:27.089Z",
+ "last_activity_at": "2016-06-17T07:47:27.310Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 5,
+ "name": "H5bp",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:26.621Z",
+ "updated_at": "2016-06-17T07:47:26.621Z",
+ "description": "Id consequatur rem vel qui doloremque saepe.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 4,
+ "public_builds": true,
+ "shared_with_groups": [
+ {
+ "group_id": 4,
+ "group_name": "Twitter",
+ "group_access_level": 30
+ },
+ {
+ "group_id": 3,
+ "group_name": "Gitlab Org",
+ "group_access_level": 10
+ }
+ ]
+ }
+ ]
+}
+```
## New group
@@ -201,7 +377,8 @@ Example response: "star_count": 1,
"forks_count": 0,
"open_issues_count": 3,
- "public_builds": true
+ "public_builds": true,
+ "shared_with_groups": []
}
]
}
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index aee94b3fc36..a8c3b068d22 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -49,10 +49,10 @@ Parameters: "state": "active", "created_at": "2012-04-29T08:46:00Z" }, - "source_project_id": "2", - "target_project_id": "3", + "source_project_id": 2, + "target_project_id": 3, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -68,7 +68,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : false, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ] ``` @@ -113,10 +115,10 @@ Parameters: "state": "active", "created_at": "2012-04-29T08:46:00Z" }, - "source_project_id": "2", - "target_project_id": "3", + "source_project_id": 2, + "target_project_id": 3, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -132,7 +134,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -233,6 +237,8 @@ Parameters: "merge_status": "can_be_merged", "subscribed" : true, "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false, "changes": [ { "old_path": "VERSION", @@ -296,7 +302,7 @@ Parameters: "source_project_id": 4, "target_project_id": 4, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -312,7 +318,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 0 + "user_notes_count": 0, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -383,7 +391,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -465,7 +475,7 @@ Parameters: "source_project_id": 4, "target_project_id": 4, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -481,7 +491,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -531,7 +543,7 @@ Parameters: "source_project_id": 4, "target_project_id": 4, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -547,7 +559,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -866,7 +880,9 @@ Example response: "merge_when_build_succeeds": false, "merge_status": "unchecked", "subscribed": true, - "user_notes_count": 7 + "user_notes_count": 7, + "should_remove_source_branch": true, + "force_remove_source_branch": false }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7", "body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.", diff --git a/doc/api/projects.md b/doc/api/projects.md index 0425487ee58..dceee7b4ea7 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -52,7 +52,7 @@ Parameters: "owner": { "id": 3, "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" + "created_at": "2013-09-30T13:46:02Z" }, "name": "Diaspora Client", "name_with_namespace": "Diaspora / Diaspora Client", @@ -64,17 +64,18 @@ Parameters: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "archived": false, "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png", @@ -82,7 +83,8 @@ Parameters: "forks_count": 0, "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", - "public_builds": true + "public_builds": true, + "shared_with_groups": [] }, { "id": 6, @@ -112,6 +114,7 @@ Parameters: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, + "container_registry_enabled": false, "created_at": "2013-09-30T13:46:02Z", "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, @@ -140,7 +143,8 @@ Parameters: "forks_count": 0, "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", - "public_builds": true + "public_builds": true, + "shared_with_groups": [] } ] ``` @@ -223,7 +227,7 @@ Parameters: "owner": { "id": 3, "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" + "created_at": "2013-09-30T13:46:02Z" }, "name": "Diaspora Project Site", "name_with_namespace": "Diaspora / Diaspora Project Site", @@ -235,17 +239,18 @@ Parameters: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "permissions": { "project_access": { @@ -262,7 +267,20 @@ Parameters: "shared_runners_enabled": true, "forks_count": 0, "star_count": 0, - "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b" + "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", + "public_builds": true, + "shared_with_groups": [ + { + "group_id": 4, + "group_name": "Twitter", + "group_access_level": 30 + }, + { + "group_id": 3, + "group_name": "Gitlab Org", + "group_access_level": 10 + } + ] } ``` @@ -425,6 +443,7 @@ Parameters: - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `container_registry_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `import_url` (optional) @@ -449,6 +468,7 @@ Parameters: - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `container_registry_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `import_url` (optional) @@ -475,6 +495,7 @@ Parameters: - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `container_registry_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `public_builds` (optional) @@ -537,23 +558,26 @@ Example response: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "archived": true, "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png", "shared_runners_enabled": true, "forks_count": 0, - "star_count": 1 + "star_count": 1, + "public_builds": true, + "shared_with_groups": [] } ``` @@ -600,23 +624,26 @@ Example response: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "archived": true, "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png", "shared_runners_enabled": true, "forks_count": 0, - "star_count": 0 + "star_count": 0, + "public_builds": true, + "shared_with_groups": [] } ``` @@ -660,7 +687,7 @@ Example response: "owner": { "id": 3, "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" + "created_at": "2013-09-30T13:46:02Z" }, "name": "Diaspora Project Site", "name_with_namespace": "Diaspora / Diaspora Project Site", @@ -672,17 +699,18 @@ Example response: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "permissions": { "project_access": { @@ -699,7 +727,9 @@ Example response: "shared_runners_enabled": true, "forks_count": 0, "star_count": 0, - "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b" + "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", + "public_builds": true, + "shared_with_groups": [] } ``` @@ -743,7 +773,7 @@ Example response: "owner": { "id": 3, "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" + "created_at": "2013-09-30T13:46:02Z" }, "name": "Diaspora Project Site", "name_with_namespace": "Diaspora / Diaspora Project Site", @@ -755,17 +785,18 @@ Example response: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "permissions": { "project_access": { @@ -782,7 +813,9 @@ Example response: "shared_runners_enabled": true, "forks_count": 0, "star_count": 0, - "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b" + "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", + "public_builds": true, + "shared_with_groups": [] } ``` @@ -965,11 +998,11 @@ Parameters: "id": 1, "url": "http://example.com/hook", "project_id": 3, - "push_events": "true", - "issues_events": "true", - "merge_requests_events": "true", - "note_events": "true", - "enable_ssl_verification": "true", + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "note_events": true, + "enable_ssl_verification": true, "created_at": "2012-10-12T17:04:47Z" } ``` @@ -1089,8 +1122,8 @@ Parameters: "name": "Jeremy Ashkenas", "email": "jashkenas@example.com" }, - "authored_date": "2013-09-07T12: 58: 21+00: 00", - "committed_date": "2013-09-07T12: 58: 21+00: 00" + "authored_date": "2013-09-07T12:58:21+00:00", + "committed_date": "2013-09-07T12:58:21+00:00" }, "protected": false } diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md index 17e1c64bb8a..bfafcc44d66 100644 --- a/doc/ci/examples/php.md +++ b/doc/ci/examples/php.md @@ -49,7 +49,7 @@ apt-get update -yqq apt-get install git -yqq # Install phpunit, the tool that we will use for testing -curl -o /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar +curl -Lo /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar chmod +x /usr/local/bin/phpunit # Install mysql driver diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index d2d1b04f893..16a1461a7e4 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -133,7 +133,7 @@ builds, including deploy builds. This can be an array or a multi-line string. ### after_script >**Note:** -Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 (not yet released) +Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 `after_script` is used to define the command that will be run after for all builds. This has to be an array or a multi-line string. @@ -811,7 +811,7 @@ deploy: It's possible to overwrite globally defined `before_script` and `after_script`: ```yaml -before_script +before_script: - global before script job: diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md index 5893b7c219e..8d02c52c477 100644 --- a/doc/development/ui_guide.md +++ b/doc/development/ui_guide.md @@ -27,6 +27,8 @@ We try to keep the amount of tabs in the header navigation between 5 and 10 so t tab should represent separate functionality. Everything related to the issue tracker should be under the 'Issues' tab while everything related to the wiki should be under 'Wiki' tab and so on and so forth. +When adding a new tab to the header don't use more than 2 words for text in the link. +We want to keep links short and easy to remember and fit all of them in the small screen. ## Mobile screen size @@ -52,5 +54,6 @@ information from database or file system * Use red button for destructive actions (not revertable). For example removing issue. * Use green or blue button for primary action. Primary button should be only one. Do not use both green and blue button in one form. -* For all other cases use default white button +* For all other cases use default white button. +* Text button should have only first word capitalized. So should be "Create issue" instead of "Create Issue" diff --git a/doc/integration/github.md b/doc/integration/github.md index e7497e475c9..340c8a55fb3 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -19,7 +19,7 @@ GitHub will generate an application ID and secret key for you to use. - Application name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive. - Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com' - Application description: Fill this in if you wish. - - Default authorization callback URL is '${YOUR_DOMAIN}/import/github/callback' + - Authorization callback URL is 'http(s)://${YOUR_DOMAIN}' 1. Select "Register application". 1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 236eb7b12c4..fb2dd582754 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -7,11 +7,12 @@ * [Newlines](#newlines) * [Multiple underscores in words](#multiple-underscores-in-words) * [URL auto-linking](#url-auto-linking) +* [Multiline Blockquote](#multiline-blockquote) * [Code and Syntax Highlighting](#code-and-syntax-highlighting) * [Inline Diff](#inline-diff) * [Emoji](#emoji) * [Special GitLab references](#special-gitlab-references) -* [Task lists](#task-lists) +* [Task Lists](#task-lists) **[Standard Markdown](#standard-markdown)** @@ -89,6 +90,37 @@ GFM will autolink almost any URL you copy and paste into your text. * irc://irc.freenode.net/gitlab * http://localhost:3000 +## Multiline Blockquote + +On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, +GFM supports multiline blockquotes fenced by <code>>>></code>. + +```no-highlight +>>> +If you paste a message from somewhere else + +that + +spans + +multiple lines, + +you can quote that without having to manually prepend `>` to every line! +>>> +``` + +>>> +If you paste a message from somewhere else + +that + +spans + +multiple lines, + +you can quote that without having to manually prepend `>` to every line! +>>> + ## Code and Syntax Highlighting _GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md index a51790b0bda..84065a84e50 100644 --- a/doc/update/8.9-to-8.10.md +++ b/doc/update/8.9-to-8.10.md @@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-10-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v3.1.0 +sudo -u git -H git checkout v3.2.0 ``` ### 5. Update gitlab-workhorse diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index a95df038357..8b0cb90765e 100644 --- a/features/project/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -83,11 +83,6 @@ Feature: Project Commits #Given I visit my project's commits stats page #Then I see commits stats - Scenario: I browse big commit - Given I visit big commit page - Then I see big commit warning - And I see "Reload with full diff" link - Scenario: I browse a commit with an image Given I visit a commit with an image that changed Then The diff links to both the previous and current image diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature index 2bde4c8a99b..35687aac9ea 100644 --- a/features/project/commits/diff_comments.feature +++ b/features/project/commits/diff_comments.feature @@ -6,10 +6,6 @@ Feature: Project Commits Diff Comments And I visit project commit page @javascript - Scenario: I can access add diff comment buttons - Then I should see add a diff comment button - - @javascript Scenario: I can comment on a commit diff Given I leave a diff comment like "Typo, please fix" Then I should see a diff comment saying "Typo, please fix" diff --git a/features/steps/dashboard/help.rb b/features/steps/dashboard/help.rb index 800e869533e..9c94dc70df0 100644 --- a/features/steps/dashboard/help.rb +++ b/features/steps/dashboard/help.rb @@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps end step 'I visit the "Rake Tasks" help page' do - visit help_page_path("raketasks", "maintenance") + visit help_page_path("raketasks/maintenance") end step 'I should see "Rake Tasks" page markdown rendered' do diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb index 2876e8812e9..b4a32ed2e38 100644 --- a/features/steps/project/builds/artifacts.rb +++ b/features/steps/project/builds/artifacts.rb @@ -68,10 +68,16 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps end step 'download of a file extracted from build artifacts should start' do - # this will be accelerated by Workhorse - response_json = JSON.parse(page.body, symbolize_names: true) - expect(response_json[:archive]).to end_with('build_artifacts.zip') - expect(response_json[:entry]).to eq Base64.encode64('ci_artifacts.txt') + send_data = response_headers[Gitlab::Workhorse::SEND_DATA_HEADER] + + expect(send_data).to start_with('artifacts-entry:') + + base64_params = send_data.sub(/\Aartifacts\-entry:/, '') + params = JSON.parse(Base64.urlsafe_decode64(base64_params)) + + expect(params.keys).to eq(['Archive', 'Entry']) + expect(params['Archive']).to end_with('build_artifacts.zip') + expect(params['Entry']).to eq(Base64.encode64('ci_artifacts.txt')) end step 'I click a first row within build artifacts table' do diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index 239036e431d..bea9f9d198b 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -125,25 +125,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(page).to have_content 'Authors' end - step 'I visit big commit page' do - # Create a temporary scope to ensure that the stub_const is removed after user - RSpec::Mocks.with_temporary_scope do - stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_lines: 1, max_files: 1 }) - visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id) - end - end - - step 'I see big commit warning' do - expect(page).to have_content sample_big_commit.message - expect(page).to have_content "Too many changes" - end - - step 'I see "Reload with full diff" link' do - link = find_link('Reload with full diff') - expect(link[:href]).to end_with('?force_show_diff=true') - expect(link[:href]).not_to include('.html') - end - step 'I visit a commit with an image that changed' do visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id) end diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 56ef44ec969..4df4e89f5b9 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -25,17 +25,16 @@ module SharedDiffNote page.within("form[data-line-code='#{sample_commit.line_code}']") do fill_in "note[note]", with: "Typo, please fix" - find(".js-comment-button").trigger("click") - sleep 0.05 + find(".js-comment-button").click end end end step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do - click_parallel_diff_line(sample_commit.line_code, 'old') - page.within("#{diff_file_selector} form[data-line-code='#{sample_commit.line_code}']") do + click_parallel_diff_line(sample_commit.del_line_code, 'old') + page.within("#{diff_file_selector} form[data-line-code='#{sample_commit.del_line_code}']") do fill_in "note[note]", with: "Old comment" - find(".js-comment-button").trigger("click") + find(".js-comment-button").click end end @@ -43,7 +42,7 @@ module SharedDiffNote click_parallel_diff_line(sample_commit.line_code, 'new') page.within("#{diff_file_selector} form[data-line-code='#{sample_commit.line_code}']") do fill_in "note[note]", with: "New comment" - find(".js-comment-button").trigger("click") + find(".js-comment-button").click end end @@ -165,10 +164,6 @@ module SharedDiffNote end end - step 'I should see add a diff comment button' do - expect(page).to have_css('.js-add-diff-note-button') - end - step 'I should see an empty diff comment form' do page.within(diff_file_selector) do expect(page).to have_field("note[note]", with: "") @@ -215,7 +210,7 @@ module SharedDiffNote end step 'I click side-by-side diff button' do - find('#parallel-diff-btn').trigger('click') + find('#parallel-diff-btn').click end step 'I see side-by-side diff button' do @@ -227,10 +222,12 @@ module SharedDiffNote end def click_diff_line(code) - find("button[data-line-code='#{code}']").trigger('click') + find(".line_holder[id='#{code}'] td:nth-of-type(1)").trigger 'mouseover' + find(".line_holder[id='#{code}'] button").trigger 'click' end def click_parallel_diff_line(code, line_type) - find("button[data-line-code='#{code}'][data-line-type='#{line_type}']").trigger('click') + find(".line_content.parallel.#{line_type}[data-line-code='#{code}']").trigger 'mouseover' + find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click' end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index db877d2eeb0..301dbb688a7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -58,6 +58,14 @@ module API expose :path, :path_with_namespace end + class SharedGroup < Grape::Entity + expose :group_id + expose :group_name do |group_link, options| + group_link.group.name + end + expose :group_access, as: :group_access_level + end + class Project < Grape::Entity expose :id, :description, :default_branch, :tag_list expose :public?, as: :public @@ -77,6 +85,9 @@ module API expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :public_builds + expose :shared_with_groups do |project, options| + SharedGroup.represent(project.project_group_links.all, options) + end end class ProjectMember < UserBasic @@ -93,6 +104,7 @@ module API class GroupDetail < Group expose :projects, using: Entities::Project + expose :shared_projects, using: Entities::Project end class GroupMember < UserBasic @@ -187,7 +199,6 @@ module API expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id expose :label_names, as: :labels - expose :description expose :work_in_progress?, as: :work_in_progress expose :milestone, using: Entities::Milestone expose :merge_when_build_succeeds @@ -196,6 +207,8 @@ module API merge_request.subscribed?(options[:current_user]) end expose :user_notes_count + expose :should_remove_source_branch?, as: :should_remove_source_branch + expose :force_remove_source_branch?, as: :force_remove_source_branch end class MergeRequestChanges < MergeRequest diff --git a/lib/banzai/filter/blockquote_fence_filter.rb b/lib/banzai/filter/blockquote_fence_filter.rb new file mode 100644 index 00000000000..d2c4b1e4d76 --- /dev/null +++ b/lib/banzai/filter/blockquote_fence_filter.rb @@ -0,0 +1,71 @@ +module Banzai + module Filter + class BlockquoteFenceFilter < HTML::Pipeline::TextFilter + REGEX = %r{ + (?<code> + # Code blocks: + # ``` + # Anything, including `>>>` blocks which are ignored by this filter + # ``` + + ^``` + .+? + \n```$ + ) + | + (?<html> + # HTML block: + # <tag> + # Anything, including `>>>` blocks which are ignored by this filter + # </tag> + + ^<[^>]+?>\n + .+? + \n<\/[^>]+?>$ + ) + | + (?: + # Blockquote: + # >>> + # Anything, including code and HTML blocks + # >>> + + ^>>>\n + (?<quote> + (?: + # Any character that doesn't introduce a code or HTML block + (?! + ^``` + | + ^<[^>]+?>\n + ) + . + | + # A code block + \g<code> + | + # An HTML block + \g<html> + )+? + ) + \n>>>$ + ) + }mx.freeze + + def initialize(text, context = nil, result = nil) + super text, context, result + @text = @text.delete("\r") + end + + def call + @text.gsub(REGEX) do + if $~[:quote] + $~[:quote].gsub(/^/, "> ").gsub(/^> $/, ">") + else + $~[0] + end + end + end + end + end +end diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb index f0e4f28bf12..dc83b87a6c1 100644 --- a/lib/banzai/object_renderer.rb +++ b/lib/banzai/object_renderer.rb @@ -31,10 +31,10 @@ module Banzai redacted = redact_documents(documents) objects.each_with_index do |object, index| - object.__send__("#{attribute}_html=", redacted.fetch(index)) + redacted_data = redacted[index] + object.__send__("#{attribute}_html=", redacted_data[:document].to_html.html_safe) + object.user_visible_reference_count = redacted_data[:visible_reference_count] end - - objects end # Renders the attribute of every given object. @@ -50,9 +50,7 @@ module Banzai def redact_documents(documents) redactor = Redactor.new(project, user) - redactor.redact(documents).map do |document| - document.to_html.html_safe - end + redactor.redact(documents) end # Returns a Banzai context for the given object and attribute. diff --git a/lib/banzai/pipeline/pre_process_pipeline.rb b/lib/banzai/pipeline/pre_process_pipeline.rb index 50dc978b452..6cf219661d3 100644 --- a/lib/banzai/pipeline/pre_process_pipeline.rb +++ b/lib/banzai/pipeline/pre_process_pipeline.rb @@ -3,7 +3,8 @@ module Banzai class PreProcessPipeline < BasePipeline def self.filters FilterArray[ - Filter::YamlFrontMatterFilter + Filter::YamlFrontMatterFilter, + Filter::BlockquoteFenceFilter, ] end diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb index ffd267d5e9a..0df3a72d1c4 100644 --- a/lib/banzai/redactor.rb +++ b/lib/banzai/redactor.rb @@ -19,29 +19,36 @@ module Banzai # # Returns the documents passed as the first argument. def redact(documents) - nodes = documents.flat_map do |document| - Querying.css(document, 'a.gfm[data-reference-type]') - end - - redact_nodes(nodes) + all_document_nodes = document_nodes(documents) - documents + redact_document_nodes(all_document_nodes) end - # Redacts the given nodes + # Redacts the given node documents # - # nodes - An Array of HTML nodes to redact. - def redact_nodes(nodes) - visible = nodes_visible_to_user(nodes) + # data - An Array of a Hashes mapping an HTML document to nodes to redact. + def redact_document_nodes(all_document_nodes) + all_nodes = all_document_nodes.map { |x| x[:nodes] }.flatten + visible = nodes_visible_to_user(all_nodes) + metadata = [] - nodes.each do |node| - unless visible.include?(node) + all_document_nodes.each do |entry| + nodes_for_document = entry[:nodes] + doc_data = { document: entry[:document], visible_reference_count: nodes_for_document.count } + metadata << doc_data + + nodes_for_document.each do |node| + next if visible.include?(node) + + doc_data[:visible_reference_count] -= 1 # The reference should be replaced by the original text, # which is not always the same as the rendered text. text = node.attr('data-original') || node.text node.replace(text) end end + + metadata end # Returns the nodes visible to the current user. @@ -65,5 +72,11 @@ module Banzai visible end + + def document_nodes(documents) + documents.map do |document| + { document: document, nodes: Querying.css(document, 'a.gfm[data-reference-type]') } + end + end end end diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index 3d7b9c4a024..6cf218aaa0d 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -133,8 +133,9 @@ module Banzai return {} if nodes.empty? ids = unique_attribute_values(nodes, attribute) + rows = collection_objects_for_ids(collection, ids) - collection.where(id: ids).each_with_object({}) do |row, hash| + rows.each_with_object({}) do |row, hash| hash[row.id] = row end end @@ -153,6 +154,31 @@ module Banzai values.to_a end + # Queries the collection for the objects with the given IDs. + # + # If the RequestStore module is enabled this method will only query any + # objects that have not yet been queried. For objects that have already + # been queried the object is returned from the cache. + def collection_objects_for_ids(collection, ids) + if RequestStore.active? + cache = collection_cache[collection_cache_key(collection)] + to_query = ids.map(&:to_i) - cache.keys + + unless to_query.empty? + collection.where(id: to_query).each { |row| cache[row.id] = row } + end + + cache.values + else + collection.where(id: ids) + end + end + + # Returns the cache key to use for a collection. + def collection_cache_key(collection) + collection.respond_to?(:model) ? collection.model : collection + end + # Processes the list of HTML documents and returns an Array containing all # the references. def process(documents) @@ -189,7 +215,7 @@ module Banzai end def find_projects_for_hash_keys(hash) - Project.where(id: hash.keys) + collection_objects_for_ids(Project, hash.keys) end private @@ -199,6 +225,12 @@ module Banzai def lazy(&block) Gitlab::Lazy.new(&block) end + + def collection_cache + RequestStore[:banzai_collection_cache] ||= Hash.new do |hash, key| + hash[key] = {} + end + end end end end diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb index a12b0d19560..863f5725d3b 100644 --- a/lib/banzai/reference_parser/user_parser.rb +++ b/lib/banzai/reference_parser/user_parser.rb @@ -73,7 +73,7 @@ module Banzai def find_users(ids) return [] if ids.empty? - User.where(id: ids).to_a + collection_objects_for_ids(User, ids) end def find_users_for_groups(ids) @@ -85,7 +85,8 @@ module Banzai def find_users_for_projects(ids) return [] if ids.empty? - Project.where(id: ids).flat_map { |p| p.team.members.to_a } + collection_objects_for_ids(Project, ids). + flat_map { |p| p.team.members.to_a } end end end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index b0c50edba59..7e01f7b61fb 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -68,6 +68,10 @@ module Gitlab @lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a end + def collapsed_by_default? + diff.diff.bytesize > 10240 # 10 KB + end + def highlighted_diff_lines @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight end diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb index 2122339d2db..3caf9036459 100644 --- a/lib/gitlab/graphs/commits.rb +++ b/lib/gitlab/graphs/commits.rb @@ -18,7 +18,7 @@ module Gitlab end def commit_per_day - @commit_per_day ||= (@commits.size.to_f / @duration).round(1) + @commit_per_day ||= @commits.size / (@duration + 1) end def collect_data diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index 0ac6ff01e3b..025ecc12f9f 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -69,10 +69,19 @@ module Gitlab # Example: # +relation_key+ issues, loops through the list of *issues* and for each individual # issue, finds any subrelations such as notes, creates them and assign them back to the hash + # + # Recursively calls this method if the sub-relation is a hash containing more sub-relations def create_sub_relations(relation, tree_hash) relation_key = relation.keys.first.to_s + return if tree_hash[relation_key].blank? + tree_hash[relation_key].each do |relation_item| relation.values.flatten.each do |sub_relation| + # We just use author to get the user ID, do not attempt to create an instance. + next if sub_relation == :author + + create_sub_relations(sub_relation, relation_item) if sub_relation.is_a?(Hash) + relation_hash, sub_relation = assign_relation_hash(relation_item, sub_relation) relation_item[sub_relation.to_s] = create_relation(sub_relation, relation_hash) unless relation_hash.blank? end diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 86ed18fb50d..19dad699edf 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -4,6 +4,8 @@ module Gitlab regexp = URI::Parser.new.make_regexp(['http', 'https', 'ssh', 'git']) content.gsub(regexp) { |url| new(url).masked_url } + rescue Addressable::URI::InvalidURIError + content.gsub(regexp, '') end def self.valid?(url) diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index bc0193a6c32..6aeb49c0219 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -63,6 +63,18 @@ module Gitlab ] end + def send_artifacts_entry(build, entry) + params = { + 'Archive' => build.artifacts_file.path, + 'Entry' => Base64.encode64(entry.path) + } + + [ + SEND_DATA_HEADER, + "artifacts-entry:#{encode(params)}" + ] + end + protected def encode(hash) diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index 8c309efc7b8..3358ed6773e 100644 --- a/lib/rouge/formatters/html_gitlab.rb +++ b/lib/rouge/formatters/html_gitlab.rb @@ -143,18 +143,14 @@ module Rouge '</span>' end end - lines.join("\n") - else - if @linenos == 'inline' - lines = lines.each_with_index.map do |line, index| - number = index + @linenostart - "<span class=\"linenos\">#{number}</span>#{line}" - end - lines.join("\n") - else - lines.join("\n") + elsif @linenos == 'inline' + lines = lines.each_with_index.map do |line, index| + number = index + @linenostart + "<span class=\"linenos\">#{number}</span>#{line}" end end + + lines.join("\n") end def span(tok, val) diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb deleted file mode 100644 index a3a3309e15e..00000000000 --- a/spec/controllers/commit_controller_spec.rb +++ /dev/null @@ -1,246 +0,0 @@ -require 'spec_helper' - -describe Projects::CommitController do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:commit) { project.commit("master") } - let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' } - let(:master_pickable_commit) { project.commit(master_pickable_sha) } - - before do - sign_in(user) - project.team << [user, :master] - end - - describe "#show" do - shared_examples "export as" do |format| - it "should generally work" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: commit.id, - format: format) - - expect(response).to be_success - end - - it "should generate it" do - expect_any_instance_of(Commit).to receive(:"to_#{format}") - - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: commit.id, format: format) - end - - it "should render it" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: commit.id, format: format) - - expect(response.body).to eq(commit.send(:"to_#{format}")) - end - - it "should not escape Html" do - allow_any_instance_of(Commit).to receive(:"to_#{format}"). - and_return('HTML entities &<>" ') - - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: commit.id, format: format) - - expect(response.body).not_to include('&') - expect(response.body).not_to include('>') - expect(response.body).not_to include('<') - expect(response.body).not_to include('"') - end - end - - describe "as diff" do - include_examples "export as", :diff - let(:format) { :diff } - - it "should really only be a git diff" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: commit.id, - format: format) - - expect(response.body).to start_with("diff --git") - end - - it "should really only be a git diff without whitespace changes" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: '66eceea0db202bb39c4e445e8ca28689645366c5', - # id: commit.id, - format: format, - w: 1) - - expect(response.body).to start_with("diff --git") - # without whitespace option, there are more than 2 diff_splits - diff_splits = assigns(:diffs).first.diff.split("\n") - expect(diff_splits.length).to be <= 2 - end - end - - describe "as patch" do - include_examples "export as", :patch - let(:format) { :patch } - - it "should really be a git email patch" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: commit.id, - format: format) - - expect(response.body).to start_with("From #{commit.id}") - end - - it "should contain a git diff" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: commit.id, - format: format) - - expect(response.body).to match(/^diff --git/) - end - end - - context 'commit that removes a submodule' do - render_views - - let(:fork_project) { create(:forked_project_with_submodules) } - let(:commit) { fork_project.commit('remove-submodule') } - - before do - fork_project.team << [user, :master] - end - - it 'renders it' do - get(:show, - namespace_id: fork_project.namespace.to_param, - project_id: fork_project.to_param, - id: commit.id) - - expect(response).to be_success - end - end - end - - describe "#branches" do - it "contains branch and tags information" do - get(:branches, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: commit.id) - - expect(assigns(:branches)).to include("master", "feature_conflict") - expect(assigns(:tags)).to include("v1.1.0") - end - end - - describe '#revert' do - context 'when target branch is not provided' do - it 'should render the 404 page' do - post(:revert, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: commit.id) - - expect(response).not_to be_success - expect(response).to have_http_status(404) - end - end - - context 'when the revert was successful' do - it 'should redirect to the commits page' do - post(:revert, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - target_branch: 'master', - id: commit.id) - - expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master') - expect(flash[:notice]).to eq('The commit has been successfully reverted.') - end - end - - context 'when the revert failed' do - before do - post(:revert, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - target_branch: 'master', - id: commit.id) - end - - it 'should redirect to the commit page' do - # Reverting a commit that has been already reverted. - post(:revert, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - target_branch: 'master', - id: commit.id) - - expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id) - expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.') - end - end - end - - describe '#cherry_pick' do - context 'when target branch is not provided' do - it 'should render the 404 page' do - post(:cherry_pick, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: master_pickable_commit.id) - - expect(response).not_to be_success - expect(response).to have_http_status(404) - end - end - - context 'when the cherry-pick was successful' do - it 'should redirect to the commits page' do - post(:cherry_pick, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - target_branch: 'master', - id: master_pickable_commit.id) - - expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master') - expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.') - end - end - - context 'when the cherry_pick failed' do - before do - post(:cherry_pick, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - target_branch: 'master', - id: master_pickable_commit.id) - end - - it 'should redirect to the commit page' do - # Cherry-picking a commit that has been already cherry-picked. - post(:cherry_pick, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - target_branch: 'master', - id: master_pickable_commit.id) - - expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) - expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.') - end - end - end -end diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index 1f7fd517342..267d511c2db 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -11,7 +11,7 @@ describe HelpController do context 'for Markdown formats' do context 'when requested file exists' do before do - get :show, category: 'ssh', file: 'README', format: :md + get :show, path: 'ssh/README', format: :md end it 'assigns to @markdown' do @@ -26,7 +26,7 @@ describe HelpController do context 'when requested file is missing' do it 'renders not found' do - get :show, category: 'foo', file: 'bar', format: :md + get :show, path: 'foo/bar', format: :md expect(response).to be_not_found end end @@ -36,8 +36,7 @@ describe HelpController do context 'when requested file exists' do it 'renders the raw file' do get :show, - category: 'workflow/protected_branches', - file: 'protected_branches1', + path: 'workflow/protected_branches/protected_branches1', format: :png expect(response).to be_success expect(response.content_type).to eq 'image/png' @@ -48,8 +47,7 @@ describe HelpController do context 'when requested file is missing' do it 'renders not found' do get :show, - category: 'foo', - file: 'bar', + path: 'foo/bar', format: :png expect(response).to be_not_found end @@ -59,8 +57,7 @@ describe HelpController do context 'for other formats' do it 'always renders not found' do get :show, - category: 'ssh', - file: 'README', + path: 'ssh/README', format: :foo expect(response).to be_not_found end diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 6e3db10e451..3001d32e719 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -1,9 +1,29 @@ -require 'rails_helper' +require 'spec_helper' describe Projects::CommitController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:commit) { project.commit("master") } + let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' } + let(:master_pickable_commit) { project.commit(master_pickable_sha) } + + before do + sign_in(user) + project.team << [user, :master] + end + describe 'GET show' do render_views + def go(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project.to_param + } + + get :show, params.merge(extra_params) + end + let(:project) { create(:project) } before do @@ -15,7 +35,7 @@ describe Projects::CommitController do context 'with valid id' do it 'responds with 200' do - go id: project.commit.id + go(id: commit.id) expect(response).to be_ok end @@ -23,27 +43,274 @@ describe Projects::CommitController do context 'with invalid id' do it 'responds with 404' do - go id: project.commit.id.reverse + go(id: commit.id.reverse) expect(response).to be_not_found end end it 'handles binary files' do - get(:show, + go(id: TestEnv::BRANCH_SHA['binary-encoding'], format: 'html') + + expect(response).to be_success + end + + shared_examples "export as" do |format| + it "should generally work" do + go(id: commit.id, format: format) + + expect(response).to be_success + end + + it "should generate it" do + expect_any_instance_of(Commit).to receive(:"to_#{format}") + + go(id: commit.id, format: format) + end + + it "should render it" do + go(id: commit.id, format: format) + + expect(response.body).to eq(commit.send(:"to_#{format}")) + end + + it "should not escape Html" do + allow_any_instance_of(Commit).to receive(:"to_#{format}"). + and_return('HTML entities &<>" ') + + go(id: commit.id, format: format) + + expect(response.body).not_to include('&') + expect(response.body).not_to include('>') + expect(response.body).not_to include('<') + expect(response.body).not_to include('"') + end + end + + describe "as diff" do + include_examples "export as", :diff + let(:format) { :diff } + + it "should really only be a git diff" do + go(id: commit.id, format: format) + + expect(response.body).to start_with("diff --git") + end + + it "should really only be a git diff without whitespace changes" do + go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format, w: 1) + + expect(response.body).to start_with("diff --git") + # without whitespace option, there are more than 2 diff_splits + diff_splits = assigns(:diffs).first.diff.split("\n") + expect(diff_splits.length).to be <= 2 + end + end + + describe "as patch" do + include_examples "export as", :patch + let(:format) { :patch } + + it "should really be a git email patch" do + go(id: commit.id, format: format) + + expect(response.body).to start_with("From #{commit.id}") + end + + it "should contain a git diff" do + go(id: commit.id, format: format) + + expect(response.body).to match(/^diff --git/) + end + end + + context 'commit that removes a submodule' do + render_views + + let(:fork_project) { create(:forked_project_with_submodules, visibility_level: 20) } + let(:commit) { fork_project.commit('remove-submodule') } + + it 'renders it' do + get(:show, + namespace_id: fork_project.namespace.to_param, + project_id: fork_project.to_param, + id: commit.id) + + expect(response).to be_success + end + end + end + + describe "GET branches" do + it "contains branch and tags information" do + get(:branches, namespace_id: project.namespace.to_param, project_id: project.to_param, - id: TestEnv::BRANCH_SHA['binary-encoding'], - format: "html") + id: commit.id) - expect(response).to be_success + expect(assigns(:branches)).to include("master", "feature_conflict") + expect(assigns(:tags)).to include("v1.1.0") + end + end + + describe 'POST revert' do + context 'when target branch is not provided' do + it 'should render the 404 page' do + post(:revert, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: commit.id) + + expect(response).not_to be_success + expect(response).to have_http_status(404) + end + end + + context 'when the revert was successful' do + it 'should redirect to the commits page' do + post(:revert, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + target_branch: 'master', + id: commit.id) + + expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master') + expect(flash[:notice]).to eq('The commit has been successfully reverted.') + end + end + + context 'when the revert failed' do + before do + post(:revert, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + target_branch: 'master', + id: commit.id) + end + + it 'should redirect to the commit page' do + # Reverting a commit that has been already reverted. + post(:revert, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + target_branch: 'master', + id: commit.id) + + expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id) + expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.') + end + end + end + + describe 'POST cherry_pick' do + context 'when target branch is not provided' do + it 'should render the 404 page' do + post(:cherry_pick, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: master_pickable_commit.id) + + expect(response).not_to be_success + expect(response).to have_http_status(404) + end + end + + context 'when the cherry-pick was successful' do + it 'should redirect to the commits page' do + post(:cherry_pick, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + target_branch: 'master', + id: master_pickable_commit.id) + + expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master') + expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.') + end end - def go(id:) - get :show, + context 'when the cherry_pick failed' do + before do + post(:cherry_pick, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + target_branch: 'master', + id: master_pickable_commit.id) + end + + it 'should redirect to the commit page' do + # Cherry-picking a commit that has been already cherry-picked. + post(:cherry_pick, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + target_branch: 'master', + id: master_pickable_commit.id) + + expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) + expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.') + end + end + end + + describe 'GET diff_for_path' do + def diff_for_path(extra_params = {}) + params = { namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: id + project_id: project.to_param + } + + get :diff_for_path, params.merge(extra_params) + end + + let(:existing_path) { '.gitmodules' } + + context 'when the commit exists' do + context 'when the user has access to the project' do + context 'when the path exists in the diff' do + it 'enables diff notes' do + diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) + + expect(assigns(:diff_notes_disabled)).to be_falsey + expect(assigns(:comments_target)).to eq(noteable_type: 'Commit', + commit_id: commit.id) + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| + expect(diffs.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs, diff_refs, project) + end + + diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) + end + end + + context 'when the path does not exist in the diff' do + before { diff_for_path(id: commit.id, old_path: existing_path.succ, new_path: existing_path.succ) } + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the user does not have access to the project' do + before do + project.team.truncate + diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the commit does not exist' do + before { diff_for_path(id: commit.id.succ, old_path: existing_path, new_path: existing_path) } + + it 'returns a 404' do + expect(response).to have_http_status(404) + end end end end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 4018dac95a2..4058d5e2453 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -64,4 +64,73 @@ describe Projects::CompareController do expect(assigns(:commits)).to eq(nil) end end + + describe 'GET diff_for_path' do + def diff_for_path(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project.to_param + } + + get :diff_for_path, params.merge(extra_params) + end + + let(:existing_path) { 'files/ruby/feature.rb' } + + context 'when the from and to refs exist' do + context 'when the user has access to the project' do + context 'when the path exists in the diff' do + it 'disables diff notes' do + diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path) + + expect(assigns(:diff_notes_disabled)).to be_truthy + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| + expect(diffs.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs, diff_refs, project) + end + + diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path) + end + end + + context 'when the path does not exist in the diff' do + before { diff_for_path(from: ref_from, to: ref_to, old_path: existing_path.succ, new_path: existing_path.succ) } + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the user does not have access to the project' do + before do + project.team.truncate + diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the from ref does not exist' do + before { diff_for_path(from: ref_from.succ, to: ref_to, old_path: existing_path, new_path: existing_path) } + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + + context 'when the to ref does not exist' do + before { diff_for_path(from: ref_from, to: ref_to.succ, old_path: existing_path, new_path: existing_path) } + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c4b57e77804..210085e3b1a 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -10,7 +10,7 @@ describe Projects::MergeRequestsController do project.team << [user, :master] end - describe '#new' do + describe 'GET new' do context 'merge request that removes a submodule' do render_views @@ -34,7 +34,7 @@ describe Projects::MergeRequestsController do end end - describe "#show" do + describe "GET show" do shared_examples "export merge as" do |format| it "should generally work" do get(:show, @@ -108,7 +108,7 @@ describe Projects::MergeRequestsController do end end - describe 'GET #index' do + describe 'GET index' do def get_merge_requests get :index, namespace_id: project.namespace.to_param, @@ -140,7 +140,7 @@ describe Projects::MergeRequestsController do end end - describe 'PUT #update' do + describe 'PUT update' do context 'there is no source project' do let(:project) { create(:project) } let(:fork_project) { create(:forked_project_with_submodules) } @@ -168,7 +168,7 @@ describe Projects::MergeRequestsController do end end - describe 'POST #merge' do + describe 'POST merge' do let(:base_params) do { namespace_id: project.namespace.path, @@ -266,7 +266,7 @@ describe Projects::MergeRequestsController do end end - describe "DELETE #destroy" do + describe "DELETE destroy" do it "denies access to users unless they're admin or project owner" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid @@ -290,96 +290,210 @@ describe Projects::MergeRequestsController do end describe 'GET diffs' do - def go(format: 'html') - get :diffs, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: merge_request.iid, - format: format + def go(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid + } + + get :diffs, params.merge(extra_params) end - context 'as html' do - it 'renders the diff template' do - go + context 'with default params' do + context 'as html' do + before { go(format: 'html') } - expect(response).to render_template('diffs') + it 'renders the diff template' do + expect(response).to render_template('diffs') + end end - end - context 'as json' do - it 'renders the diffs template to a string' do - go format: 'json' + context 'as json' do + before { go(format: 'json') } - expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(JSON.parse(response.body)).to have_key('html') + it 'renders the diffs template to a string' do + expect(response).to render_template('projects/merge_requests/show/_diffs') + expect(JSON.parse(response.body)).to have_key('html') + end end - end - - context 'with forked projects with submodules' do - render_views - let(:project) { create(:project) } - let(:fork_project) { create(:forked_project_with_submodules) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } + context 'with forked projects with submodules' do + render_views - before do - fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) - fork_project.save - merge_request.reload - end + let(:project) { create(:project) } + let(:fork_project) { create(:forked_project_with_submodules) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } - it 'renders' do - go format: 'json' + before do + fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) + fork_project.save + merge_request.reload + go(format: 'json') + end - expect(response).to be_success - expect(response.body).to have_content('Subproject commit') + it 'renders' do + expect(response).to be_success + expect(response.body).to have_content('Subproject commit') + end end end - end - describe 'GET diffs with ignore_whitespace_change' do - def go(format: 'html') - get :diffs, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: merge_request.iid, - format: format, - w: 1 - end + context 'with ignore_whitespace_change' do + context 'as html' do + before { go(format: 'html', w: 1) } - context 'as html' do - it 'renders the diff template' do - go + it 'renders the diff template' do + expect(response).to render_template('diffs') + end + end + + context 'as json' do + before { go(format: 'json', w: 1) } - expect(response).to render_template('diffs') + it 'renders the diffs template to a string' do + expect(response).to render_template('projects/merge_requests/show/_diffs') + expect(JSON.parse(response.body)).to have_key('html') + end end end - context 'as json' do - it 'renders the diffs template to a string' do - go format: 'json' + context 'with view' do + before { go(view: 'parallel') } - expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(JSON.parse(response.body)).to have_key('html') + it 'saves the preferred diff view in a cookie' do + expect(response.cookies['diff_view']).to eq('parallel') end end end - describe 'GET diffs with view' do - def go(extra_params = {}) + describe 'GET diff_for_path' do + def diff_for_path(extra_params = {}) params = { namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: merge_request.iid + project_id: project.to_param } - get :diffs, params.merge(extra_params) + get :diff_for_path, params.merge(extra_params) end - it 'saves the preferred diff view in a cookie' do - go view: 'parallel' + context 'when an ID param is passed' do + let(:existing_path) { 'files/ruby/popen.rb' } - expect(response.cookies['diff_view']).to eq('parallel') + context 'when the merge request exists' do + context 'when the user can view the merge request' do + context 'when the path exists in the diff' do + it 'enables diff notes' do + diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) + + expect(assigns(:diff_notes_disabled)).to be_falsey + expect(assigns(:comments_target)).to eq(noteable_type: 'MergeRequest', + noteable_id: merge_request.id) + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| + expect(diffs.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs, diff_refs, project) + end + + diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) + end + end + + context 'when the path does not exist in the diff' do + before { diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') } + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the user cannot view the merge request' do + before do + project.team.truncate + diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the merge request does not exist' do + before { diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) } + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + + context 'when the merge request belongs to a different project' do + let(:other_project) { create(:empty_project) } + + before do + other_project.team << [user, :master] + diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project.to_param) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when source and target params are passed' do + let(:existing_path) { 'files/ruby/feature.rb' } + + context 'when both branches are in the same project' do + it 'disables diff notes' do + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) + + expect(assigns(:diff_notes_disabled)).to be_truthy + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| + expect(diffs.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs, diff_refs, project) + end + + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) + end + end + + context 'when the source branch is in a different project to the target' do + let(:other_project) { create(:project) } + + before { other_project.team << [user, :master] } + + context 'when the path exists in the diff' do + it 'disables diff notes' do + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) + + expect(assigns(:diff_notes_disabled)).to be_truthy + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| + expect(diffs.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs, diff_refs, project) + end + + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) + end + end + + context 'when the path does not exist in the diff' do + before { diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) } + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 5c8ddbebf0d..b682ced75ac 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -2,7 +2,7 @@ FactoryGirl.define do # Project without repository # # Project does not have bare repository. - # Use this factory if you dont need repository in tests + # Use this factory if you don't need repository in tests factory :empty_project, class: 'Project' do sequence(:name) { |n| "project#{n}" } path { name.downcase.gsub(/\s/, '_') } diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb new file mode 100644 index 00000000000..78bc888f2a6 --- /dev/null +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -0,0 +1,207 @@ +require 'spec_helper' + +feature 'Expand and collapse diffs', js: true, feature: true do + include WaitForAjax + + before do + login_as :admin + project = create(:project) + branch = 'expand-collapse-diffs' + + # Ensure that undiffable.md is in .gitattributes + project.repository.copy_gitattributes(branch) + visit namespace_project_commit_path(project.namespace, project, project.commit(branch)) + execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });') + end + + def file_container(filename) + find("[data-blob-diff-path*='#{filename}']") + end + + # Use define_method instead of let (which is memoized) so that this just works across a + # reload. + # + files = [ + 'small_diff.md', 'large_diff.md', 'large_diff_renamed.md', 'undiffable.md', + 'too_large.md', 'too_large_image.jpg' + ] + + files.each do |file| + define_method(file.split('.').first) { file_container(file) } + end + + context 'visiting a commit with collapsed diffs' do + it 'shows small diffs immediately' do + expect(small_diff).to have_selector('.code') + expect(small_diff).not_to have_selector('.nothing-here-block') + end + + it 'collapses large diffs by default' do + expect(large_diff).not_to have_selector('.code') + expect(large_diff).to have_selector('.nothing-here-block') + end + + it 'collapses large diffs for renamed files by default' do + expect(large_diff_renamed).not_to have_selector('.code') + expect(large_diff_renamed).to have_selector('.nothing-here-block') + expect(large_diff_renamed).to have_selector('.file-title .deletion') + expect(large_diff_renamed).to have_selector('.file-title .addition') + end + + it 'shows non-renderable diffs as such immediately, regardless of their size' do + expect(undiffable).not_to have_selector('.code') + expect(undiffable).to have_selector('.nothing-here-block') + expect(undiffable).to have_content('gitattributes') + end + + it 'does not allow diffs that are larger than the maximum size to be expanded' do + expect(too_large).not_to have_selector('.code') + expect(too_large).to have_selector('.nothing-here-block') + expect(too_large).to have_content('too large') + end + + it 'shows image diffs immediately, regardless of their size' do + expect(too_large_image).not_to have_selector('.nothing-here-block') + expect(too_large_image).to have_selector('.image') + end + + context 'expanding a diff for a renamed file' do + before do + large_diff_renamed.find('.nothing-here-block').click + wait_for_ajax + end + + it 'shows the old content' do + old_line = large_diff_renamed.find('.line_content.old') + + expect(old_line).to have_content('two copies') + end + + it 'shows the new content' do + new_line = large_diff_renamed.find('.line_content.new', match: :prefer_exact) + + expect(new_line).to have_content('three copies') + end + end + + context 'expanding a large diff' do + before do + click_link('large_diff.md') + wait_for_ajax + end + + it 'makes a request to get the content' do + ajax_uris = evaluate_script('ajaxUris') + + expect(ajax_uris).not_to be_empty + expect(ajax_uris.first).to include('large_diff.md') + end + + it 'shows the diff content' do + expect(large_diff).to have_selector('.code') + expect(large_diff).not_to have_selector('.nothing-here-block') + end + + context 'adding a comment to the expanded diff' do + let(:comment_text) { 'A comment' } + + before do + large_diff.find('.diff-line-num', match: :prefer_exact).hover + large_diff.find('.add-diff-note').click + large_diff.find('.note-textarea').send_keys comment_text + large_diff.find_button('Comment').click + wait_for_ajax + end + + it 'adds the comment' do + expect(large_diff.find('.notes')).to have_content comment_text + end + + context 'reloading the page' do + before { refresh } + + it 'collapses the large diff by default' do + expect(large_diff).not_to have_selector('.code') + expect(large_diff).to have_selector('.nothing-here-block') + end + + context 'expanding the diff' do + before do + click_link('large_diff.md') + wait_for_ajax + end + + it 'shows the diff content' do + expect(large_diff).to have_selector('.code') + expect(large_diff).not_to have_selector('.nothing-here-block') + end + + it 'shows the diff comment' do + expect(large_diff.find('.notes')).to have_content comment_text + end + end + end + end + end + + context 'collapsing an expanded diff' do + before { click_link('small_diff.md') } + + it 'hides the diff content' do + expect(small_diff).not_to have_selector('.code') + expect(small_diff).to have_selector('.nothing-here-block') + end + + context 're-expanding the same diff' do + before { click_link('small_diff.md') } + + it 'shows the diff content' do + expect(small_diff).to have_selector('.code') + expect(small_diff).not_to have_selector('.nothing-here-block') + end + + it 'does not make a new HTTP request' do + expect(evaluate_script('ajaxUris')).not_to include(a_string_matching('small_diff.md')) + end + end + end + end + + context 'expanding all diffs' do + before do + click_link('Expand all') + wait_for_ajax + execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });') + end + + it 'reloads the page with all diffs expanded' do + expect(small_diff).to have_selector('.code') + expect(small_diff).not_to have_selector('.nothing-here-block') + + expect(large_diff).to have_selector('.code') + expect(large_diff).not_to have_selector('.nothing-here-block') + end + + context 'collapsing an expanded diff' do + before { click_link('small_diff.md') } + + it 'hides the diff content' do + expect(small_diff).not_to have_selector('.code') + expect(small_diff).to have_selector('.nothing-here-block') + end + + context 're-expanding the same diff' do + before { click_link('small_diff.md') } + + it 'shows the diff content' do + expect(small_diff).to have_selector('.code') + expect(small_diff).not_to have_selector('.nothing-here-block') + end + + it 'does not make a new HTTP request' do + expect(evaluate_script('ajaxUris')).not_to include(a_string_matching('small_diff.md')) + end + end + end + end +end diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index 8c6b669ce78..1e2306d7f59 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -6,7 +6,7 @@ describe 'Help Pages', feature: true do login_as :user end it 'replace the variable $your_email with the email of the user' do - visit help_page_path('ssh', 'README') + visit help_page_path('ssh/README') expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"") end end diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb new file mode 100644 index 00000000000..c9a0059645d --- /dev/null +++ b/spec/features/merge_requests/diffs_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +feature 'Diffs URL', js: true, feature: true do + before do + login_as :admin + @merge_request = create(:merge_request) + @project = @merge_request.source_project + end + + context 'when visit with */* as accept header' do + before(:each) do + page.driver.add_header('Accept', '*/*') + end + + it 'renders the notes' do + create :note_on_merge_request, project: @project, noteable: @merge_request, note: 'Rebasing with master' + + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + + # Load notes and diff through AJAX + expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master') + expect(page).to have_css('.diffs.tab-pane.active') + end + end +end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 5174168713c..0b38c413f44 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -135,6 +135,28 @@ describe 'Comments', feature: true do end end + describe 'Handles cross-project system notes', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:project2) { create(:project, :private) } + let(:issue) { create(:issue, project: project2) } + let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') } + let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") } + + it 'shows the system note' do + login_as :admin + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + + expect(page).to have_css('.system-note') + end + + it 'hides redacted system note' do + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + + expect(page).not_to have_css('.system-note') + end + end + describe 'On a merge request diff', js: true, feature: true do let(:merge_request) { create(:merge_request) } let(:project) { merge_request.source_project } @@ -231,6 +253,7 @@ describe 'Comments', feature: true do end def click_diff_line(data = line_code) - execute_script("$('button[data-line-code=\"#{data}\"]').click()") + find(".line_holder[id='#{data}'] td.line_content").hover + find(".line_holder[id='#{data}'] button").trigger('click') end end diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 6a39c302f55..98ba93b4036 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -76,7 +76,7 @@ feature 'Prioritize labels', feature: true do expect(page.all('li').last).to have_content('bug') end - visit current_url + refresh wait_for_ajax page.within('.prioritized-labels') do diff --git a/spec/fixtures/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md new file mode 100644 index 00000000000..2652a842c0e --- /dev/null +++ b/spec/fixtures/blockquote_fence_after.md @@ -0,0 +1,115 @@ +Single `>>>` inside code block: + +``` +# Code +>>> +# Code +``` + +Double `>>>` inside code block: + +```txt +# Code +>>> +# Code +>>> +# Code +``` + +Blockquote outside code block: + +> Quote + +Code block inside blockquote: + +> Quote +> +> ``` +> # Code +> ``` +> +> Quote + +Single `>>>` inside code block inside blockquote: + +> Quote +> +> ``` +> # Code +> >>> +> # Code +> ``` +> +> Quote + +Double `>>>` inside code block inside blockquote: + +> Quote +> +> ``` +> # Code +> >>> +> # Code +> >>> +> # Code +> ``` +> +> Quote + +Single `>>>` inside HTML: + +<pre> +# Code +>>> +# Code +</pre> + +Double `>>>` inside HTML: + +<pre> +# Code +>>> +# Code +>>> +# Code +</pre> + +Blockquote outside HTML: + +> Quote + +HTML inside blockquote: + +> Quote +> +> <pre> +> # Code +> </pre> +> +> Quote + +Single `>>>` inside HTML inside blockquote: + +> Quote +> +> <pre> +> # Code +> >>> +> # Code +> </pre> +> +> Quote + +Double `>>>` inside HTML inside blockquote: + +> Quote +> +> <pre> +> # Code +> >>> +> # Code +> >>> +> # Code +> </pre> +> +> Quote diff --git a/spec/fixtures/blockquote_fence_before.md b/spec/fixtures/blockquote_fence_before.md new file mode 100644 index 00000000000..d52eec72896 --- /dev/null +++ b/spec/fixtures/blockquote_fence_before.md @@ -0,0 +1,131 @@ +Single `>>>` inside code block: + +``` +# Code +>>> +# Code +``` + +Double `>>>` inside code block: + +```txt +# Code +>>> +# Code +>>> +# Code +``` + +Blockquote outside code block: + +>>> +Quote +>>> + +Code block inside blockquote: + +>>> +Quote + +``` +# Code +``` + +Quote +>>> + +Single `>>>` inside code block inside blockquote: + +>>> +Quote + +``` +# Code +>>> +# Code +``` + +Quote +>>> + +Double `>>>` inside code block inside blockquote: + +>>> +Quote + +``` +# Code +>>> +# Code +>>> +# Code +``` + +Quote +>>> + +Single `>>>` inside HTML: + +<pre> +# Code +>>> +# Code +</pre> + +Double `>>>` inside HTML: + +<pre> +# Code +>>> +# Code +>>> +# Code +</pre> + +Blockquote outside HTML: + +>>> +Quote +>>> + +HTML inside blockquote: + +>>> +Quote + +<pre> +# Code +</pre> + +Quote +>>> + +Single `>>>` inside HTML inside blockquote: + +>>> +Quote + +<pre> +# Code +>>> +# Code +</pre> + +Quote +>>> + +Double `>>>` inside HTML inside blockquote: + +>>> +Quote + +<pre> +# Code +>>> +# Code +>>> +# Code +</pre> + +Quote +>>> diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index e2db33d8345..4b134a48410 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -31,26 +31,11 @@ describe DiffHelper do end end - describe 'diff_hard_limit_enabled?' do - it 'should return true if param is provided' do - allow(controller).to receive(:params) { { force_show_diff: true } } - expect(diff_hard_limit_enabled?).to be_truthy - end - - it 'should return false if param is not provided' do - expect(diff_hard_limit_enabled?).to be_falsey - end - end - describe 'diff_options' do - it 'should return hard limit for a diff if force diff is true' do + it 'should return hard limit for a diff' do allow(controller).to receive(:params) { { force_show_diff: true } } expect(diff_options).to include(Commit.max_diff_options) end - - it 'should return safe limit for a diff if force diff is false' do - expect(diff_options).not_to include(:max_lines, :max_files) - end end describe 'unfold_bottom_class' do @@ -59,7 +44,7 @@ describe DiffHelper do end it 'should return js class when bottom lines should be unfolded' do - expect(unfold_bottom_class(true)).to eq('js-unfold-bottom') + expect(unfold_bottom_class(true)).to include('js-unfold-bottom') end end diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml index 470cabeafbb..06c2ab1e823 100644 --- a/spec/javascripts/fixtures/issues_show.html.haml +++ b/spec/javascripts/fixtures/issues_show.html.haml @@ -1,7 +1,7 @@ :css .hidden { display: none !important; } -.flash-container +.flash-container.flash-container-page .flash-alert .flash-notice diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb new file mode 100644 index 00000000000..2799249ae3e --- /dev/null +++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +describe Banzai::Filter::BlockquoteFenceFilter, lib: true do + include FilterSpecHelper + + it 'converts blockquote fences to blockquote lines' do + content = File.read(Rails.root.join('spec/fixtures/blockquote_fence_before.md')) + expected = File.read(Rails.root.join('spec/fixtures/blockquote_fence_after.md')) + + output = filter(content) + + expect(output).to eq(expected) + end +end diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 44256b32bdc..d99175967af 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -17,6 +17,7 @@ describe Banzai::ObjectRenderer do and_call_original expect(object).to receive(:note_html=).with('<p>hello</p>') + expect(object).to receive(:user_visible_reference_count=).with(0) renderer.render([object], :note) end @@ -25,6 +26,7 @@ describe Banzai::ObjectRenderer do describe '#render_objects' do it 'renders an Array of objects' do object = double(:object, note: 'hello') + renderer = described_class.new(project, user) expect(renderer).to receive(:render_attribute).with(object, :note). @@ -38,7 +40,7 @@ describe Banzai::ObjectRenderer do end describe '#redact_documents' do - it 'redacts a set of documents and returns them as an Array of Strings' do + it 'redacts a set of documents and returns them as an Array of Hashes' do doc = Nokogiri::HTML.fragment('<p>hello</p>') renderer = described_class.new(project, user) @@ -48,7 +50,9 @@ describe Banzai::ObjectRenderer do redacted = renderer.redact_documents([doc]) - expect(redacted).to eq(['<p>hello</p>']) + expect(redacted.count).to eq(1) + expect(redacted.first[:visible_reference_count]).to eq(0) + expect(redacted.first[:document].to_html).to eq('<p>hello</p>') end end diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb index 488f465bcda..254657a881d 100644 --- a/spec/lib/banzai/redactor_spec.rb +++ b/spec/lib/banzai/redactor_spec.rb @@ -15,11 +15,31 @@ describe Banzai::Redactor do expect(redactor).to receive(:nodes_visible_to_user).and_return([]) - expect(redactor.redact([doc1, doc2])).to eq([doc1, doc2]) + redacted_data = redactor.redact([doc1, doc2]) + expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2]) + expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([0, 0]) expect(doc1.to_html).to eq('foo') expect(doc2.to_html).to eq('bar') end + + it 'does not redact an Array of documents' do + doc1_html = '<a class="gfm" data-reference-type="issue">foo</a>' + doc1 = Nokogiri::HTML.fragment(doc1_html) + + doc2_html = '<a class="gfm" data-reference-type="issue">bar</a>' + doc2 = Nokogiri::HTML.fragment(doc2_html) + + nodes = redactor.document_nodes([doc1, doc2]).map { |x| x[:nodes] } + expect(redactor).to receive(:nodes_visible_to_user).and_return(nodes.flatten) + + redacted_data = redactor.redact([doc1, doc2]) + + expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2]) + expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([1, 1]) + expect(doc1.to_html).to eq(doc1_html) + expect(doc2.to_html).to eq(doc2_html) + end end describe '#redact_nodes' do @@ -31,7 +51,7 @@ describe Banzai::Redactor do with([node]). and_return(Set.new) - redactor.redact_nodes([node]) + redactor.redact_document_nodes([{ document: doc, nodes: [node] }]) expect(doc.to_html).to eq('foo') end diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index 543b4786d84..ac9c66e2663 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -234,4 +234,79 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do to eq([project]) end end + + describe '#collection_objects_for_ids' do + context 'with RequestStore disabled' do + it 'queries the collection directly' do + collection = User.all + + expect(collection).to receive(:where).twice.and_call_original + + 2.times do + expect(subject.collection_objects_for_ids(collection, [user.id])). + to eq([user]) + end + end + end + + context 'with RequestStore enabled' do + before do + cache = Hash.new { |hash, key| hash[key] = {} } + + allow(RequestStore).to receive(:active?).and_return(true) + allow(subject).to receive(:collection_cache).and_return(cache) + end + + it 'queries the collection on the first call' do + expect(subject.collection_objects_for_ids(User, [user.id])). + to eq([user]) + end + + it 'does not query previously queried objects' do + collection = User.all + + expect(collection).to receive(:where).once.and_call_original + + 2.times do + expect(subject.collection_objects_for_ids(collection, [user.id])). + to eq([user]) + end + end + + it 'casts String based IDs to Fixnums before querying objects' do + 2.times do + expect(subject.collection_objects_for_ids(User, [user.id.to_s])). + to eq([user]) + end + end + + it 'queries any additional objects after the first call' do + other_user = create(:user) + + expect(subject.collection_objects_for_ids(User, [user.id])). + to eq([user]) + + expect(subject.collection_objects_for_ids(User, [user.id, other_user.id])). + to eq([user, other_user]) + end + + it 'caches objects on a per collection class basis' do + expect(subject.collection_objects_for_ids(User, [user.id])). + to eq([user]) + + expect(subject.collection_objects_for_ids(Project, [project.id])). + to eq([project]) + end + end + end + + describe '#collection_cache_key' do + it 'returns the cache key for a Class' do + expect(subject.collection_cache_key(Project)).to eq(Project) + end + + it 'returns the cache key for an ActiveRecord::Relation' do + expect(subject.collection_cache_key(Project.all)).to eq(Project) + end + end end diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb new file mode 100644 index 00000000000..f5c064303ad --- /dev/null +++ b/spec/lib/gitlab/graphs/commits_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Gitlab::Graphs::Commits, lib: true do + let!(:project) { create(:project, :public, :empty_repo) } + + let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) } + let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago)} + + let!(:commit2) { create(:commit, git_commit: RepoHelpers.another_sample_commit, project: project, committed_date: Time.now) } + + describe '#commit_per_day' do + context 'when range is only commits from today' do + subject { described_class.new([commit2, commit1]).commit_per_day } + it { is_expected.to eq 2 } + end + end + + context 'when range is only commits from today' do + subject { described_class.new([commit2, commit1]) } + describe '#commit_per_day' do + it { expect(subject.commit_per_day).to eq 2 } + end + + describe '#duration' do + it { expect(subject.duration).to eq 0 } + end + end + + context 'with commits from yesterday and today' do + subject { described_class.new([commit2, commit1_yesterday]) } + describe '#commit_per_day' do + it { expect(subject.commit_per_day).to eq 1 } + end + + describe '#duration' do + it { expect(subject.duration).to eq 1 } + end + end +end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 0b30e8c9b04..7286b0c39c0 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -4208,7 +4208,18 @@ "name": "User 4" }, "events": [ - + { + "id": 529, + "target_type": "Note", + "target_id": 2521, + "title": "test levels", + "data": null, + "project_id": 4, + "created_at": "2016-07-07T14:35:12.128Z", + "updated_at": "2016-07-07T14:35:12.128Z", + "action": 6, + "author_id": 1 + } ] }, { diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index a72aaa44e82..05ffec8ea0a 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -24,11 +24,27 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(Ci::Pipeline.first.notes).not_to be_empty end - it 'restores the correct event' do + it 'restores the correct event with symbolised data' do restored_project_json expect(Event.where.not(data: nil).first.data[:ref]).not_to be_empty end + + context 'event at forth level of the tree' do + let(:event) { Event.where(title: 'test levels').first } + + before do + restored_project_json + end + + it 'restores the event' do + expect(event).not_to be_nil + end + + it 'event belongs to note, belongs to merge request, belongs to a project' do + expect(event.note.noteable.project).not_to be_nil + end + end end end end diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 59024d3290b..2cb74629da8 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -45,6 +45,12 @@ describe Gitlab::UrlSanitizer, lib: true do expect(filtered_content).to include("user@server:project.git") end + + it 'returns an empty string for invalid URLs' do + filtered_content = sanitize_url('ssh://') + + expect(filtered_content).to include("repository '' not found") + end end describe '#sanitized_url' do diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb new file mode 100644 index 00000000000..9a637c94fbe --- /dev/null +++ b/spec/models/merge_request_diff_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe MergeRequestDiff, models: true do + describe '#diffs' do + let(:mr) { create(:merge_request, :with_diffs) } + let(:mr_diff) { mr.merge_request_diff } + + context 'when the :ignore_whitespace_change option is set' do + it 'creates a new compare object instead of loading from the DB' do + expect(mr_diff).not_to receive(:load_diffs) + expect(Gitlab::Git::Compare).to receive(:new).and_call_original + + mr_diff.diffs(ignore_whitespace_change: true) + end + end + + context 'when the raw diffs are empty' do + before { mr_diff.update_attributes(st_diffs: '') } + + it 'returns an empty DiffCollection' do + expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.diffs).to be_empty + end + end + + context 'when the raw diffs exist' do + it 'returns the diffs' do + expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.diffs).not_to be_empty + end + + context 'when the :paths option is set' do + let(:diffs) { mr_diff.diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) } + + it 'only returns diffs that match the (old path, new path) given' do + expect(diffs.map(&:new_path)).to contain_exactly('files/ruby/popen.rb') + end + + it 'uses the diffs from the DB' do + expect(mr_diff).to receive(:load_diffs) + + diffs + end + end + end + end +end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index a4b6ff8f8ad..c8ad7ab3e7f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -116,6 +116,31 @@ describe MergeRequest, models: true do end end + describe '#diffs' do + let(:merge_request) { build(:merge_request) } + let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } } + + context 'when there are MR diffs' do + it 'delegates to the MR diffs' do + merge_request.merge_request_diff = MergeRequestDiff.new + + expect(merge_request.merge_request_diff).to receive(:diffs).with(options) + + merge_request.diffs(options) + end + end + + context 'when there are no MR diffs' do + it 'delegates to the compare object' do + merge_request.compare = double(:compare) + + expect(merge_request.compare).to receive(:diffs).with(options) + + merge_request.diffs(options) + end + end + end + describe "#mr_and_commit_notes" do let!(:merge_request) { create(:merge_request) } diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 6549791f675..7d0697dab42 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -226,6 +226,20 @@ describe Note, models: true do it "returns false" do expect(note.cross_reference_not_visible_for?(private_user)).to be_falsy end + + it "returns false if user visible reference count set" do + note.user_visible_reference_count = 1 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_falsy + end + + it "returns true if ref count is 0" do + note.user_visible_reference_count = 0 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + end end describe 'clear_blank_line_code!' do diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 04141a45031..c2c94040ece 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -49,10 +49,25 @@ describe API::API, api: true do describe "GET /groups/:id" do context "when authenticated as user" do - it "should return one of user1's groups" do + it "returns one of user1's groups" do + project = create(:project, namespace: group2, path: 'Foo') + create(:project_group_link, project: project, group: group1) + get api("/groups/#{group1.id}", user1) + expect(response).to have_http_status(200) - json_response['name'] == group1.name + expect(json_response['id']).to eq(group1.id) + expect(json_response['name']).to eq(group1.name) + expect(json_response['path']).to eq(group1.path) + expect(json_response['description']).to eq(group1.description) + expect(json_response['visibility_level']).to eq(group1.visibility_level) + expect(json_response['avatar_url']).to eq(group1.avatar_url) + expect(json_response['web_url']).to eq(group1.web_url) + expect(json_response['projects']).to be_an Array + expect(json_response['projects'].length).to eq(2) + expect(json_response['shared_projects']).to be_an Array + expect(json_response['shared_projects'].length).to eq(1) + expect(json_response['shared_projects'][0]['id']).to eq(project.id) end it "should not return a non existing group" do diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 4a1b5600bdf..651b91e9f68 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -138,6 +138,8 @@ describe API::API, api: true do expect(json_response['work_in_progress']).to be_falsy expect(json_response['merge_when_build_succeeds']).to be_falsy expect(json_response['merge_status']).to eq('can_be_merged') + expect(json_response['should_close_merge_request']).to be_falsy + expect(json_response['force_close_merge_request']).to be_falsy end it "should return merge_request" do @@ -147,6 +149,8 @@ describe API::API, api: true do expect(json_response['iid']).to eq(merge_request.iid) expect(json_response['work_in_progress']).to eq(false) expect(json_response['merge_status']).to eq('can_be_merged') + expect(json_response['should_close_merge_request']).to be_falsy + expect(json_response['force_close_merge_request']).to be_falsy end it 'should return merge_request by iid' do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 611dd2a2a88..8a52725a893 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -392,11 +392,47 @@ describe API::API, api: true do before { project } before { project_member } - it 'should return a project by id' do + it 'returns a project by id' do + group = create(:group) + link = create(:project_group_link, project: project, group: group) + get api("/projects/#{project.id}", user) + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(project.id) + expect(json_response['description']).to eq(project.description) + expect(json_response['default_branch']).to eq(project.default_branch) + expect(json_response['tag_list']).to be_an Array + expect(json_response['public']).to be_falsey + expect(json_response['archived']).to be_falsey + expect(json_response['visibility_level']).to be_present + expect(json_response['ssh_url_to_repo']).to be_present + expect(json_response['http_url_to_repo']).to be_present + expect(json_response['web_url']).to be_present + expect(json_response['owner']).to be_a Hash + expect(json_response['owner']).to be_a Hash expect(json_response['name']).to eq(project.name) - expect(json_response['owner']['username']).to eq(user.username) + expect(json_response['path']).to be_present + expect(json_response['issues_enabled']).to be_present + expect(json_response['merge_requests_enabled']).to be_present + expect(json_response['wiki_enabled']).to be_present + expect(json_response['builds_enabled']).to be_present + expect(json_response['snippets_enabled']).to be_present + expect(json_response['container_registry_enabled']).to be_present + expect(json_response['created_at']).to be_present + expect(json_response['last_activity_at']).to be_present + expect(json_response['shared_runners_enabled']).to be_present + expect(json_response['creator_id']).to be_present + expect(json_response['namespace']).to be_present + expect(json_response['avatar_url']).to be_nil + expect(json_response['star_count']).to be_present + expect(json_response['forks_count']).to be_present + expect(json_response['public_builds']).to be_present + expect(json_response['shared_with_groups']).to be_an Array + expect(json_response['shared_with_groups'].length).to eq(1) + expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) + expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) + expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) end it 'should return a project by path name' do diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 8a8e131c57b..2c755919456 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -98,7 +98,7 @@ describe SnippetsController, "routing" do end # help GET /help(.:format) help#index -# help_page GET /help/:category/:file(.:format) help#show {:category=>/.*/, :file=>/[^\/\.]+/} +# help_page GET /help/*path(.:format) help#show # help_shortcuts GET /help/shortcuts(.:format) help#shortcuts # help_ui GET /help/ui(.:format) help#ui describe HelpController, "routing" do @@ -109,23 +109,19 @@ describe HelpController, "routing" do it 'to #show' do path = '/help/markdown/markdown.md' expect(get(path)).to route_to('help#show', - category: 'markdown', - file: 'markdown', + path: 'markdown/markdown', format: 'md') path = '/help/workflow/protected_branches/protected_branches1.png' expect(get(path)).to route_to('help#show', - category: 'workflow/protected_branches', - file: 'protected_branches1', + path: 'workflow/protected_branches/protected_branches1', format: 'png') - end - - it 'to #shortcuts' do - expect(get('/help/shortcuts')).to route_to('help#shortcuts') - end - - it 'to #ui' do - expect(get('/help/ui')).to route_to('help#ui') + path = '/help/shortcuts' + expect(get(path)).to route_to('help#show', + path: 'shortcuts') + path = '/help/ui' + expect(get(path)).to route_to('help#show', + path: 'ui') end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 54719cbb8d8..d3dddfb4817 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -293,6 +293,30 @@ describe NotificationService, services: true do end end end + + context "merge request diff note" do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request, source_project: project, assignee: user) } + let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) } + + before do + build_team(note.project) + project.team << [merge_request.author, :master] + project.team << [merge_request.assignee, :master] + end + + describe :new_note do + it "records sent notifications" do + # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note + expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(4).times.and_call_original + + notification.new_note(note) + + expect(SentNotification.last.position).to eq(note.position) + end + end + end end describe 'Issues' do diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index bd4dc6a0f79..7ab95e042ce 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -12,7 +12,7 @@ describe Projects::HousekeepingService do it 'enqueues a sidekiq job' do expect(subject).to receive(:try_obtain_lease).and_return(true) - expect(GitlabShellOneShotWorker).to receive(:perform_async).with(:gc, project.repository_storage_path, project.path_with_namespace) + expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id) subject.execute expect(project.pushes_since_gc).to eq(0) @@ -20,7 +20,7 @@ describe Projects::HousekeepingService do it 'does not enqueue a job when no lease can be obtained' do expect(subject).to receive(:try_obtain_lease).and_return(false) - expect(GitlabShellOneShotWorker).not_to receive(:perform_async) + expect(GitGarbageCollectWorker).not_to receive(:perform_async) expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken) expect(project.pushes_since_gc).to eq(0) diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb index 9b5c3065eed..b57a3493aff 100644 --- a/spec/support/capybara_helpers.rb +++ b/spec/support/capybara_helpers.rb @@ -27,6 +27,14 @@ module CapybaraHelpers end end end + + # Refresh the page. Calling `visit current_url` doesn't seem to work consistently. + # + def refresh + url = current_url + visit 'about:blank' + visit url + end end RSpec.configure do |config| diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 6b99b0f24cb..bb6c84262f6 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -5,19 +5,20 @@ module TestEnv # When developing the seed repository, comment out the branch you will modify. BRANCH_SHA = { - 'empty-branch' => '7efb185', - 'flatten-dir' => 'e56497b', - 'feature' => '0b4bc9a', - 'feature_conflict' => 'bb5206f', - 'fix' => '48f0be4', - 'improve/awesome' => '5937ac0', - 'markdown' => '0ed8c6c', - 'lfs' => 'be93687', - 'master' => '5937ac0', - "'test'" => 'e56497b', - 'orphaned-branch' => '45127a9', - 'binary-encoding' => '7b1cf43', - 'gitattributes' => '5a62481', + 'empty-branch' => '7efb185', + 'flatten-dir' => 'e56497b', + 'feature' => '0b4bc9a', + 'feature_conflict' => 'bb5206f', + 'fix' => '48f0be4', + 'improve/awesome' => '5937ac0', + 'markdown' => '0ed8c6c', + 'lfs' => 'be93687', + 'master' => '5937ac0', + "'test'" => 'e56497b', + 'orphaned-branch' => '45127a9', + 'binary-encoding' => '7b1cf43', + 'gitattributes' => '5a62481', + 'expand-collapse-diffs' => '4842455' } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb new file mode 100644 index 00000000000..a9cce8b8b59 --- /dev/null +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe GitGarbageCollectWorker do + let(:project) { create(:project) } + let(:shell) { Gitlab::Shell.new } + + subject { GitGarbageCollectWorker.new } + + before do + allow(subject).to receive(:gitlab_shell).and_return(shell) + end + + describe "#perform" do + it "runs `git gc`" do + expect(shell).to receive(:gc).with( + project.repository_storage_path, + project.path_with_namespace). + and_return(true) + expect_any_instance_of(Repository).to receive(:after_create_branch) + + subject.perform(project.id) + end + end +end |