Commit 313da6d3 authored by Dennis Tang's avatar Dennis Tang

Merge remote-tracking branch 'origin/master' into 43446-new-cluster-page-tabs

parents a5217676 9c296194
......@@ -342,7 +342,7 @@ group :development, :test do
gem 'capybara', '~> 2.15'
gem 'capybara-screenshot', '~> 1.0.0'
gem 'selenium-webdriver', '~> 3.5'
gem 'selenium-webdriver', '~> 3.12'
gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4'
......@@ -384,7 +384,7 @@ group :test do
gem 'test-prof', '~> 0.2.5'
end
gem 'octokit', '~> 4.8'
gem 'octokit', '~> 4.9'
gem 'mail_room', '~> 0.9.1'
......
......@@ -115,7 +115,7 @@ GEM
mime-types (>= 1.16)
cause (0.1)
charlock_holmes (0.7.6)
childprocess (0.7.0)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
chronic (0.10.2)
chronic_duration (0.10.6)
......@@ -517,7 +517,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
octokit (4.8.0)
octokit (4.9.0)
sawyer (~> 0.8.0, >= 0.5.3)
omniauth (1.8.1)
hashie (>= 3.4.6, < 3.6.0)
......@@ -828,9 +828,9 @@ GEM
activesupport (>= 3.1)
select2-rails (3.5.9.3)
thor (~> 0.14)
selenium-webdriver (3.5.0)
selenium-webdriver (3.12.0)
childprocess (~> 0.5)
rubyzip (~> 1.0)
rubyzip (~> 1.2)
sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
......@@ -1084,7 +1084,7 @@ DEPENDENCIES
net-ssh (~> 4.2.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.8)
octokit (~> 4.9)
omniauth (~> 1.8)
omniauth-auth0 (~> 2.0.0)
omniauth-authentiq (~> 0.3.3)
......@@ -1154,7 +1154,7 @@ DEPENDENCIES
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5)
selenium-webdriver (~> 3.12)
sentry-raven (~> 2.7)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
......
......@@ -54,7 +54,7 @@ export default {
placement: 'top',
content: sprintf(
__(`
The character highligher helps you keep the subject line to %{titleLength} characters
The character highlighter helps you keep the subject line to %{titleLength} characters
and wrap the body at %{bodyLength} so they are readable in git.
`),
{ titleLength: MAX_TITLE_LENGTH, bodyLength: MAX_BODY_LENGTH },
......
......@@ -25,14 +25,22 @@ module SearchHelper
return unless collection.count > 0
from = collection.offset_value + 1
to = collection.offset_value + collection.length
to = collection.offset_value + collection.count
count = collection.total_count
"Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
end
def find_project_for_result_blob(result)
@project
end
def parse_search_result(result)
Gitlab::ProjectSearchResults.parse_search_result(result)
result
end
def search_blob_title(project, filename)
filename
end
private
......
......@@ -7,12 +7,17 @@ module Ci
include Presentable
include Gitlab::OptimisticLocking
include Gitlab::Utils::StrongMemoize
include AtomicInternalId
belongs_to :project, inverse_of: :pipelines
belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
has_internal_id :iid, scope: :project, presence: false, init: ->(s) do
s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines&.count
end
has_many :stages
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline
......@@ -531,6 +536,7 @@ module Ci
def predefined_variables
Gitlab::Ci::Variables::Collection.new
.append(key: 'CI_PIPELINE_IID', value: iid.to_s)
.append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message)
......
......@@ -11,12 +11,12 @@ module Clusters
attr_encrypted :password,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
attr_encrypted :token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
before_validation :enforce_namespace_to_lower_case
......
......@@ -11,7 +11,7 @@ module Clusters
attr_encrypted :access_token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
validates :gcp_project_id,
......
......@@ -25,9 +25,13 @@ module AtomicInternalId
extend ActiveSupport::Concern
module ClassMethods
def has_internal_id(column, scope:, init:) # rubocop:disable Naming/PredicateName
before_validation(on: :create) do
def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName
before_validation :"ensure_#{scope}_#{column}!", on: :create
validates column, presence: presence
define_method("ensure_#{scope}_#{column}!") do
scope_value = association(scope).reader
if read_attribute(column).blank? && scope_value
scope_attrs = { scope_value.class.table_name.singularize.to_sym => scope_value }
usage = self.class.table_name.to_sym
......@@ -35,13 +39,9 @@ module AtomicInternalId
new_iid = InternalId.generate_next(self, scope_attrs, usage, init)
write_attribute(column, new_iid)
end
end
validates column, presence: true, numericality: true
read_attribute(column)
end
end
end
def to_param
iid.to_s
end
end
module IidRoutes
##
# This automagically enforces all related routes to use `iid` instead of `id`
# If you want to use `iid` for some routes and `id` for other routes, this module should not to be included,
# instead you should define `iid` or `id` explictly at each route generators. e.g. pipeline_path(project.id, pipeline.iid)
def to_param
iid.to_s
end
end
class Deployment < ActiveRecord::Base
include AtomicInternalId
include IidRoutes
belongs_to :project, required: true
belongs_to :environment, required: true
......
......@@ -14,7 +14,7 @@ class InternalId < ActiveRecord::Base
belongs_to :project
belongs_to :namespace
enum usage: { issues: 0, merge_requests: 1, deployments: 2, milestones: 3, epics: 4 }
enum usage: { issues: 0, merge_requests: 1, deployments: 2, milestones: 3, epics: 4, ci_pipelines: 5 }
validates :usage, presence: true
......
......@@ -2,6 +2,7 @@ require 'carrierwave/orm/activerecord'
class Issue < ActiveRecord::Base
include AtomicInternalId
include IidRoutes
include Issuable
include Noteable
include Referable
......
class MergeRequest < ActiveRecord::Base
include AtomicInternalId
include IidRoutes
include Issuable
include Noteable
include Referable
......
......@@ -9,6 +9,7 @@ class Milestone < ActiveRecord::Base
include CacheMarkdownField
include AtomicInternalId
include IidRoutes
include Sortable
include Referable
include StripAttribute
......
......@@ -140,10 +140,6 @@ class ProjectWiki
[title, title_array.join("/")]
end
def search_files(query)
repository.search_files_by_content(query, default_branch)
end
def repository
@repository ||= Repository.new(full_path, @project, disk_path: disk_path, is_wiki: true)
end
......
- file_name, blob = blob
.blob-result
.file-holder
.js-file-title.file-title
- ref = @search_results.repository_ref
- blob_link = project_blob_path(@project, tree_join(ref, file_name))
= link_to blob_link do
%i.fa.fa-file
%strong
= file_name
- if blob
.file-content.code.term
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link
- project = find_project_for_result_blob(blob)
- file_name, blob = parse_search_result(blob)
- blob_link = project_blob_path(project, tree_join(blob.ref, file_name))
= render partial: 'search/results/blob_data', locals: { blob: blob, project: project, file_name: file_name, blob_link: blob_link }
.blob-result
.file-holder
.js-file-title.file-title
= link_to blob_link do
%i.fa.fa-file
= search_blob_title(project, file_name)
- if blob.data
.file-content.code.term
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline
- wiki_blob = parse_search_result(wiki_blob)
.blob-result
.file-holder
.js-file-title.file-title
= link_to project_wiki_path(@project, wiki_blob.basename) do
%i.fa.fa-file
%strong
= wiki_blob.basename
.file-content.code.term
= render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline
- project = find_project_for_result_blob(wiki_blob)
- file_name, wiki_blob = parse_search_result(wiki_blob)
- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: file_name, blob_link: wiki_blob_link }
---
title: Update selenium-webdriver to 3.12.0
merge_request: 19351
author: Takuya Noguchi
type: other
---
title: Include username in output when testing SSH to GitLab
merge_request: 19358
author:
type: other
---
title: Added ability to search by wiki titles
merge_request: 19112
author:
type: added
---
title: Add per-project pipeline id
merge_request: 18558
author:
type: added
---
title: Rails5 fix arel from
merge_request: 19340
author: Jasper Maes
type: fixed
---
title: Fix attr_encryption key settings
merge_request:
author:
type: fixed
---
title: Fix N+1 with source_projects in merge requests API
merge_request:
author:
type: performance
......@@ -85,17 +85,24 @@ class Settings < Settingslogic
File.expand_path(path, Rails.root)
end
# Returns a 256-bit key for attr_encrypted
def attr_encrypted_db_key_base
# Ruby 2.4+ requires passing in the exact required length for OpenSSL keys
# (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1).
# Previous versions quietly truncated the input.
#
# The default mode for the attr_encrypted gem is to use a 256-bit key.
# We truncate the 128-byte string to 32 bytes.
# Ruby 2.4+ requires passing in the exact required length for OpenSSL keys
# (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1).
# Previous versions quietly truncated the input.
#
# Use this when using :per_attribute_iv mode for attr_encrypted.
# We have to truncate the string to 32 bytes for a 256-bit cipher.
def attr_encrypted_db_key_base_truncated
Gitlab::Application.secrets.db_key_base[0..31]
end
# This should be used for :per_attribute_salt_and_iv mode. There is no
# need to truncate the key because the encryptor will use the salt to
# generate a hash of the password:
# https://github.com/attr-encrypted/encryptor/blob/c3a62c4a9e74686dd95e0548f9dc2a361fdc95d1/lib/encryptor.rb#L77
def attr_encrypted_db_key_base
Gitlab::Application.secrets.db_key_base
end
private
def base_url(config)
......
class AddPipelineIidToCiPipelines < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
add_column :ci_pipelines, :iid, :integer
end
def down
remove_column :ci_pipelines, :iid, :integer
end
end
class AddIndexConstraintsToPipelineIid < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_pipelines, [:project_id, :iid], unique: true, where: 'iid IS NOT NULL'
end
def down
remove_concurrent_index :ci_pipelines, [:project_id, :iid]
end
end
......@@ -48,7 +48,7 @@ class MigrateKubernetesServiceToNewClustersArchitectures < ActiveRecord::Migrati
attr_encrypted :token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
end
......
......@@ -451,10 +451,12 @@ ActiveRecord::Schema.define(version: 20180529093006) do
t.integer "config_source"
t.boolean "protected"
t.integer "failure_reason"
t.integer "iid"
end
add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree
add_index "ci_pipelines", ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree
add_index "ci_pipelines", ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, where: "(iid IS NOT NULL)", using: :btree
add_index "ci_pipelines", ["project_id", "ref", "status", "id"], name: "index_ci_pipelines_on_project_id_and_ref_and_status_and_id", using: :btree
add_index "ci_pipelines", ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree
add_index "ci_pipelines", ["project_id"], name: "index_ci_pipelines_on_project_id", using: :btree
......
......@@ -83,12 +83,12 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
host that GitLab runs. For example, an entry would look like this:
```
*.example.io. 1800 IN A 1.1.1.1
*.example.io. 1800 IN A 192.0.2.1
*.example.io. 1800 IN AAAA 2001::1
```
where `example.io` is the domain under which GitLab Pages will be served
and `1.1.1.1` is the IPv4 address of your GitLab instance and `2001::1` is the
and `192.0.2.1` is the IPv4 address of your GitLab instance and `2001::1` is the
IPv6 address. If you don't have IPv6, you can omit the AAAA record.
> **Note:**
......@@ -193,13 +193,13 @@ world. Custom domains are supported, but no TLS.
```shell
pages_external_url "http://example.io"
nginx['listen_addresses'] = ['1.1.1.1']
nginx['listen_addresses'] = ['192.0.2.1']
pages_nginx['enable'] = false
gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80']
gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
```
where `1.1.1.1` is the primary IP address that GitLab is listening to and
`1.1.1.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
where `192.0.2.1` is the primary IP address that GitLab is listening to and
`192.0.2.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure]
......@@ -228,16 +228,16 @@ world. Custom domains and TLS are supported.
```shell
pages_external_url "https://example.io"
nginx['listen_addresses'] = ['1.1.1.1']
nginx['listen_addresses'] = ['192.0.2.1']
pages_nginx['enable'] = false
gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80']
gitlab_pages['external_https'] = ['1.1.1.2:443', '[2001::2]:443']
gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
gitlab_pages['external_https'] = ['192.0.2.2:443', '[2001::2]:443']
```
where `1.1.1.1` is the primary IP address that GitLab is listening to and
`1.1.1.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
where `192.0.2.1` is the primary IP address that GitLab is listening to and
`192.0.2.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure]
......
......@@ -67,11 +67,11 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
host that GitLab runs. For example, an entry would look like this:
```
*.example.io. 1800 IN A 1.1.1.1
*.example.io. 1800 IN A 192.0.2.1
```
where `example.io` is the domain under which GitLab Pages will be served
and `1.1.1.1` is the IP address of your GitLab instance.
and `192.0.2.1` is the IP address of your GitLab instance.
> **Note:**
You should not use the GitLab domain to serve user pages. For more information
......@@ -253,7 +253,7 @@ world. Custom domains are supported, but no TLS.
port: 80
https: false
external_http: 1.1.1.2:80
external_http: 192.0.2.2:80
```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
......@@ -263,7 +263,7 @@ world. Custom domains are supported, but no TLS.
```
gitlab_pages_enabled=true
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80"
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80"
```
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
......@@ -274,7 +274,7 @@ world. Custom domains are supported, but no TLS.
```
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
`0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab
listens to.
1. Restart NGINX
1. [Restart GitLab][restart]
......@@ -320,8 +320,8 @@ world. Custom domains and TLS are supported.
port: 443
https: true
external_http: 1.1.1.2:80
external_https: 1.1.1.2:443
external_http: 192.0.2.2:80
external_https: 192.0.2.2:443
```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
......@@ -333,7 +333,7 @@ world. Custom domains and TLS are supported.
```
gitlab_pages_enabled=true
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80 -listen-https 1.1.1.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80 -listen-https 192.0.2.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
```
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
......@@ -344,7 +344,7 @@ world. Custom domains and TLS are supported.
```
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
`0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab
listens to.
1. Restart NGINX
1. [Restart GitLab][restart]
......
......@@ -582,6 +582,7 @@ Parameters:
"changes_count": "1",
"should_remove_source_branch": true,
"force_remove_source_branch": false,
"squash": false,
"web_url": "http://example.com/example/example/merge_requests/1",
"discussion_locked": false,
"time_stats": {
......
......@@ -72,6 +72,7 @@ future GitLab releases.**
| **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job |
| **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
| **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
| **CI_PIPELINE_IID** | 11.0 | all | The unique id of the current pipeline scoped to project |
| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
| **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run |
......@@ -352,6 +353,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI_PROJECT_URL=https://example.com/gitlab-examples/ci-debug-trace
++ export CI_PIPELINE_ID=52666
++ CI_PIPELINE_ID=52666
++ export CI_PIPELINE_IID=123
++ CI_PIPELINE_IID=123
++ export CI_RUNNER_ID=1337
++ CI_RUNNER_ID=1337
++ export CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com
......@@ -439,6 +442,7 @@ export CI_JOB_MANUAL="true"
export CI_JOB_TRIGGERED="true"
export CI_JOB_TOKEN="abcde-1234ABCD5678ef"
export CI_PIPELINE_ID="1000"
export CI_PIPELINE_IID="10"
export CI_PROJECT_ID="34"
export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
export CI_PROJECT_NAME="gitlab-ce"
......
......@@ -144,7 +144,7 @@ helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
or passing them on the command line:
```bash
helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
helm install --name gitlab --set baseDomain=gitlab.io,baseIP=192.0.2.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
```
## Updating GitLab using the Helm Chart
......
......@@ -38,7 +38,7 @@ module API
merge_requests = MergeRequestsFinder.new(current_user, args).execute
.reorder(args[:order_by] => args[:sort])
merge_requests = paginate(merge_requests)
.preload(:target_project)
.preload(:source_project, :target_project)
return merge_requests if args[:view] == 'simple'
......
......@@ -34,9 +34,7 @@ module API
def process_results(results)
case params[:scope]
when 'wiki_blobs'
paginate(results).map { |blob| Gitlab::ProjectSearchResults.parse_search_result(blob, user_project) }
when 'blobs'
when 'blobs', 'wiki_blobs'
paginate(results).map { |blob| blob[1] }
else
paginate(results)
......
......@@ -8,6 +8,9 @@ module Gitlab
PopulateError = Class.new(StandardError)
def perform!
# Allocate next IID. This operation must be outside of transactions of pipeline creations.
pipeline.ensure_project_iid!
##
# Populate pipeline with block argument of CreatePipelineService#execute.
#
......
......@@ -143,8 +143,13 @@ module Gitlab
.order(arel_table[column_sym])
).as('row_id')
count = arel_table.from(arel_table.alias)
.project('COUNT(*)')
arel_from = if Gitlab.rails5?
arel_table.from.from(arel_table.alias)
else
arel_table.from(arel_table.alias)
end
count = arel_from.project('COUNT(*)')
.where(arel_table[partition_column].eq(arel_table.alias[partition_column]))
.as('ct')
......
......@@ -32,17 +32,13 @@ module Gitlab
end
def find_by_filename(query, except: [])
filenames = repository.search_files_by_name(query, ref).first(BATCH_SIZE)
filenames.delete_if { |filename| except.include?(filename) } unless except.empty?
filenames = search_filenames(query, except)
blob_refs = filenames.map { |filename| [ref, filename] }
blobs = Gitlab::Git::Blob.batch(repository, blob_refs, blob_size_limit: 1024)
blobs.map do |blob|
blobs(filenames).map do |blob|
Gitlab::SearchResults::FoundBlob.new(
id: blob.id,
filename: blob.path,
basename: File.basename(blob.path),
basename: File.basename(blob.path, File.extname(blob.path)),
ref: ref,
startline: 1,
data: blob.data,
......@@ -50,5 +46,21 @@ module Gitlab
)
end
end
def search_filenames(query, except)
filenames = repository.search_files_by_name(query, ref).first(BATCH_SIZE)
filenames.delete_if { |filename| except.include?(filename) } unless except.empty?
filenames
end
def blob_refs(filenames)
filenames.map { |filename| [ref, filename] }
end
def blobs(filenames)
Gitlab::Git::Blob.batch(repository, blob_refs(filenames), blob_size_limit: 1024)
end
end
end
......@@ -106,7 +106,8 @@ module Gitlab
project_wiki = ProjectWiki.new(project)
unless project_wiki.empty?
project_wiki.search_files(query)
ref = repository_ref || project.wiki.default_branch
Gitlab::WikiFileFinder.new(project, ref).find(query)
else
[]
end
......
module Gitlab
module SlashCommands
class Command < BaseCommand
COMMANDS = [
Gitlab::SlashCommands::IssueShow,
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove,
Gitlab::SlashCommands::Deploy
].freeze
def self.commands
[
Gitlab::SlashCommands::IssueShow,
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove,
Gitlab::SlashCommands::Deploy
]
end
def execute
command, match = match_command
......@@ -37,7 +39,7 @@ module Gitlab
private
def available_commands
COMMANDS.select do |klass|
self.class.commands.keep_if do |klass|
klass.available?(project)
end
end
......
module Gitlab
class WikiFileFinder < FileFinder
attr_reader :repository
def initialize(project, ref)
@project = project
@ref = ref
@repository = project.wiki.repository
end
private
def search_filenames(query, except)
safe_query = Regexp.escape(query.tr(' ', '-'))
safe_query = Regexp.new(safe_query, Regexp::IGNORECASE)
filenames = repository.ls_files(ref)
filenames.delete_if { |filename| except.include?(filename) } unless except.empty?
filenames.grep(safe_query).first(BATCH_SIZE)
end
end
end
......@@ -42,6 +42,10 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
it 'correctly assigns user' do
expect(pipeline.builds).to all(have_attributes(user: user))
end
it 'has pipeline iid' do
expect(pipeline.iid).to be > 0
end
end
context 'when pipeline is empty' do
......@@ -68,6 +72,10 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
expect(pipeline.errors.to_a)
.to include 'No stages / jobs for this pipeline.'
end
it 'wastes pipeline iid' do
expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0
end
end
context 'when pipeline has validation errors' do
......@@ -87,6 +95,10 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
expect(pipeline.errors.to_a)
.to include 'Failed to build the pipeline!'
end
it 'wastes pipeline iid' do
expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0
end
end
context 'when there is a seed blocks present' do
......@@ -111,6 +123,12 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
expect(pipeline.variables.first.key).to eq 'VAR'
expect(pipeline.variables.first.value).to eq '123'
end
it 'has pipeline iid' do
step.perform!
expect(pipeline.iid).to be > 0
end
end
context 'when seeds block tries to persist some resources' do
......@@ -121,6 +139,12 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
it 'raises exception' do
expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved)
end
it 'wastes pipeline iid' do
expect { step.perform! }.to raise_error
expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0
end
end
end
......@@ -132,22 +156,39 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
end
context 'when using only/except build policies' do
let(:config) do
{ rspec: { script: 'rspec', stage: 'test', only: ['master'] },
prod: { script: 'cap prod', stage: 'deploy', only: ['tags'] } }
end
context 'when variables policy is specified' do
shared_examples_for 'a correct pipeline' do
it 'populates pipeline according to used policies' do
step.perform!
let(:pipeline) do
build(:ci_pipeline, ref: 'master', config: config)
expect(pipeline.stages.size).to eq 1
expect(pipeline.stages.first.builds.size).to eq 1
expect(pipeline.stages.first.builds.first.name).to eq 'rspec'
end
end
it 'populates pipeline according to used policies' do
step.perform!
context 'when using only/except build policies' do
let(:config) do
{ rspec: { script: 'rspec', stage: 'test', only: ['master'] },
prod: { script: 'cap prod', stage: 'deploy', only: ['tags'] } }
end
let(:pipeline) do
build(:ci_pipeline, ref: 'master', config: config)
end
expect(pipeline.stages.size).to eq 1
expect(pipeline.stages.first.builds.size).to eq 1
expect(pipeline.stages.first.builds.first.name).to eq 'rspec'
it_behaves_like 'a correct pipeline'
context 'when variables expression is specified' do
context 'when pipeline iid is the subject' do
let(:config) do
{ rspec: { script: 'rspec', only: { variables: ["$CI_PIPELINE_IID == '1'"] } },
prod: { script: 'cap prod', only: { variables: ["$CI_PIPELINE_IID == '1000'"] } } }
end
it_behaves_like 'a correct pipeline'
end
end
end
end
end
......@@ -3,7 +3,12 @@ require 'spec_helper'
describe Gitlab::CycleAnalytics::UsageData do
describe '#to_json' do
before do
Timecop.freeze do
# Since git commits only have second precision, round up to the
# nearest second to ensure we have accurate median and standard
# deviation calculations.
current_time = Time.at(Time.now.to_i)
Timecop.freeze(current_time) do
user = create(:user, :admin)
projects = create_list(:project, 2, :repository)
......@@ -37,13 +42,7 @@ describe Gitlab::CycleAnalytics::UsageData do
expected_values.each_pair do |op, value|
expect(stage_values).to have_key(op)
if op == :missing
expect(stage_values[op]).to eq(value)
else
# delta is used because of git timings that Timecop does not stub
expect(stage_values[op].to_i).to be_within(5).of(value.to_i)
end
expect(stage_values[op]).to eq(value)
end
end
end
......@@ -58,8 +57,8 @@ describe Gitlab::CycleAnalytics::UsageData do
missing: 0
},
plan: {
average: 2,
sd: 2,
average: 1,
sd: 0,
missing: 0
},
code: {
......
......@@ -3,27 +3,11 @@ require 'spec_helper'
describe Gitlab::FileFinder do
describe '#find' do
let(:project) { create(:project, :public, :repository) }
let(:finder) { described_class.new(project, project.default_branch) }
it 'finds by name' do
results = finder.find('files')
filename, blob = results.find { |_, blob| blob.filename == 'files/images/wm.svg' }
expect(filename).to eq('files/images/wm.svg')
expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
expect(blob.ref).to eq(finder.ref)
expect(blob.data).not_to be_empty
end
it 'finds by content' do
results = finder.find('files')
filename, blob = results.find { |_, blob| blob.filename == 'CHANGELOG' }
expect(filename).to eq('CHANGELOG')
expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
expect(blob.ref).to eq(finder.ref)
expect(blob.data).not_to be_empty
it_behaves_like 'file finder' do
subject { described_class.new(project, project.default_branch) }
let(:expected_file_by_name) { 'files/images/wm.svg' }
let(:expected_file_by_content) { 'CHANGELOG' }
end
end
end
......@@ -242,6 +242,7 @@ Ci::Pipeline:
- config_source
- failure_reason
- protected
- iid
Ci::Stage:
- id
- name
......
......@@ -22,47 +22,57 @@ describe Gitlab::ProjectSearchResults do
it { expect(results.query).to eq('hello world') }
end
describe 'blob search' do
let(:project) { create(:project, :public, :repository) }
subject(:results) { described_class.new(user, project, 'files').objects('blobs') }
context 'when repository is disabled' do
let(:project) { create(:project, :public, :repository, :repository_disabled) }
shared_examples 'general blob search' do |entity_type, blob_kind|
let(:query) { 'files' }
subject(:results) { described_class.new(user, project, query).objects(blob_type) }
it 'hides blobs from members' do
context "when #{entity_type} is disabled" do
let(:project) { disabled_project }
it "hides #{blob_kind} from members" do
project.add_reporter(user)
is_expected.to be_empty
end
it 'hides blobs from non-members' do
it "hides #{blob_kind} from non-members" do
is_expected.to be_empty
end
end
context 'when repository is internal' do
let(:project) { create(:project, :public, :repository, :repository_private) }
context "when #{entity_type} is internal" do
let(:project) { private_project }
it 'finds blobs for members' do
it "finds #{blob_kind} for members" do
project.add_reporter(user)
is_expected.not_to be_empty
end
it 'hides blobs from non-members' do
it "hides #{blob_kind} from non-members" do
is_expected.to be_empty
end
end
it 'finds by name' do
expect(results.map(&:first)).to include('files/images/wm.svg')
expect(results.map(&:first)).to include(expected_file_by_name)
end
it 'finds by content' do
blob = results.select { |result| result.first == "CHANGELOG" }.flatten.last
blob = results.select { |result| result.first == expected_file_by_content }.flatten.last
expect(blob.filename).to eq("CHANGELOG")
expect(blob.filename).to eq(expected_file_by_content)
end
end
describe 'blob search' do
let(:project) { create(:project, :public, :repository) }
it_behaves_like 'general blob search', 'repository', 'blobs' do
let(:blob_type) { 'blobs' }
let(:disabled_project) { create(:project, :public, :repository, :repository_disabled) }
let(:private_project) { create(:project, :public, :repository, :repository_private) }
let(:expected_file_by_name) { 'files/images/wm.svg' }
let(:expected_file_by_content) { 'CHANGELOG' }
end
describe 'parsing results' do
......@@ -189,40 +199,18 @@ describe Gitlab::ProjectSearchResults do
describe 'wiki search' do
let(:project) { create(:project, :public, :wiki_repo) }
let(:wiki) { build(:project_wiki, project: project) }
let!(:wiki_page) { wiki.create_page('Title', 'Content') }
subject(:results) { described_class.new(user, project, 'Content').objects('wiki_blobs') }
context 'when wiki is disabled' do
let(:project) { create(:project, :public, :wiki_repo, :wiki_disabled) }
it 'hides wiki blobs from members' do
project.add_reporter(user)
is_expected.to be_empty
end
it 'hides wiki blobs from non-members' do
is_expected.to be_empty
end
end
context 'when wiki is internal' do
let(:project) { create(:project, :public, :wiki_repo, :wiki_private) }
it 'finds wiki blobs for guest' do
project.add_guest(user)
is_expected.not_to be_empty
end
it 'hides wiki blobs from non-members' do
is_expected.to be_empty
end
before do
wiki.create_page('Files/Title', 'Content')
wiki.create_page('CHANGELOG', 'Files example')
end
it 'finds by content' do
expect(results).to include("master:Title.md\x001\x00Content\n")
it_behaves_like 'general blob search', 'wiki', 'wiki blobs' do
let(:blob_type) { 'wiki_blobs' }
let(:disabled_project) { create(:project, :public, :wiki_repo, :wiki_disabled) }
let(:private_project) { create(:project, :public, :wiki_repo, :wiki_private) }
let(:expected_file_by_name) { 'Files/Title.md' }
let(:expected_file_by_content) { 'CHANGELOG.md' }
end
end
......
require 'spec_helper'
describe Gitlab::WikiFileFinder do
describe '#find' do
let(:project) { create(:project, :public, :wiki_repo) }
let(:wiki) { build(:project_wiki, project: project) }
before do
wiki.create_page('Files/Title', 'Content')
wiki.create_page('CHANGELOG', 'Files example')
end
it_behaves_like 'file finder' do
subject { described_class.new(project, project.wiki.default_branch) }
let(:expected_file_by_name) { 'Files/Title.md' }
let(:expected_file_by_content) { 'CHANGELOG.md' }
end
end
end
......@@ -1559,6 +1559,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true },
{ key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true },
{ key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
{ key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true },
{ key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true },
......
......@@ -35,6 +35,16 @@ describe Ci::Pipeline, :mailer do
end
end
describe 'modules' do
it_behaves_like 'AtomicInternalId', validate_presence: false do
let(:internal_id_attribute) { :iid }
let(:instance) { build(:ci_pipeline) }
let(:scope) { :project }
let(:scope_attrs) { { project: instance.project } }
let(:usage) { :ci_pipelines }
end
end
describe '#source' do
context 'when creating new pipeline' do
let(:pipeline) do
......@@ -195,7 +205,8 @@ describe Ci::Pipeline, :mailer do
it 'includes all predefined variables in a valid order' do
keys = subject.map { |variable| variable[:key] }
expect(keys).to eq %w[CI_CONFIG_PATH
expect(keys).to eq %w[CI_PIPELINE_IID
CI_CONFIG_PATH
CI_PIPELINE_SOURCE
CI_COMMIT_MESSAGE
CI_COMMIT_TITLE
......
......@@ -20,6 +20,7 @@ describe Deployment do
it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid }
let(:instance) { build(:deployment) }
let(:scope) { :project }
let(:scope_attrs) { { project: instance.project } }
let(:usage) { :deployments }
end
......
......@@ -17,6 +17,7 @@ describe Issue do
it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid }
let(:instance) { build(:issue) }
let(:scope) { :project }
let(:scope_attrs) { { project: instance.project } }
let(:usage) { :issues }
end
......
......@@ -84,6 +84,7 @@ describe MergeRequest do
it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid }
let(:instance) { build(:merge_request) }
let(:scope) { :target_project }
let(:scope_attrs) { { project: instance.target_project } }
let(:usage) { :merge_requests }
end
......
......@@ -6,6 +6,7 @@ describe Milestone do
it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid }
let(:instance) { build(:milestone, project: build(:project), group: nil) }
let(:scope) { :project }
let(:scope_attrs) { { project: instance.project } }
let(:usage) { :milestones }
end
......@@ -15,6 +16,7 @@ describe Milestone do
it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid }
let(:instance) { build(:milestone, project: nil, group: build(:group)) }
let(:scope) { :group }
let(:scope_attrs) { { namespace: instance.group } }
let(:usage) { :milestones }
end
......
......@@ -4,12 +4,12 @@ module CycleAnalyticsHelpers
create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
end
def create_commit(message, project, user, branch_name, count: 1)
def create_commit(message, project, user, branch_name, count: 1, commit_time: nil, skip_push_handler: false)
repository = project.repository
oldrev = repository.commit(branch_name).sha
oldrev = repository.commit(branch_name)&.sha || Gitlab::Git::BLANK_SHA
if Timecop.frozen? && Gitlab::GitalyClient.feature_enabled?(:operation_user_commit_files)
mock_gitaly_multi_action_dates(repository.raw)
mock_gitaly_multi_action_dates(repository.raw, commit_time)
end
commit_shas = Array.new(count) do |index|
......@@ -19,6 +19,8 @@ module CycleAnalyticsHelpers
commit_sha
end
return if skip_push_handler
GitPushService.new(project,
user,
oldrev: oldrev,
......@@ -44,13 +46,11 @@ module CycleAnalyticsHelpers
project.repository.add_branch(user, source_branch, 'master')
end
sha = project.repository.create_file(
user,
generate(:branch),
'content',
message: commit_message,
branch_name: source_branch)
project.repository.commit(sha)
# Cycle analytic specs often test with frozen times, which causes metrics to be
# pinned to the current time. For example, in the plan stage, we assume that an issue
# milestone has been created before any code has been written. We add a second
# to ensure that the plan time is positive.
create_commit(commit_message, project, user, source_branch, commit_time: Time.now + 1.second, skip_push_handler: true)
opts = {
title: 'Awesome merge_request',
......@@ -116,9 +116,9 @@ module CycleAnalyticsHelpers
protected: false)
end
def mock_gitaly_multi_action_dates(raw_repository)
def mock_gitaly_multi_action_dates(raw_repository, commit_time)
allow(raw_repository).to receive(:multi_action).and_wrap_original do |m, *args|
new_date = Time.now
new_date = commit_time || Time.now
branch_update = m.call(*args)
if branch_update.newrev
......
shared_examples 'file finder' do
let(:query) { 'files' }
let(:search_results) { subject.find(query) }
it 'finds by name' do
filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_name }
expect(filename).to eq(expected_file_by_name)
expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
expect(blob.ref).to eq(subject.ref)
expect(blob.data).not_to be_empty
end
it 'finds by content' do
filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_content }
expect(filename).to eq(expected_file_by_content)
expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
expect(blob.ref).to eq(subject.ref)
expect(blob.data).not_to be_empty
end
end
require 'spec_helper'
shared_examples_for 'AtomicInternalId' do
shared_examples_for 'AtomicInternalId' do |validate_presence: true|
describe '.has_internal_id' do
describe 'Module inclusion' do
subject { described_class }
......@@ -9,14 +9,31 @@ shared_examples_for 'AtomicInternalId' do
end
describe 'Validation' do
subject { instance }
before do
allow(InternalId).to receive(:generate_next).and_return(nil)
allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!")
instance.valid?
end
it { is_expected.to validate_presence_of(internal_id_attribute) }
it { is_expected.to validate_numericality_of(internal_id_attribute) }
context 'when presence validation is required' do
before do
skip unless validate_presence
end
it 'validates presence' do
expect(instance.errors[internal_id_attribute]).to include("can't be blank")
end
end
context 'when presence validation is not required' do
before do
skip if validate_presence
end
it 'does not validate presence' do
expect(instance.errors[internal_id_attribute]).to be_empty
end
end
end
describe 'Creating an instance' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment