summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/milestone_select.js3
-rw-r--r--app/controllers/projects/compare_controller.rb6
-rw-r--r--app/finders/issuable_finder.rb12
-rw-r--r--app/finders/issues_finder.rb4
-rw-r--r--app/finders/merge_requests_finder.rb4
-rw-r--r--app/models/concerns/issuable.rb1
-rw-r--r--app/models/concerns/updated_at_filterable.rb12
-rw-r--r--app/services/issuable_base_service.rb6
-rw-r--r--app/services/merge_requests/base_service.rb11
-rw-r--r--app/services/merge_requests/create_service.rb6
-rw-r--r--app/services/merge_requests/update_service.rb11
-rw-r--r--app/services/quick_actions/interpret_service.rb6
-rw-r--r--changelogs/unreleased/41616-api-issues-between-date.yml5
-rw-r--r--changelogs/unreleased/41719-mr-title-fix.yml5
-rw-r--r--changelogs/unreleased/43837-error-handle-in-updating-milestone-on-issue.yml5
-rw-r--r--changelogs/unreleased/feature--43691-count-diff-note-calendar-activity.yml5
-rw-r--r--changelogs/unreleased/wip-new-mr-cmd.yml4
-rw-r--r--config/initializers/forbid_sidekiq_in_transactions.rb17
-rw-r--r--doc/api/issues.md14
-rw-r--r--doc/api/merge_requests.md12
-rw-r--r--lib/api/issues.rb2
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/banzai/filter/autolink_filter.rb86
-rw-r--r--lib/gitlab/contributions_calendar.rb2
-rw-r--r--lib/gitlab/git/blob.rb14
-rw-r--r--lib/gitlab/git/lfs_changes.rb26
-rw-r--r--lib/gitlab/git/repository.rb6
-rw-r--r--lib/gitlab/gitaly_client/blob_service.rb55
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb4
-rw-r--r--lib/gitlab/string_range_marker.rb2
-rw-r--r--lib/gitlab/string_regex_marker.rb12
-rw-r--r--spec/finders/issues_finder_spec.rb42
-rw-r--r--spec/finders/merge_requests_finder_spec.rb51
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb126
-rw-r--r--spec/lib/gitlab/checks/lfs_integrity_spec.rb22
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb14
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb29
-rw-r--r--spec/lib/gitlab/git/lfs_changes_spec.rb38
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb360
-rw-r--r--spec/lib/gitlab/gitaly_client/blob_service_spec.rb60
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb35
-rw-r--r--spec/requests/api/issues_spec.rb36
-rw-r--r--spec/requests/api/merge_requests_spec.rb36
-rw-r--r--spec/services/merge_requests/create_service_spec.rb35
-rw-r--r--spec/support/bare_repo_operations.rb14
48 files changed, 900 insertions, 366 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 359ee08a7ce..fe6d01c1a45 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.87.0
+0.88.0
diff --git a/Gemfile b/Gemfile
index d8cb5267d81..a3352b8923c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -411,7 +411,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.87.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
gem 'google-protobuf', '= 3.5.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 6918f92aa84..70f86a45043 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.87.0)
+ gitaly-proto (0.88.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (5.3.3)
@@ -1057,7 +1057,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.87.0)
+ gitaly-proto (~> 0.88.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 2841ecb558b..c259d5405bd 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -216,6 +216,9 @@ export default class MilestoneSelect {
$value.html(milestoneLinkNoneTemplate);
return $sidebarCollapsedValue.find('span').text('No');
}
+ })
+ .catch(() => {
+ $loading.fadeOut();
});
}
}
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 3cb4eb23981..2b0c2ca97c0 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -17,10 +17,8 @@ class Projects::CompareController < Projects::ApplicationController
def show
apply_diff_view_cookie!
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37430
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- render
- end
+
+ render
end
def diff_for_path
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 9dd6634b38f..b2d4f9938ff 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -19,6 +19,10 @@
# non_archived: boolean
# iids: integer[]
# my_reaction_emoji: string
+# created_after: datetime
+# created_before: datetime
+# updated_after: datetime
+# updated_before: datetime
#
class IssuableFinder
prepend FinderWithCrossProjectAccess
@@ -79,6 +83,7 @@ class IssuableFinder
def filter_items(items)
items = by_scope(items)
items = by_created_at(items)
+ items = by_updated_at(items)
items = by_state(items)
items = by_group(items)
items = by_search(items)
@@ -283,6 +288,13 @@ class IssuableFinder
end
end
+ def by_updated_at(items)
+ items = items.updated_after(params[:updated_after]) if params[:updated_after].present?
+ items = items.updated_before(params[:updated_before]) if params[:updated_before].present?
+
+ items
+ end
+
def by_state(items)
case params[:state].to_s
when 'closed'
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index d65c620e75a..2a27ff0e386 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -17,6 +17,10 @@
# my_reaction_emoji: string
# public_only: boolean
# due_date: date or '0', '', 'overdue', 'week', or 'month'
+# created_after: datetime
+# created_before: datetime
+# updated_after: datetime
+# updated_before: datetime
#
class IssuesFinder < IssuableFinder
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index 068ae7f8c89..64dc1e6af0f 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -19,6 +19,10 @@
# my_reaction_emoji: string
# source_branch: string
# target_branch: string
+# created_after: datetime
+# created_before: datetime
+# updated_after: datetime
+# updated_before: datetime
#
class MergeRequestsFinder < IssuableFinder
def klass
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 7049f340c9d..4560bc23193 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -19,6 +19,7 @@ module Issuable
include AfterCommitQueue
include Sortable
include CreatedAtFilterable
+ include UpdatedAtFilterable
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
diff --git a/app/models/concerns/updated_at_filterable.rb b/app/models/concerns/updated_at_filterable.rb
new file mode 100644
index 00000000000..edb423b7828
--- /dev/null
+++ b/app/models/concerns/updated_at_filterable.rb
@@ -0,0 +1,12 @@
+module UpdatedAtFilterable
+ extend ActiveSupport::Concern
+
+ included do
+ scope :updated_before, ->(date) { where(scoped_table[:updated_at].lteq(date)) }
+ scope :updated_after, ->(date) { where(scoped_table[:updated_at].gteq(date)) }
+
+ def self.scoped_table
+ arel_table.alias(table_name)
+ end
+ end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index e87fd49d193..02fb48108fb 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -109,6 +109,10 @@ class IssuableBaseService < BaseService
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
end
+ def handle_quick_actions_on_create(issuable)
+ merge_quick_actions_into_params!(issuable)
+ end
+
def merge_quick_actions_into_params!(issuable)
original_description = params.fetch(:description, issuable.description)
@@ -131,7 +135,7 @@ class IssuableBaseService < BaseService
end
def create(issuable)
- merge_quick_actions_into_params!(issuable)
+ handle_quick_actions_on_create(issuable)
filter_params(issuable)
params.delete(:state_event)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 20a2b50d3de..23262b62615 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -24,6 +24,17 @@ module MergeRequests
private
+ def handle_wip_event(merge_request)
+ if wip_event = params.delete(:wip_event)
+ # We update the title that is provided in the params or we use the mr title
+ title = params[:title] || merge_request.title
+ params[:title] = case wip_event
+ when 'wip' then MergeRequest.wip_title(title)
+ when 'unwip' then MergeRequest.wipless_title(title)
+ end
+ end
+ end
+
def merge_request_metrics_service(merge_request)
MergeRequestMetricsService.new(merge_request.metrics)
end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index a18b1c90765..c57a2445341 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -34,6 +34,12 @@ module MergeRequests
super
end
+ # Override from IssuableBaseService
+ def handle_quick_actions_on_create(merge_request)
+ super
+ handle_wip_event(merge_request)
+ end
+
private
def update_merge_requests_head_pipeline(merge_request)
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index c153872c874..8a40ad88182 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -98,17 +98,6 @@ module MergeRequests
private
- def handle_wip_event(merge_request)
- if wip_event = params.delete(:wip_event)
- # We update the title that is provided in the params or we use the mr title
- title = params[:title] || merge_request.title
- params[:title] = case wip_event
- when 'wip' then MergeRequest.wip_title(title)
- when 'unwip' then MergeRequest.wipless_title(title)
- end
- end
- end
-
def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
SystemNoteService.change_branch(
issuable, issuable.project, current_user, branch_type,
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 1e9bd84e749..cba49faac31 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -347,9 +347,9 @@ module QuickActions
"#{verb} this #{noun} as Work In Progress."
end
condition do
- issuable.persisted? &&
- issuable.respond_to?(:work_in_progress?) &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
+ issuable.respond_to?(:work_in_progress?) &&
+ # Allow it to mark as WIP on MR creation page _or_ through MR notes.
+ (issuable.new_record? || current_user.can?(:"update_#{issuable.to_ability_name}", issuable))
end
command :wip do
@updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
diff --git a/changelogs/unreleased/41616-api-issues-between-date.yml b/changelogs/unreleased/41616-api-issues-between-date.yml
new file mode 100644
index 00000000000..d8a23f48699
--- /dev/null
+++ b/changelogs/unreleased/41616-api-issues-between-date.yml
@@ -0,0 +1,5 @@
+---
+title: Adds updated_at filter to issues and merge_requests API
+merge_request: 17417
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/41719-mr-title-fix.yml b/changelogs/unreleased/41719-mr-title-fix.yml
new file mode 100644
index 00000000000..92388f30cb2
--- /dev/null
+++ b/changelogs/unreleased/41719-mr-title-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Render htmlentities correctly for links not supported by Rinku
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/43837-error-handle-in-updating-milestone-on-issue.yml b/changelogs/unreleased/43837-error-handle-in-updating-milestone-on-issue.yml
new file mode 100644
index 00000000000..526523964c3
--- /dev/null
+++ b/changelogs/unreleased/43837-error-handle-in-updating-milestone-on-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Stop loading spinner on error of milestone update on issue
+merge_request: 17507
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/feature--43691-count-diff-note-calendar-activity.yml b/changelogs/unreleased/feature--43691-count-diff-note-calendar-activity.yml
new file mode 100644
index 00000000000..768686aeda8
--- /dev/null
+++ b/changelogs/unreleased/feature--43691-count-diff-note-calendar-activity.yml
@@ -0,0 +1,5 @@
+---
+title: Count comments on diffs as contributions for the contributions calendar
+merge_request: 17418
+author: Riccardo Padovani
+type: fixed
diff --git a/changelogs/unreleased/wip-new-mr-cmd.yml b/changelogs/unreleased/wip-new-mr-cmd.yml
new file mode 100644
index 00000000000..ce7072631dd
--- /dev/null
+++ b/changelogs/unreleased/wip-new-mr-cmd.yml
@@ -0,0 +1,4 @@
+title: Port /wip quick action command to Merge Request creation (on description)
+merge_request: 17463
+author: Adam Pahlevi
+type: added
diff --git a/config/initializers/forbid_sidekiq_in_transactions.rb b/config/initializers/forbid_sidekiq_in_transactions.rb
index cb611aa21df..4cf1d455eb4 100644
--- a/config/initializers/forbid_sidekiq_in_transactions.rb
+++ b/config/initializers/forbid_sidekiq_in_transactions.rb
@@ -18,13 +18,26 @@ module Sidekiq
%i(perform_async perform_at perform_in).each do |name|
define_method(name) do |*args|
if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction?
- raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
+ begin
+ raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
`#{self}.#{name}` cannot be called inside a transaction as this can lead to
race conditions when the worker runs before the transaction is committed and
tries to access a model that has not been saved yet.
Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead.
- MSG
+ MSG
+ rescue Sidekiq::Worker::EnqueueFromTransactionError => e
+ if Rails.env.production?
+ Rails.logger.error(e.message)
+
+ if Gitlab::Sentry.enabled?
+ Gitlab::Sentry.context
+ Raven.capture_exception(e)
+ end
+ else
+ raise
+ end
+ end
end
super(*args)
diff --git a/doc/api/issues.md b/doc/api/issues.md
index da89db17cd9..a4a51101297 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -46,6 +46,10 @@ GET /issues?my_reaction_emoji=star
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search issues against their `title` and `description` |
+| `created_after` | datetime | no | Return issues created on or after the given time |
+| `created_before` | datetime | no | Return issues created on or before the given time |
+| `updated_after` | datetime | no | Return issues updated on or after the given time |
+| `updated_before` | datetime | no | Return issues updated on or before the given time |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues
@@ -152,6 +156,10 @@ GET /groups/:id/issues?my_reaction_emoji=star
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search group issues against their `title` and `description` |
+| `created_after` | datetime | no | Return issues created on or after the given time |
+| `created_before` | datetime | no | Return issues created on or before the given time |
+| `updated_after` | datetime | no | Return issues updated on or after the given time |
+| `updated_before` | datetime | no | Return issues updated on or before the given time |
```bash
@@ -259,8 +267,10 @@ GET /projects/:id/issues?my_reaction_emoji=star
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search project issues against their `title` and `description` |
-| `created_after` | datetime | no | Return issues created after the given time (inclusive) |
-| `created_before` | datetime | no | Return issues created before the given time (inclusive) |
+| `created_after` | datetime | no | Return issues created on or after the given time |
+| `created_before` | datetime | no | Return issues created on or before the given time |
+| `updated_after` | datetime | no | Return issues updated on or after the given time |
+| `updated_before` | datetime | no | Return issues updated on or before the given time |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 3e60e001bc1..25b0807eb18 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -41,8 +41,10 @@ Parameters:
| `milestone` | string | no | Return merge requests for a specific milestone |
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
| `labels` | string | no | Return merge requests matching a comma separated list of labels |
-| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
-| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
+| `created_after` | datetime | no | Return merge requests created on or after the given time |
+| `created_before` | datetime | no | Return merge requests created on or before the given time |
+| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
+| `updated_before` | datetime | no | Return merge requests updated on or before the given time |
| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` |
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` |
@@ -158,8 +160,10 @@ Parameters:
| `milestone` | string | no | Return merge requests for a specific milestone |
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
| `labels` | string | no | Return merge requests matching a comma separated list of labels |
-| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
-| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
+| `created_after` | datetime | no | Return merge requests created on or after the given time |
+| `created_before` | datetime | no | Return merge requests created on or before the given time |
+| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
+| `updated_before` | datetime | no | Return merge requests updated on or before the given time |
| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index b6c278c89d0..f74b3b26802 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -32,6 +32,8 @@ module API
optional :search, type: String, desc: 'Search issues for text present in the title or description'
optional :created_after, type: DateTime, desc: 'Return issues created after the specified time'
optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
+ optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time'
+ optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time'
optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 16d0f005f21..8c02972b421 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -42,6 +42,8 @@ module API
optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
+ optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time'
+ optional :updated_before, type: DateTime, desc: 'Return merge requests updated before the specified time'
optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request'
optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID'
optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID'
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index b8d2673c1a6..75b64ae9af2 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -25,8 +25,8 @@ module Banzai
# period or comma for punctuation without those characters being included
# in the generated link.
#
- # Rubular: http://rubular.com/r/cxjPyZc7Sb
- LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?<!,|\.)}
+ # Rubular: http://rubular.com/r/JzPhi6DCZp
+ LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!,|\.)}
# Text matching LINK_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
@@ -35,53 +35,19 @@ module Banzai
TEXT_QUERY = %Q(descendant-or-self::text()[
not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')})
and contains(., '://')
- and not(starts-with(., 'http'))
- and not(starts-with(., 'ftp'))
]).freeze
+ PUNCTUATION_PAIRS = {
+ "'" => "'",
+ '"' => '"',
+ ')' => '(',
+ ']' => '[',
+ '}' => '{'
+ }.freeze
+
def call
return doc if context[:autolink] == false
- rinku_parse
- text_parse
- end
-
- private
-
- # Run the text through Rinku as a first pass
- #
- # This will quickly autolink http(s) and ftp links.
- #
- # `@doc` will be re-parsed with the HTML String from Rinku.
- def rinku_parse
- # Convert the options from a Hash to a String that Rinku expects
- options = tag_options(link_options)
-
- # NOTE: We don't parse email links because it will erroneously match
- # external Commit and CommitRange references.
- #
- # The final argument tells Rinku to link short URLs that don't include a
- # period (e.g., http://localhost:3000/)
- rinku = Rinku.auto_link(html, :urls, options, IGNORE_PARENTS.to_a, 1)
-
- return if rinku == html
-
- # Rinku returns a String, so parse it back to a Nokogiri::XML::Document
- # for further processing.
- @doc = parse_html(rinku)
- end
-
- # Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
- def contains_unsafe?(scheme)
- return false unless scheme
-
- scheme = scheme.strip.downcase
- Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
- end
-
- # Autolinks any text matching LINK_PATTERN that Rinku didn't already
- # replace
- def text_parse
doc.xpath(TEXT_QUERY).each do |node|
content = node.to_html
@@ -97,6 +63,16 @@ module Banzai
doc
end
+ private
+
+ # Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
+ def contains_unsafe?(scheme)
+ return false unless scheme
+
+ scheme = scheme.strip.downcase
+ Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
+ end
+
def autolink_match(match)
# start by stripping out dangerous links
begin
@@ -112,12 +88,30 @@ module Banzai
match.gsub!(/((?:&[\w#]+;)+)\z/, '')
dropped = ($1 || '').html_safe
+ # To match the behaviour of Rinku, if the matched link ends with a
+ # closing part of a matched pair of punctuation, we remove that trailing
+ # character unless there are an equal number of closing and opening
+ # characters in the link.
+ if match.end_with?(*PUNCTUATION_PAIRS.keys)
+ close_character = match[-1]
+ close_count = match.count(close_character)
+ open_character = PUNCTUATION_PAIRS[close_character]
+ open_count = match.count(open_character)
+
+ if open_count != close_count || open_character == close_character
+ dropped += close_character
+ match = match[0..-2]
+ end
+ end
+
options = link_options.merge(href: match)
- content_tag(:a, match, options) + dropped
+ content_tag(:a, match.html_safe, options) + dropped
end
def autolink_filter(text)
- text.gsub(LINK_PATTERN) { |match| autolink_match(match) }
+ Gitlab::StringRegexMarker.new(CGI.unescapeHTML(text), text.html_safe).mark(LINK_PATTERN) do |link, left:, right:|
+ autolink_match(link)
+ end
end
def link_options
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 9576d5a3fd8..02d3763514e 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -23,7 +23,7 @@ module Gitlab
mr_events = event_counts(date_from, :merge_requests)
.having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
note_events = event_counts(date_from, :merge_requests)
- .having(action: [Event::COMMENTED], target_type: "Note")
+ .having(action: [Event::COMMENTED], target_type: %w(Note DiffNote))
union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events])
events = Event.find_by_sql(union.to_sql).map(&:attributes)
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index b2fca2c16de..eabcf46cf58 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -238,9 +238,9 @@ module Gitlab
self.__send__("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
end
- @loaded_all_data = false
# Retain the actual size before it is encoded
@loaded_size = @data.bytesize if @data
+ @loaded_all_data = @loaded_size == size
end
def binary?
@@ -255,10 +255,15 @@ module Gitlab
# memory as a Ruby string.
def load_all_data!(repository)
return if @data == '' # don't mess with submodule blobs
- return @data if @loaded_all_data
- Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
- @data = begin
+ # Even if we return early, recalculate wether this blob is binary in
+ # case a blob was initialized as text but the full data isn't
+ @binary = nil
+
+ return if @loaded_all_data
+
+ @data = Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
+ begin
if is_enabled
repository.gitaly_blob_client.get_blob(oid: id, limit: -1).data
else
@@ -269,7 +274,6 @@ module Gitlab
@loaded_all_data = true
@loaded_size = @data.bytesize
- @binary = nil
end
def name
diff --git a/lib/gitlab/git/lfs_changes.rb b/lib/gitlab/git/lfs_changes.rb
index 48434047fce..b9e5cf258f4 100644
--- a/lib/gitlab/git/lfs_changes.rb
+++ b/lib/gitlab/git/lfs_changes.rb
@@ -7,6 +7,28 @@ module Gitlab
end
def new_pointers(object_limit: nil, not_in: nil)
+ @repository.gitaly_migrate(:blob_get_new_lfs_pointers) do |is_enabled|
+ if is_enabled
+ @repository.gitaly_blob_client.get_new_lfs_pointers(@newrev, object_limit, not_in)
+ else
+ git_new_pointers(object_limit, not_in)
+ end
+ end
+ end
+
+ def all_pointers
+ @repository.gitaly_migrate(:blob_get_all_lfs_pointers) do |is_enabled|
+ if is_enabled
+ @repository.gitaly_blob_client.get_all_lfs_pointers(@newrev)
+ else
+ git_all_pointers
+ end
+ end
+ end
+
+ private
+
+ def git_new_pointers(object_limit, not_in)
@new_pointers ||= begin
rev_list.new_objects(not_in: not_in, require_path: true) do |object_ids|
object_ids = object_ids.take(object_limit) if object_limit
@@ -16,14 +38,12 @@ module Gitlab
end
end
- def all_pointers
+ def git_all_pointers
rev_list.all_objects(require_path: true) do |object_ids|
Gitlab::Git::Blob.batch_lfs_pointers(@repository, object_ids)
end
end
- private
-
def rev_list
Gitlab::Git::RevList.new(@repository, newrev: @newrev)
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index d7c373ccd6f..21c79a7a550 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -479,9 +479,8 @@ module Gitlab
raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
end
- # TODO support options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1049
gitaly_migrate(:find_commits) do |is_enabled|
- if is_enabled && !options[:all]
+ if is_enabled
gitaly_commit_client.find_commits(options)
else
raw_log(options).map { |c| Commit.decorate(self, c) }
@@ -508,9 +507,8 @@ module Gitlab
def count_commits(options)
count_commits_options = process_count_commits_options(options)
- # TODO add support for options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1050
gitaly_migrate(:count_commits) do |is_enabled|
- if is_enabled && !options[:all]
+ if is_enabled
count_commits_by_gitaly(count_commits_options)
else
count_commits_by_shelling_out(count_commits_options)
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
index dfa0fa43b0f..28554208984 100644
--- a/lib/gitlab/gitaly_client/blob_service.rb
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -45,16 +45,7 @@ module Gitlab
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request)
- response.flat_map do |message|
- message.lfs_pointers.map do |lfs_pointer|
- Gitlab::Git::Blob.new(
- id: lfs_pointer.oid,
- size: lfs_pointer.size,
- data: lfs_pointer.data,
- binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
- )
- end
- end
+ map_lfs_pointers(response)
end
def get_blobs(revision_paths, limit = -1)
@@ -80,6 +71,50 @@ module Gitlab
GitalyClient::BlobsStitcher.new(response)
end
+
+ def get_new_lfs_pointers(revision, limit, not_in)
+ request = Gitaly::GetNewLFSPointersRequest.new(
+ repository: @gitaly_repo,
+ revision: encode_binary(revision),
+ limit: limit || 0
+ )
+
+ if not_in.nil? || not_in == :all
+ request.not_in_all = true
+ else
+ request.not_in_refs += not_in
+ end
+
+ response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_new_lfs_pointers, request)
+
+ map_lfs_pointers(response)
+ end
+
+ def get_all_lfs_pointers(revision)
+ request = Gitaly::GetNewLFSPointersRequest.new(
+ repository: @gitaly_repo,
+ revision: encode_binary(revision)
+ )
+
+ response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request)
+
+ map_lfs_pointers(response)
+ end
+
+ private
+
+ def map_lfs_pointers(response)
+ response.flat_map do |message|
+ message.lfs_pointers.map do |lfs_pointer|
+ Gitlab::Git::Blob.new(
+ id: lfs_pointer.oid,
+ size: lfs_pointer.size,
+ data: lfs_pointer.data,
+ binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
+ )
+ end
+ end
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 1ad0bf1d060..456a8a1a2d6 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -134,7 +134,8 @@ module Gitlab
def commit_count(ref, options = {})
request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo,
- revision: encode_binary(ref)
+ revision: encode_binary(ref),
+ all: !!options[:all]
)
request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present?
request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
@@ -269,6 +270,7 @@ module Gitlab
offset: options[:offset],
follow: options[:follow],
skip_merges: options[:skip_merges],
+ all: !!options[:all],
disable_walk: true # This option is deprecated. The 'walk' implementation is being removed.
)
request.after = GitalyClient.timestamp(options[:after]) if options[:after]
diff --git a/lib/gitlab/string_range_marker.rb b/lib/gitlab/string_range_marker.rb
index f9faa134206..c6ad997a4d4 100644
--- a/lib/gitlab/string_range_marker.rb
+++ b/lib/gitlab/string_range_marker.rb
@@ -14,7 +14,7 @@ module Gitlab
end
def mark(marker_ranges)
- return rich_line unless marker_ranges
+ return rich_line unless marker_ranges&.any?
if html_escaped
rich_marker_ranges = []
diff --git a/lib/gitlab/string_regex_marker.rb b/lib/gitlab/string_regex_marker.rb
index 7ebf1c0428c..b19aa6dea35 100644
--- a/lib/gitlab/string_regex_marker.rb
+++ b/lib/gitlab/string_regex_marker.rb
@@ -1,13 +1,15 @@
module Gitlab
class StringRegexMarker < StringRangeMarker
def mark(regex, group: 0, &block)
- regex_match = raw_line.match(regex)
- return rich_line unless regex_match
+ ranges = []
- begin_index, end_index = regex_match.offset(group)
- name_range = begin_index..(end_index - 1)
+ raw_line.scan(regex) do
+ begin_index, end_index = Regexp.last_match.offset(group)
- super([name_range], &block)
+ ranges << (begin_index..(end_index - 1))
+ end
+
+ super(ranges, &block)
end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index abb7631d7d7..45439640ea3 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -10,9 +10,9 @@ describe IssuesFinder do
set(:project3) { create(:project, group: subgroup) }
set(:milestone) { create(:milestone, project: project1) }
set(:label) { create(:label, project: project2) }
- set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) }
- set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') }
- set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) }
+ set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) }
+ set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) }
+ set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) }
set(:issue4) { create(:issue, project: project3) }
set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
@@ -275,12 +275,46 @@ describe IssuesFinder do
end
context 'through created_before' do
- let(:params) { { created_before: issue1.created_at + 1.second } }
+ let(:params) { { created_before: issue1.created_at } }
it 'returns issues created on or before the given date' do
expect(issues).to contain_exactly(issue1)
end
end
+
+ context 'through created_after and created_before' do
+ let(:params) { { created_after: issue2.created_at, created_before: issue3.created_at } }
+
+ it 'returns issues created between the given dates' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
+ end
+ end
+
+ context 'filtering by updated_at' do
+ context 'through updated_after' do
+ let(:params) { { updated_after: issue3.updated_at } }
+
+ it 'returns issues updated on or after the given date' do
+ expect(issues).to contain_exactly(issue3)
+ end
+ end
+
+ context 'through updated_before' do
+ let(:params) { { updated_before: issue1.updated_at } }
+
+ it 'returns issues updated on or before the given date' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
+
+ context 'through updated_after and updated_before' do
+ let(:params) { { updated_after: issue2.updated_at, updated_before: issue3.updated_at } }
+
+ it 'returns issues updated between the given dates' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
+ end
end
context 'filtering by reaction name' do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 7917a00fc50..c8a43ddf410 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -109,7 +109,7 @@ describe MergeRequestsFinder do
end
end
- context 'with created_after and created_before params' do
+ context 'filtering by created_at/updated_at' do
let(:new_project) { create(:project, forked_from_project: project1) }
let!(:new_merge_request) do
@@ -117,15 +117,18 @@ describe MergeRequestsFinder do
:simple,
author: user,
created_at: 1.week.from_now,
+ updated_at: 1.week.from_now,
source_project: new_project,
- target_project: project1)
+ target_project: new_project)
end
let!(:old_merge_request) do
create(:merge_request,
:simple,
author: user,
+ source_branch: 'feature_1',
created_at: 1.week.ago,
+ updated_at: 1.week.ago,
source_project: new_project,
target_project: new_project)
end
@@ -135,7 +138,7 @@ describe MergeRequestsFinder do
end
it 'filters by created_after' do
- params = { project_id: project1.id, created_after: new_merge_request.created_at }
+ params = { project_id: new_project.id, created_after: new_merge_request.created_at }
merge_requests = described_class.new(user, params).execute
@@ -143,12 +146,52 @@ describe MergeRequestsFinder do
end
it 'filters by created_before' do
- params = { project_id: new_project.id, created_before: old_merge_request.created_at + 1.second }
+ params = { project_id: new_project.id, created_before: old_merge_request.created_at }
merge_requests = described_class.new(user, params).execute
expect(merge_requests).to contain_exactly(old_merge_request)
end
+
+ it 'filters by created_after and created_before' do
+ params = {
+ project_id: new_project.id,
+ created_after: old_merge_request.created_at,
+ created_before: new_merge_request.created_at
+ }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+ end
+
+ it 'filters by updated_after' do
+ params = { project_id: new_project.id, updated_after: new_merge_request.updated_at }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(new_merge_request)
+ end
+
+ it 'filters by updated_before' do
+ params = { project_id: new_project.id, updated_before: old_merge_request.updated_at }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request)
+ end
+
+ it 'filters by updated_after and updated_before' do
+ params = {
+ project_id: new_project.id,
+ updated_after: old_merge_request.updated_at,
+ updated_before: new_merge_request.updated_at
+ }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+ end
end
end
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index b7c2ff03125..b502daea418 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -4,6 +4,7 @@ describe Banzai::Filter::AutolinkFilter do
include FilterSpecHelper
let(:link) { 'http://about.gitlab.com/' }
+ let(:quotes) { ['"', "'"] }
it 'does nothing when :autolink is false' do
exp = act = link
@@ -15,17 +16,7 @@ describe Banzai::Filter::AutolinkFilter do
expect(filter(act).to_html).to eq exp
end
- context 'when the input contains no links' do
- it 'does not parse_html back the rinku returned value' do
- act = HTML::Pipeline.parse('<p>This text contains no links to autolink</p>')
-
- expect_any_instance_of(described_class).not_to receive(:parse_html)
-
- filter(act).to_html
- end
- end
-
- context 'Rinku schemes' do
+ context 'Various schemes' do
it 'autolinks http' do
doc = filter("See #{link}")
expect(doc.at_css('a').text).to eq link
@@ -56,32 +47,26 @@ describe Banzai::Filter::AutolinkFilter do
expect(doc.at_css('a')['href']).to eq link
end
- it 'accepts link_attr options' do
- doc = filter("See #{link}", link_attr: { class: 'custom' })
+ it 'autolinks multiple URLs' do
+ link1 = 'http://localhost:3000/'
+ link2 = 'http://google.com/'
- expect(doc.at_css('a')['class']).to eq 'custom'
- end
+ doc = filter("See #{link1} and #{link2}")
- described_class::IGNORE_PARENTS.each do |elem|
- it "ignores valid links contained inside '#{elem}' element" do
- exp = act = "<#{elem}>See #{link}</#{elem}>"
- expect(filter(act).to_html).to eq exp
- end
- end
+ found_links = doc.css('a')
- context 'when the input contains link' do
- it 'does parse_html back the rinku returned value' do
- act = HTML::Pipeline.parse("<p>See #{link}</p>")
+ expect(found_links.size).to eq(2)
+ expect(found_links[0].text).to eq(link1)
+ expect(found_links[0]['href']).to eq(link1)
+ expect(found_links[1].text).to eq(link2)
+ expect(found_links[1]['href']).to eq(link2)
+ end
- expect_any_instance_of(described_class).to receive(:parse_html).at_least(:once).and_call_original
+ it 'accepts link_attr options' do
+ doc = filter("See #{link}", link_attr: { class: 'custom' })
- filter(act).to_html
- end
+ expect(doc.at_css('a')['class']).to eq 'custom'
end
- end
-
- context 'other schemes' do
- let(:link) { 'foo://bar.baz/' }
it 'autolinks smb' do
link = 'smb:///Volumes/shared/foo.pdf'
@@ -91,6 +76,21 @@ describe Banzai::Filter::AutolinkFilter do
expect(doc.at_css('a')['href']).to eq link
end
+ it 'autolinks multiple occurences of smb' do
+ link1 = 'smb:///Volumes/shared/foo.pdf'
+ link2 = 'smb:///Volumes/shared/bar.pdf'
+
+ doc = filter("See #{link1} and #{link2}")
+
+ found_links = doc.css('a')
+
+ expect(found_links.size).to eq(2)
+ expect(found_links[0].text).to eq(link1)
+ expect(found_links[0]['href']).to eq(link1)
+ expect(found_links[1].text).to eq(link2)
+ expect(found_links[1]['href']).to eq(link2)
+ end
+
it 'autolinks irc' do
link = 'irc://irc.freenode.net/git'
doc = filter("See #{link}")
@@ -132,6 +132,45 @@ describe Banzai::Filter::AutolinkFilter do
expect(doc.at_css('a').text).to eq link
end
+ it 'includes trailing punctuation when part of a balanced pair' do
+ described_class::PUNCTUATION_PAIRS.each do |close, open|
+ next if open.in?(quotes)
+
+ balanced_link = "#{link}#{open}abc#{close}"
+ balanced_actual = filter("See #{balanced_link}...")
+ unbalanced_link = "#{link}#{close}"
+ unbalanced_actual = filter("See #{unbalanced_link}...")
+
+ expect(balanced_actual.at_css('a').text).to eq(balanced_link)
+ expect(unescape(balanced_actual.to_html)).to eq(Rinku.auto_link("See #{balanced_link}..."))
+ expect(unbalanced_actual.at_css('a').text).to eq(link)
+ expect(unescape(unbalanced_actual.to_html)).to eq(Rinku.auto_link("See #{unbalanced_link}..."))
+ end
+ end
+
+ it 'removes trailing quotes' do
+ quotes.each do |quote|
+ balanced_link = "#{link}#{quote}abc#{quote}"
+ balanced_actual = filter("See #{balanced_link}...")
+ unbalanced_link = "#{link}#{quote}"
+ unbalanced_actual = filter("See #{unbalanced_link}...")
+
+ expect(balanced_actual.at_css('a').text).to eq(balanced_link[0...-1])
+ expect(unescape(balanced_actual.to_html)).to eq(Rinku.auto_link("See #{balanced_link}..."))
+ expect(unbalanced_actual.at_css('a').text).to eq(link)
+ expect(unescape(unbalanced_actual.to_html)).to eq(Rinku.auto_link("See #{unbalanced_link}..."))
+ end
+ end
+
+ it 'removes one closing punctuation mark when the punctuation in the link is unbalanced' do
+ complicated_link = "(#{link}(a'b[c'd]))'"
+ expected_complicated_link = %Q{(<a href="#{link}(a'b[c'd]))">#{link}(a'b[c'd]))</a>'}
+ actual = unescape(filter(complicated_link).to_html)
+
+ expect(actual).to eq(Rinku.auto_link(complicated_link))
+ expect(actual).to eq(expected_complicated_link)
+ end
+
it 'does not include trailing HTML entities' do
doc = filter("See &lt;&lt;&lt;#{link}&gt;&gt;&gt;")
@@ -151,4 +190,29 @@ describe Banzai::Filter::AutolinkFilter do
end
end
end
+
+ context 'when the link is inside a tag' do
+ %w[http rdar].each do |protocol|
+ it "renders text after the link correctly for #{protocol}" do
+ doc = filter(ERB::Util.html_escape_once("<#{protocol}://link><another>"))
+
+ expect(doc.children.last.text).to include('<another>')
+ end
+ end
+ end
+
+ # Rinku does not escape these characters in HTML attributes, but content_tag
+ # does. We don't care about that difference for these specs, though.
+ def unescape(html)
+ %w([ ] { }).each do |cgi_escape|
+ html.sub!(CGI.escape(cgi_escape), cgi_escape)
+ end
+
+ quotes.each do |html_escape|
+ html.sub!(CGI.escape_html(html_escape), html_escape)
+ html.sub!(CGI.escape(html_escape), CGI.escape_html(html_escape))
+ end
+
+ html
+ end
end
diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
index 17756621221..7201e4f7bf6 100644
--- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
@@ -2,23 +2,25 @@ require 'spec_helper'
describe Gitlab::Checks::LfsIntegrity do
include ProjectForksHelper
+
let(:project) { create(:project, :repository) }
- let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
+ let(:repository) { project.repository }
+ let(:newrev) do
+ operations = BareRepoOperations.new(repository.path)
+
+ # Create a commit not pointed at by any ref to emulate being in the
+ # pre-receive hook so that `--not --all` returns some objects
+ operations.commit_tree('8856a329dd38ca86dfb9ce5aa58a16d88cc119bd', "New LFS objects")
+ end
subject { described_class.new(project, newrev) }
describe '#objects_missing?' do
- let(:blob_object) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
-
- before do
- allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects) do |&lazy_block|
- lazy_block.call([blob_object.id])
- end
- end
+ let(:blob_object) { repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
context 'with LFS not enabled' do
it 'skips integrity check' do
- expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects)
+ expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers)
subject.objects_missing?
end
@@ -33,7 +35,7 @@ describe Gitlab::Checks::LfsIntegrity do
let(:newrev) { nil }
it 'skips integrity check' do
- expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects)
+ expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers)
expect(subject.objects_missing?).to be_falsey
end
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 49a179ba875..167876ca158 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::ContributionsCalendar do
end
let(:public_project) do
- create(:project, :public) do |project|
+ create(:project, :public, :repository) do |project|
create(:project_member, user: contributor, project: project)
end
end
@@ -40,13 +40,13 @@ describe Gitlab::ContributionsCalendar do
described_class.new(contributor, current_user)
end
- def create_event(project, day, hour = 0)
+ def create_event(project, day, hour = 0, action = Event::CREATED, target_symbol = :issue)
@targets ||= {}
- @targets[project] ||= create(:issue, project: project, author: contributor)
+ @targets[project] ||= create(target_symbol, project: project, author: contributor)
Event.create!(
project: project,
- action: Event::CREATED,
+ action: action,
target: @targets[project],
author: contributor,
created_at: DateTime.new(day.year, day.month, day.day, hour)
@@ -71,6 +71,12 @@ describe Gitlab::ContributionsCalendar do
expect(calendar(contributor).activity_dates[today]).to eq(2)
end
+ it "counts the diff notes on merge request" do
+ create_event(public_project, today, 0, Event::COMMENTED, :diff_note_on_merge_request)
+
+ expect(calendar(contributor).activity_dates[today]).to eq(1)
+ end
+
context "when events fall under different dates depending on the time zone" do
before do
create_event(public_project, today, 1)
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index a6341cd509b..67d898e787e 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -500,4 +500,33 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
end
+
+ describe '#load_all_data!' do
+ let(:full_data) { 'abcd' }
+ let(:blob) { Gitlab::Git::Blob.new(name: 'test', size: 4, data: 'abc') }
+
+ subject { blob.load_all_data!(repository) }
+
+ it 'loads missing data' do
+ expect(Gitlab::GitalyClient).to receive(:migrate)
+ .with(:git_blob_load_all_data).and_return(full_data)
+
+ subject
+
+ expect(blob.data).to eq(full_data)
+ end
+
+ context 'with a fully loaded blob' do
+ let(:blob) { Gitlab::Git::Blob.new(name: 'test', size: 4, data: full_data) }
+
+ it "doesn't perform any loading" do
+ expect(Gitlab::GitalyClient).not_to receive(:migrate)
+ .with(:git_blob_load_all_data)
+
+ subject
+
+ expect(blob.data).to eq(full_data)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb
index c9007d7d456..d0dd8c6303f 100644
--- a/spec/lib/gitlab/git/lfs_changes_spec.rb
+++ b/spec/lib/gitlab/git/lfs_changes_spec.rb
@@ -7,34 +7,36 @@ describe Gitlab::Git::LfsChanges do
subject { described_class.new(project.repository, newrev) }
- describe 'new_pointers' do
- before do
- allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects).and_yield([blob_object_id])
+ describe '#new_pointers' do
+ shared_examples 'new pointers' do
+ it 'filters new objects to find lfs pointers' do
+ expect(subject.new_pointers(not_in: []).first.id).to eq(blob_object_id)
+ end
+
+ it 'limits new_objects using object_limit' do
+ expect(subject.new_pointers(object_limit: 1)).to eq([])
+ end
end
- it 'uses rev-list to find new objects' do
- rev_list = double
- allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list)
-
- expect(rev_list).to receive(:new_objects).and_return([])
-
- subject.new_pointers
+ context 'with gitaly enabled' do
+ it_behaves_like 'new pointers'
end
- it 'filters new objects to find lfs pointers' do
- expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, [blob_object_id])
+ context 'with gitaly disabled', :skip_gitaly_mock do
+ it_behaves_like 'new pointers'
- subject.new_pointers(object_limit: 1)
- end
+ it 'uses rev-list to find new objects' do
+ rev_list = double
+ allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list)
- it 'limits new_objects using object_limit' do
- expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, [])
+ expect(rev_list).to receive(:new_objects).and_return([])
- subject.new_pointers(object_limit: 0)
+ subject.new_pointers
+ end
end
end
- describe 'all_pointers' do
+ describe '#all_pointers', :skip_gitaly_mock do
it 'uses rev-list to find all objects' do
rev_list = double
allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list)
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 25defb98b7c..52c9876cbb6 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -751,255 +751,263 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe "#log" do
- let(:commit_with_old_name) do
- Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id)
- end
- let(:commit_with_new_name) do
- Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id)
- end
- let(:rename_commit) do
- Gitlab::Git::Commit.decorate(repository, @rename_commit_id)
- end
-
- before(:context) do
- # Add new commits so that there's a renamed file in the commit history
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- @commit_with_old_name_id = new_commit_edit_old_file(repo)
- @rename_commit_id = new_commit_move_file(repo)
- @commit_with_new_name_id = new_commit_edit_new_file(repo)
- end
-
- after(:context) do
- # Erase our commits so other tests get the original repo
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
- end
-
- context "where 'follow' == true" do
- let(:options) { { ref: "master", follow: true } }
+ shared_examples 'repository log' do
+ let(:commit_with_old_name) do
+ Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id)
+ end
+ let(:commit_with_new_name) do
+ Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id)
+ end
+ let(:rename_commit) do
+ Gitlab::Git::Commit.decorate(repository, @rename_commit_id)
+ end
- context "and 'path' is a directory" do
- it "does not follow renames" do
- log_commits = repository.log(options.merge(path: "encoding"))
+ before(:context) do
+ # Add new commits so that there's a renamed file in the commit history
+ repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+ @commit_with_old_name_id = new_commit_edit_old_file(repo)
+ @rename_commit_id = new_commit_move_file(repo)
+ @commit_with_new_name_id = new_commit_edit_new_file(repo)
+ end
- aggregate_failures do
- expect(log_commits).to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_old_name)
- end
- end
+ after(:context) do
+ # Erase our commits so other tests get the original repo
+ repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+ repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
end
- context "and 'path' is a file that matches the new filename" do
- context 'without offset' do
- it "follows renames" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
+ context "where 'follow' == true" do
+ let(:options) { { ref: "master", follow: true } }
+
+ context "and 'path' is a directory" do
+ it "does not follow renames" do
+ log_commits = repository.log(options.merge(path: "encoding"))
aggregate_failures do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
- expect(log_commits).to include(commit_with_old_name)
+ expect(log_commits).not_to include(commit_with_old_name)
end
end
end
- context 'with offset=1' do
- it "follows renames and skip the latest commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
+ context "and 'path' is a file that matches the new filename" do
+ context 'without offset' do
+ it "follows renames" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
- aggregate_failures do
- expect(log_commits).not_to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).to include(commit_with_old_name)
+ aggregate_failures do
+ expect(log_commits).to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
end
end
- end
- context 'with offset=1', 'and limit=1' do
- it "follows renames, skip the latest commit and return only one commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
+ context 'with offset=1' do
+ it "follows renames and skip the latest commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
- expect(log_commits).to contain_exactly(rename_commit)
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
end
- end
- context 'with offset=1', 'and limit=2' do
- it "follows renames, skip the latest commit and return only two commits" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
+ context 'with offset=1', 'and limit=1' do
+ it "follows renames, skip the latest commit and return only one commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
- aggregate_failures do
- expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
+ expect(log_commits).to contain_exactly(rename_commit)
end
end
- end
- context 'with offset=2' do
- it "follows renames and skip the latest commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
+ context 'with offset=1', 'and limit=2' do
+ it "follows renames, skip the latest commit and return only two commits" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
- aggregate_failures do
- expect(log_commits).not_to include(commit_with_new_name)
- expect(log_commits).not_to include(rename_commit)
- expect(log_commits).to include(commit_with_old_name)
+ aggregate_failures do
+ expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
+ end
end
end
- end
- context 'with offset=2', 'and limit=1' do
- it "follows renames, skip the two latest commit and return only one commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
+ context 'with offset=2' do
+ it "follows renames and skip the latest commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
- expect(log_commits).to contain_exactly(commit_with_old_name)
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).not_to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
+ end
+
+ context 'with offset=2', 'and limit=1' do
+ it "follows renames, skip the two latest commit and return only one commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
+
+ expect(log_commits).to contain_exactly(commit_with_old_name)
+ end
+ end
+
+ context 'with offset=2', 'and limit=2' do
+ it "follows renames, skip the two latest commit and return only one commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
+
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).not_to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
end
end
- context 'with offset=2', 'and limit=2' do
- it "follows renames, skip the two latest commit and return only one commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
+ context "and 'path' is a file that matches the old filename" do
+ it "does not follow renames" do
+ log_commits = repository.log(options.merge(path: "CHANGELOG"))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
- expect(log_commits).not_to include(rename_commit)
+ expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
- end
- context "and 'path' is a file that matches the old filename" do
- it "does not follow renames" do
- log_commits = repository.log(options.merge(path: "CHANGELOG"))
+ context "unknown ref" do
+ it "returns an empty array" do
+ log_commits = repository.log(options.merge(ref: 'unknown'))
- aggregate_failures do
- expect(log_commits).not_to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).to include(commit_with_old_name)
+ expect(log_commits).to eq([])
end
end
end
- context "unknown ref" do
- it "returns an empty array" do
- log_commits = repository.log(options.merge(ref: 'unknown'))
-
- expect(log_commits).to eq([])
- end
- end
- end
+ context "where 'follow' == false" do
+ options = { follow: false }
- context "where 'follow' == false" do
- options = { follow: false }
+ context "and 'path' is a directory" do
+ let(:log_commits) do
+ repository.log(options.merge(path: "encoding"))
+ end
- context "and 'path' is a directory" do
- let(:log_commits) do
- repository.log(options.merge(path: "encoding"))
+ it "does not follow renames" do
+ expect(log_commits).to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).not_to include(commit_with_old_name)
+ end
end
- it "does not follow renames" do
- expect(log_commits).to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_old_name)
- end
- end
+ context "and 'path' is a file that matches the new filename" do
+ let(:log_commits) do
+ repository.log(options.merge(path: "encoding/CHANGELOG"))
+ end
- context "and 'path' is a file that matches the new filename" do
- let(:log_commits) do
- repository.log(options.merge(path: "encoding/CHANGELOG"))
+ it "does not follow renames" do
+ expect(log_commits).to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).not_to include(commit_with_old_name)
+ end
end
- it "does not follow renames" do
- expect(log_commits).to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_old_name)
- end
- end
+ context "and 'path' is a file that matches the old filename" do
+ let(:log_commits) do
+ repository.log(options.merge(path: "CHANGELOG"))
+ end
- context "and 'path' is a file that matches the old filename" do
- let(:log_commits) do
- repository.log(options.merge(path: "CHANGELOG"))
+ it "does not follow renames" do
+ expect(log_commits).to include(commit_with_old_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).not_to include(commit_with_new_name)
+ end
end
- it "does not follow renames" do
- expect(log_commits).to include(commit_with_old_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_new_name)
+ context "and 'path' includes a directory that used to be a file" do
+ let(:log_commits) do
+ repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt"))
+ end
+
+ it "returns a list of commits" do
+ expect(log_commits.size).to eq(1)
+ end
end
end
- context "and 'path' includes a directory that used to be a file" do
- let(:log_commits) do
- repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt"))
- end
+ context "where provides 'after' timestamp" do
+ options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
- it "returns a list of commits" do
- expect(log_commits.size).to eq(1)
+ it "should returns commits on or after that timestamp" do
+ commits = repository.log(options)
+
+ expect(commits.size).to be > 0
+ expect(commits).to satisfy do |commits|
+ commits.all? { |commit| commit.committed_date >= options[:after] }
+ end
end
end
- end
- context "where provides 'after' timestamp" do
- options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
+ context "where provides 'before' timestamp" do
+ options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
- it "should returns commits on or after that timestamp" do
- commits = repository.log(options)
+ it "should returns commits on or before that timestamp" do
+ commits = repository.log(options)
- expect(commits.size).to be > 0
- expect(commits).to satisfy do |commits|
- commits.all? { |commit| commit.committed_date >= options[:after] }
+ expect(commits.size).to be > 0
+ expect(commits).to satisfy do |commits|
+ commits.all? { |commit| commit.committed_date <= options[:before] }
+ end
end
end
- end
- context "where provides 'before' timestamp" do
- options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
+ context 'when multiple paths are provided' do
+ let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
- it "should returns commits on or before that timestamp" do
- commits = repository.log(options)
-
- expect(commits.size).to be > 0
- expect(commits).to satisfy do |commits|
- commits.all? { |commit| commit.committed_date <= options[:before] }
+ def commit_files(commit)
+ commit.rugged_diff_from_parent.deltas.flat_map do |delta|
+ [delta.old_file[:path], delta.new_file[:path]].uniq.compact
+ end
end
- end
- end
- context 'when multiple paths are provided' do
- let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
+ it 'only returns commits matching at least one path' do
+ commits = repository.log(options)
- def commit_files(commit)
- commit.rugged_diff_from_parent.deltas.flat_map do |delta|
- [delta.old_file[:path], delta.new_file[:path]].uniq.compact
+ expect(commits.size).to be > 0
+ expect(commits).to satisfy do |commits|
+ commits.none? { |commit| (commit_files(commit) & options[:path]).empty? }
+ end
end
end
- it 'only returns commits matching at least one path' do
- commits = repository.log(options)
+ context 'limit validation' do
+ where(:limit) do
+ [0, nil, '', 'foo']
+ end
- expect(commits.size).to be > 0
- expect(commits).to satisfy do |commits|
- commits.none? { |commit| (commit_files(commit) & options[:path]).empty? }
+ with_them do
+ it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
end
end
- end
- context 'limit validation' do
- where(:limit) do
- [0, nil, '', 'foo']
- end
+ context 'with all' do
+ it 'returns a list of commits' do
+ commits = repository.log({ all: true, limit: 50 })
- with_them do
- it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
+ expect(commits.size).to eq(37)
+ end
end
end
- context 'with all' do
- let(:options) { { all: true, limit: 50 } }
-
- it 'returns a list of commits' do
- commits = repository.log(options)
+ context 'when Gitaly find_commits feature is enabled' do
+ it_behaves_like 'repository log'
+ end
- expect(commits.size).to eq(37)
- end
+ context 'when Gitaly find_commits feature is disabled', :disable_gitaly do
+ it_behaves_like 'repository log'
end
end
@@ -1136,14 +1144,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(repository.count_commits(options)).to eq(10)
end
end
- end
-
- context 'when Gitaly count_commits feature is enabled' do
- it_behaves_like 'extended commit counting'
- end
-
- context 'when Gitaly count_commits feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'extended commit counting'
context "with all" do
it "returns the number of commits in the whole repository" do
@@ -1155,10 +1155,18 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'without all or ref being specified' do
it "raises an ArgumentError" do
- expect { repository.count_commits({}) }.to raise_error(ArgumentError, "Please specify a valid ref or set the 'all' attribute to true")
+ expect { repository.count_commits({}) }.to raise_error(ArgumentError)
end
end
end
+
+ context 'when Gitaly count_commits feature is enabled' do
+ it_behaves_like 'extended commit counting'
+ end
+
+ context 'when Gitaly count_commits feature is disabled', :disable_gitaly do
+ it_behaves_like 'extended commit counting'
+ end
end
describe '#autocrlf' do
diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
new file mode 100644
index 00000000000..a2770ef2fe4
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::BlobService do
+ let(:project) { create(:project, :repository) }
+ let(:storage_name) { project.repository_storage }
+ let(:relative_path) { project.disk_path + '.git' }
+ let(:repository) { project.repository }
+ let(:client) { described_class.new(repository) }
+
+ describe '#get_new_lfs_pointers' do
+ let(:revision) { 'master' }
+ let(:limit) { 5 }
+ let(:not_in) { ['branch-a', 'branch-b'] }
+ let(:expected_params) do
+ { revision: revision, limit: limit, not_in_refs: not_in, not_in_all: false }
+ end
+
+ subject { client.get_new_lfs_pointers(revision, limit, not_in) }
+
+ it 'sends a get_new_lfs_pointers message' do
+ expect_any_instance_of(Gitaly::BlobService::Stub)
+ .to receive(:get_new_lfs_pointers)
+ .with(gitaly_request_with_params(expected_params), kind_of(Hash))
+ .and_return([])
+
+ subject
+ end
+
+ context 'with not_in = :all' do
+ let(:not_in) { :all }
+ let(:expected_params) do
+ { revision: revision, limit: limit, not_in_refs: [], not_in_all: true }
+ end
+
+ it 'sends the correct message' do
+ expect_any_instance_of(Gitaly::BlobService::Stub)
+ .to receive(:get_new_lfs_pointers)
+ .with(gitaly_request_with_params(expected_params), kind_of(Hash))
+ .and_return([])
+
+ subject
+ end
+ end
+ end
+
+ describe '#get_all_lfs_pointers' do
+ let(:revision) { 'master' }
+
+ subject { client.get_all_lfs_pointers(revision) }
+
+ it 'sends a get_all_lfs_pointers message' do
+ expect_any_instance_of(Gitaly::BlobService::Stub)
+ .to receive(:get_all_lfs_pointers)
+ .with(gitaly_request_with_params(revision: revision), kind_of(Hash))
+ .and_return([])
+
+ subject
+ end
+ end
+end
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
index d715f9bd641..37b1298b962 100644
--- a/spec/lib/gitlab/string_regex_marker_spec.rb
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -2,17 +2,36 @@ require 'spec_helper'
describe Gitlab::StringRegexMarker do
describe '#mark' do
- let(:raw) { %{"name": "AFNetworking"} }
- let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
- subject do
- described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:|
- %{<a href="#">#{text}</a>}
+ context 'with a single occurrence' do
+ let(:raw) { %{"name": "AFNetworking"} }
+ let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
+
+ subject do
+ described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:|
+ %{<a href="#">#{text}</a>}
+ end
+ end
+
+ it 'marks the match' do
+ expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>})
+ expect(subject).to be_html_safe
end
end
- it 'marks the inline diffs' do
- expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>})
- expect(subject).to be_html_safe
+ context 'with multiple occurrences' do
+ let(:raw) { %{a <b> <c> d} }
+ let(:rich) { %{a &lt;b&gt; &lt;c&gt; d}.html_safe }
+
+ subject do
+ described_class.new(raw, rich).mark(/<[a-z]>/) do |text, left:, right:|
+ %{<strong>#{text}</strong>}
+ end
+ end
+
+ it 'marks the matches' do
+ expect(subject).to eq(%{a <strong>&lt;b&gt;</strong> <strong>&lt;c&gt;</strong> d})
+ expect(subject).to be_html_safe
+ end
end
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index d1569e5d650..6614e8cea43 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -163,6 +163,42 @@ describe API::Issues do
expect(first_issue['id']).to eq(issue.id)
end
+ context 'filtering before a specific date' do
+ let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) }
+
+ it 'returns issues created before a specific date' do
+ get api('/issues?created_before=2000-01-02T00:00:00.060Z', user)
+
+ expect(json_response.size).to eq(1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
+ it 'returns issues updated before a specific date' do
+ get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user)
+
+ expect(json_response.size).to eq(1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+ end
+
+ context 'filtering after a specific date' do
+ let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) }
+
+ it 'returns issues created after a specific date' do
+ get api("/issues?created_after=#{issue2.created_at}", user)
+
+ expect(json_response.size).to eq(1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
+ it 'returns issues updated after a specific date' do
+ get api("/issues?updated_after=#{issue2.updated_at}", user)
+
+ expect(json_response.size).to eq(1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+ end
+
it 'returns an array of labeled issues' do
get api("/issues", user), labels: label.title
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e8eb01f6c32..484322752c0 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -172,6 +172,42 @@ describe API::MergeRequests do
end
end
+ it 'returns merge requests created before a specific date' do
+ merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', created_at: Date.new(2000, 1, 1))
+
+ get api('/merge_requests?created_before=2000-01-02T00:00:00.060Z', user)
+
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request2.id)
+ end
+
+ it 'returns merge requests created after a specific date' do
+ merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', created_at: 1.week.from_now)
+
+ get api("/merge_requests?created_after=#{merge_request2.created_at}", user)
+
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request2.id)
+ end
+
+ it 'returns merge requests updated before a specific date' do
+ merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', updated_at: Date.new(2000, 1, 1))
+
+ get api('/merge_requests?updated_before=2000-01-02T00:00:00.060Z', user)
+
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request2.id)
+ end
+
+ it 'returns merge requests updated after a specific date' do
+ merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', updated_at: 1.week.from_now)
+
+ get api("/merge_requests?updated_after=#{merge_request2.updated_at}", user)
+
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request2.id)
+ end
+
context 'search params' do
before do
merge_request.update(title: 'Search title', description: 'Search description')
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 5d226f34d2d..44a83c436cb 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -28,6 +28,7 @@ describe MergeRequests::CreateService do
it 'creates an MR' do
expect(merge_request).to be_valid
+ expect(merge_request.work_in_progress?).to be(false)
expect(merge_request.title).to eq('Awesome merge_request')
expect(merge_request.assignee).to be_nil
expect(merge_request.merge_params['force_remove_source_branch']).to eq('1')
@@ -62,6 +63,40 @@ describe MergeRequests::CreateService do
expect(Event.where(attributes).count).to eq(1)
end
+ describe 'when marked with /wip' do
+ context 'in title and in description' do
+ let(:opts) do
+ {
+ title: 'WIP: Awesome merge_request',
+ description: "well this is not done yet\n/wip",
+ source_branch: 'feature',
+ target_branch: 'master',
+ assignee: assignee
+ }
+ end
+
+ it 'sets MR to WIP' do
+ expect(merge_request.work_in_progress?).to be(true)
+ end
+ end
+
+ context 'in description only' do
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ description: "well this is not done yet\n/wip",
+ source_branch: 'feature',
+ target_branch: 'master',
+ assignee: assignee
+ }
+ end
+
+ it 'sets MR to WIP' do
+ expect(merge_request.work_in_progress?).to be(true)
+ end
+ end
+ end
+
context 'when merge request is assigned to someone' do
let(:opts) do
{
diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb
index 38d11992dc2..8eeaa37d3c5 100644
--- a/spec/support/bare_repo_operations.rb
+++ b/spec/support/bare_repo_operations.rb
@@ -11,6 +11,14 @@ class BareRepoOperations
@path_to_repo = path_to_repo
end
+ def commit_tree(tree_id, msg, parent: EMPTY_TREE_ID)
+ commit_tree_args = ['commit-tree', tree_id, '-m', msg]
+ commit_tree_args += ['-p', parent] unless parent == EMPTY_TREE_ID
+ commit_id = execute(commit_tree_args)
+
+ commit_id[0]
+ end
+
# Based on https://stackoverflow.com/a/25556917/1856239
def commit_file(file, dst_path, branch = 'master')
head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || EMPTY_TREE_ID
@@ -26,11 +34,9 @@ class BareRepoOperations
tree_id = execute(['write-tree'])
- commit_tree_args = ['commit-tree', tree_id[0], '-m', "Add #{dst_path}"]
- commit_tree_args += ['-p', head_id] unless head_id == EMPTY_TREE_ID
- commit_id = execute(commit_tree_args)
+ commit_id = commit_tree(tree_id[0], "Add #{dst_path}", parent: head_id)
- execute(['update-ref', "refs/heads/#{branch}", commit_id[0]])
+ execute(['update-ref', "refs/heads/#{branch}", commit_id])
end
private