Commit 2f943b06 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-03-06

# Conflicts:
#	Gemfile.lock
#	app/helpers/branches_helper.rb
#	app/services/projects/update_pages_service.rb
#	app/views/projects/branches/index.html.haml
#	config/prometheus/additional_metrics.yml
#	db/schema.rb
#	doc/user/project/integrations/prometheus_library/kubernetes.md

[ci skip]
parents e7d9291f 5e8138aa
...@@ -1109,7 +1109,10 @@ DEPENDENCIES ...@@ -1109,7 +1109,10 @@ DEPENDENCIES
grape-route-helpers (~> 2.1.0) grape-route-helpers (~> 2.1.0)
grape_logging (~> 1.7) grape_logging (~> 1.7)
grpc (~> 1.8.3) grpc (~> 1.8.3)
<<<<<<< HEAD
gssapi gssapi
=======
>>>>>>> upstream/master
haml_lint (~> 0.26.0) haml_lint (~> 0.26.0)
hamlit (~> 2.6.1) hamlit (~> 2.6.1)
hashie-forbidden_attributes hashie-forbidden_attributes
......
...@@ -152,14 +152,14 @@ export default { ...@@ -152,14 +152,14 @@ export default {
showLeaveGroupModal(group, parentGroup) { showLeaveGroupModal(group, parentGroup) {
this.targetGroup = group; this.targetGroup = group;
this.targetParentGroup = parentGroup; this.targetParentGroup = parentGroup;
this.updateModal = true; this.showModal = true;
this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`); this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
}, },
hideLeaveGroupModal() { hideLeaveGroupModal() {
this.updateModal = false; this.showModal = false;
}, },
leaveGroup() { leaveGroup() {
this.updateModal = false; this.showModal = false;
this.targetGroup.isBeingRemoved = true; this.targetGroup.isBeingRemoved = true;
this.service.leaveGroup(this.targetGroup.leavePath) this.service.leaveGroup(this.targetGroup.leavePath)
.then(res => res.json()) .then(res => res.json())
...@@ -208,9 +208,9 @@ export default { ...@@ -208,9 +208,9 @@ export default {
:page-info="pageInfo" :page-info="pageInfo"
/> />
<modal <modal
v-show="showModal" v-if="showModal"
:primary-button-label="__('Leave')"
kind="warning" kind="warning"
:primary-button-label="__('Leave')"
:title="__('Are you sure?')" :title="__('Are you sure?')"
:text="groupLeaveConfirmationMessage" :text="groupLeaveConfirmationMessage"
@cancel="hideLeaveGroupModal" @cancel="hideLeaveGroupModal"
......
...@@ -7,13 +7,19 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -7,13 +7,19 @@ class Projects::BranchesController < Projects::ApplicationController
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged] before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
def index # Support legacy URLs
@sort = params[:sort].presence || sort_value_recently_updated before_action :redirect_for_legacy_index_sort_or_search, only: [:index]
@branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page])
def index
respond_to do |format| respond_to do |format|
format.html do format.html do
@sort = params[:sort].presence || sort_value_recently_updated
@mode = params[:state].presence || 'overview'
@overview_max_branches = 5
# Fetch branches for the specified mode
fetch_branches_by_mode
@refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name)) @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names = @merged_branch_names =
repository.merged_branch_names(@branches.map(&:name)) repository.merged_branch_names(@branches.map(&:name))
...@@ -28,7 +34,9 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -28,7 +34,9 @@ class Projects::BranchesController < Projects::ApplicationController
end end
end end
format.json do format.json do
render json: @branches.map(&:name) branches = BranchesFinder.new(@repository, params).execute
branches = Kaminari.paginate_array(branches).page(params[:page])
render json: branches.map(&:name)
end end
end end
end end
...@@ -123,4 +131,27 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -123,4 +131,27 @@ class Projects::BranchesController < Projects::ApplicationController
context: 'autodeploy' context: 'autodeploy'
) )
end end
def redirect_for_legacy_index_sort_or_search
# Normalize a legacy URL with redirect
if request.format != :json && !params[:state].presence && [:sort, :search, :page].any? { |key| params[key].presence }
redirect_to project_branches_filtered_path(@project, state: 'all'), notice: 'Update your bookmarked URLs as filtered/sorted branches URL has been changed.'
end
end
def fetch_branches_by_mode
if @mode == 'overview'
# overview mode
@active_branches, @stale_branches = BranchesFinder.new(@repository, sort: sort_value_recently_updated).execute.partition(&:active?)
# Here we get one more branch to indicate if there are more data we're not showing
@active_branches = @active_branches.first(@overview_max_branches + 1)
@stale_branches = @stale_branches.first(@overview_max_branches + 1)
@branches = @active_branches + @stale_branches
else
# active/stale/all view mode
@branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute
@branches = @branches.select { |b| b.state.to_s == @mode } if %w[active stale].include?(@mode)
@branches = Kaminari.paginate_array(@branches).page(params[:page])
end
end
end end
...@@ -9,25 +9,22 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -9,25 +9,22 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :assign_commit before_action :assign_commit
def show def show
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37602 @url = project_network_path(@project, @ref, @options.merge(format: :json))
Gitlab::GitalyClient.allow_n_plus_1_calls do @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
@url = project_network_path(@project, @ref, @options.merge(format: :json))
@commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
respond_to do |format|
format.html do
if @options[:extended_sha1] && !@commit
flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist."
end
end
format.json do respond_to do |format|
@graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref]) format.html do
if @options[:extended_sha1] && !@commit
flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist."
end end
end end
render format.json do
@graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
end
end end
render
end end
def assign_commit def assign_commit
......
class BranchesFinder class BranchesFinder
def initialize(repository, params) def initialize(repository, params = {})
@repository = repository @repository = repository
@params = params @params = params
end end
......
...@@ -150,9 +150,7 @@ class TodosFinder ...@@ -150,9 +150,7 @@ class TodosFinder
if project? if project?
items.where(project: project) items.where(project: project)
else else
projects = Project projects = Project.public_or_visible_to_user(current_user)
.public_or_visible_to_user(current_user)
.order_id_desc
items.joins(:project).merge(projects) items.joins(:project).merge(projects)
end end
......
module BranchesHelper module BranchesHelper
<<<<<<< HEAD
prepend EE::BranchesHelper prepend EE::BranchesHelper
def filter_branches_path(options = {}) def filter_branches_path(options = {})
...@@ -12,6 +13,8 @@ module BranchesHelper ...@@ -12,6 +13,8 @@ module BranchesHelper
project_branches_path(@project, @id, options) project_branches_path(@project, @id, options)
end end
=======
>>>>>>> upstream/master
def project_branches def project_branches
options_for_select(@project.repository.branch_names, @project.default_branch) options_for_select(@project.repository.branch_names, @project.default_branch)
end end
......
...@@ -56,12 +56,13 @@ module Clusters ...@@ -56,12 +56,13 @@ module Clusters
def specification def specification
{ {
"gitlabUrl" => gitlab_url, "gitlabUrl" => gitlab_url,
"runnerToken" => ensure_runner.token "runnerToken" => ensure_runner.token,
"runners" => { "privileged" => privileged }
} }
end end
def content_values def content_values
specification.merge(YAML.load_file(chart_values_file)) YAML.load_file(chart_values_file).deep_merge!(specification)
end end
end end
end end
......
...@@ -24,12 +24,7 @@ module Network ...@@ -24,12 +24,7 @@ module Network
end end
def parents(map) def parents(map)
@commit.parents.map do |p| map.values_at(*@commit.parent_ids).compact
if map.include?(p.id)
map[p.id]
end
end
.compact
end end
end end
end end
...@@ -32,8 +32,6 @@ class Todo < ActiveRecord::Base ...@@ -32,8 +32,6 @@ class Todo < ActiveRecord::Base
validates :target_id, presence: true, unless: :for_commit? validates :target_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit? validates :commit_id, presence: true, if: :for_commit?
default_scope { reorder(id: :desc) }
scope :pending, -> { with_state(:pending) } scope :pending, -> { with_state(:pending) }
scope :done, -> { with_state(:done) } scope :done, -> { with_state(:done) }
...@@ -53,10 +51,14 @@ class Todo < ActiveRecord::Base ...@@ -53,10 +51,14 @@ class Todo < ActiveRecord::Base
# milestones, but still show something if the user has a URL with that # milestones, but still show something if the user has a URL with that
# selected. # selected.
def sort(method) def sort(method)
case method.to_s sorted =
when 'priority', 'label_priority' then order_by_labels_priority case method.to_s
else order_by(method) when 'priority', 'label_priority' then order_by_labels_priority
end else order_by(method)
end
# Break ties with the ID column for pagination
sorted.order(id: :desc)
end end
# Order by priority depending on which issue/merge request the Todo belongs to # Order by priority depending on which issue/merge request the Todo belongs to
......
...@@ -81,6 +81,7 @@ module Projects ...@@ -81,6 +81,7 @@ module Projects
end end
def extract_tar_archive!(temp_path) def extract_tar_archive!(temp_path)
<<<<<<< HEAD
build.artifacts_file.use_file do |artifacts_path| build.artifacts_file.use_file do |artifacts_path|
results = Open3.pipeline(%W(gunzip -c #{artifacts_path}), results = Open3.pipeline(%W(gunzip -c #{artifacts_path}),
%W(dd bs=#{BLOCK_SIZE} count=#{blocks}), %W(dd bs=#{BLOCK_SIZE} count=#{blocks}),
...@@ -88,6 +89,13 @@ module Projects ...@@ -88,6 +89,13 @@ module Projects
err: '/dev/null') err: '/dev/null')
raise FailedToExtractError, 'pages failed to extract' unless results.compact.all?(&:success?) raise FailedToExtractError, 'pages failed to extract' unless results.compact.all?(&:success?)
end end
=======
results = Open3.pipeline(%W(gunzip -c #{artifacts}),
%W(dd bs=#{BLOCK_SIZE} count=#{blocks}),
%W(tar -x -C #{temp_path} #{SITE_PATH}),
err: '/dev/null')
raise FailedToExtractError, 'pages failed to extract' unless results.compact.all?(&:success?)
>>>>>>> upstream/master
end end
def extract_zip_archive!(temp_path) def extract_zip_archive!(temp_path)
...@@ -105,10 +113,15 @@ module Projects ...@@ -105,10 +113,15 @@ module Projects
# -n never overwrite existing files # -n never overwrite existing files
# We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories # We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories
site_path = File.join(SITE_PATH, '*') site_path = File.join(SITE_PATH, '*')
<<<<<<< HEAD
build.artifacts_file.use_file do |artifacts_path| build.artifacts_file.use_file do |artifacts_path|
unless system(*%W(unzip -qq -n #{artifacts_path} #{site_path} -d #{temp_path})) unless system(*%W(unzip -qq -n #{artifacts_path} #{site_path} -d #{temp_path}))
raise FailedToExtractError, 'pages failed to extract' raise FailedToExtractError, 'pages failed to extract'
end end
=======
unless system(*%W(unzip -qq -n #{artifacts} #{site_path} -d #{temp_path}))
raise FailedToExtractError, 'pages failed to extract'
>>>>>>> upstream/master
end end
end end
...@@ -176,6 +189,11 @@ module Projects ...@@ -176,6 +189,11 @@ module Projects
build.erase_artifacts! unless build.has_expiring_artifacts? build.erase_artifacts! unless build.has_expiring_artifacts?
end end
def delete_artifact!
build.reload # Reload stable object to prevent erase artifacts with old state
build.erase_artifacts! unless build.has_expiring_artifacts?
end
def latest_sha def latest_sha
project.commit(build.ref).try(:sha).to_s project.commit(build.ref).try(:sha).to_s
end end
......
- branches = local_assigns.fetch(:branches)
- state = local_assigns.fetch(:state)
- panel_title = local_assigns.fetch(:panel_title)
- show_more_text = local_assigns.fetch(:show_more_text)
- project = local_assigns.fetch(:project)
- overview_max_branches = local_assigns.fetch(:overview_max_branches)
- return unless branches.any?
.panel.panel-default.prepend-top-10
.panel-heading
%h4.panel-title
= panel_title
%ul.content-list.all-branches
- branches.first(overview_max_branches).each do |branch|
= render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch)
- if branches.size > overview_max_branches
.panel-footer.text-center
= link_to show_more_text, project_branches_filtered_path(project, state: state), id: "state-#{state}", data: { state: state }
...@@ -4,26 +4,35 @@ ...@@ -4,26 +4,35 @@
%div{ class: container_class } %div{ class: container_class }
.top-area.adjust .top-area.adjust
- if can?(current_user, :admin_project, @project) %ul.nav-links.issues-state-filters
.nav-text %li{ class: active_when(@mode == 'overview') }>
- project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project) = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches')
= s_('Branches|Protected branches can be managed in %{project_settings_link}').html_safe % { project_settings_link: project_settings_link }
%li{ class: active_when(@mode == 'active') }>
= link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), title: s_('Branches|Show active branches')
%li{ class: active_when(@mode == 'stale') }>
= link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), title: s_('Branches|Show stale branches')
%li{ class: active_when(!%w[overview active stale].include?(@mode)) }>
= link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches')
.nav-controls .nav-controls
= form_tag(filter_branches_path, method: :get) do = form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do
= search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
.dropdown.inline> - unless @mode == 'overview'
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } .dropdown.inline>
%span.light %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
= branches_sort_options_hash[@sort] %span.light
= icon('chevron-down') = branches_sort_options_hash[@sort]
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable = icon('chevron-down')
%li.dropdown-header %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
= s_('Branches|Sort by') %li.dropdown-header
- branches_sort_options_hash.each do |value, title| = s_('Branches|Sort by')
%li - branches_sort_options_hash.each do |value, title|
= link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value) %li
= link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value)
- if can? current_user, :push_code, @project - if can? current_user, :push_code, @project
= link_to project_merged_branches_path(@project), = link_to project_merged_branches_path(@project),
...@@ -36,9 +45,23 @@ ...@@ -36,9 +45,23 @@
= link_to new_project_branch_path(@project), class: 'btn btn-create' do = link_to new_project_branch_path(@project), class: 'btn btn-create' do
= s_('Branches|New branch') = s_('Branches|New branch')
<<<<<<< HEAD
= render 'projects/commits/mirror_status' = render 'projects/commits/mirror_status'
- if @branches.any? - if @branches.any?
=======
- if can?(current_user, :admin_project, @project)
- project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project)
.row-content-block
%h5
= s_('Branches|Protected branches can be managed in %{project_settings_link}.').html_safe % { project_settings_link: project_settings_link }
- if @mode == 'overview' && (@active_branches.any? || @stale_branches.any?)
= render "projects/branches/panel", branches: @active_branches, state: 'active', panel_title: s_('Branches|Active branches'), show_more_text: s_('Branches|Show more active branches'), project: @project, overview_max_branches: @overview_max_branches
= render "projects/branches/panel", branches: @stale_branches, state: 'stale', panel_title: s_('Branches|Stale branches'), show_more_text: s_('Branches|Show more stale branches'), project: @project, overview_max_branches: @overview_max_branches
- elsif @branches.any?
>>>>>>> upstream/master
%ul.content-list.all-branches %ul.content-list.all-branches
- @branches.each do |branch| - @branches.each do |branch|
= render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name) = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name)
......
---
title: Add overview of branches and a filter for active/stale branches
merge_request: 15402
author: Takuya Noguchi
type: added
---
title: Enable privileged mode for GitLab Runner
merge_request: 17528
author:
type: added
---
title: Prevent the graphs page from generating unnecessary Gitaly requests
merge_request: 37602
author:
type: performance
---
title: Store sha256 checksum to job artifacts
merge_request: 17354
author:
type: performance
...@@ -148,10 +148,13 @@ ...@@ -148,10 +148,13 @@
- query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024' - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
label: Average label: Average
unit: MB unit: MB
<<<<<<< HEAD
- query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024' - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
label: Average label: Average
unit: MB unit: MB
track: canary track: canary
=======
>>>>>>> upstream/master
- title: "CPU Usage" - title: "CPU Usage"
y_label: "Cores per Pod" y_label: "Cores per Pod"
required_metrics: required_metrics:
...@@ -159,9 +162,14 @@ ...@@ -159,9 +162,14 @@
weight: 1 weight: 1
queries: queries:
- query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))' - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
<<<<<<< HEAD
label: Average label: Average
unit: "cores" unit: "cores"
- query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))' - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
label: Average label: Average
unit: "cores" unit: "cores"
track: canary track: canary
=======
label: Average
unit: "cores"
>>>>>>> upstream/master
...@@ -49,6 +49,7 @@ scope format: false do ...@@ -49,6 +49,7 @@ scope format: false do
end end
end end
get '/branches/:state', to: 'branches#index', as: :branches_filtered, constraints: { state: /active|stale|all/ }
resources :branches, only: [:index, :new, :create, :destroy] resources :branches, only: [:index, :new, :create, :destroy]
delete :merged_branches, controller: 'branches', action: :destroy_all_merged delete :merged_branches, controller: 'branches', action: :destroy_all_merged
resources :tags, only: [:index, :show, :new, :create, :destroy] do resources :tags, only: [:index, :show, :new, :create, :destroy] do
......
class AddChecksumToCiJobArtifacts < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :ci_job_artifacts, :file_sha256, :binary
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddPrivilegedToRunner < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default :clusters_applications_runners, :privileged, :boolean, default: true, allow_null: false
end
def down
remove_column :clusters_applications_runners, :privileged
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180304204842) do ActiveRecord::Schema.define(version: 20180305144721) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -426,7 +426,11 @@ ActiveRecord::Schema.define(version: 20180304204842) do ...@@ -426,7 +426,11 @@ ActiveRecord::Schema.define(version: 20180304204842) do
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
t.datetime_with_timezone "expire_at" t.datetime_with_timezone "expire_at"
t.string "file" t.string "file"
<<<<<<< HEAD
t.integer "file_store" t.integer "file_store"
=======
t.binary "file_sha256"
>>>>>>> upstream/master
end end
add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree
...@@ -698,6 +702,7 @@ ActiveRecord::Schema.define(version: 20180304204842) do ...@@ -698,6 +702,7 @@ ActiveRecord::Schema.define(version: 20180304204842) do
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
t.string "version", null: false t.string "version", null: false
t.text "status_reason" t.text "status_reason"
t.boolean "privileged", default: true, null: false
end end
add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree
......
...@@ -48,3 +48,9 @@ Here's a few links to get you started: ...@@ -48,3 +48,9 @@ Here's a few links to get you started:
- [git-p4 manual page](https://www.kernel.org/pub/software/scm/git/docs/git-p4.html) - [git-p4 manual page](https://www.kernel.org/pub/software/scm/git/docs/git-p4.html)
- [git-p4 example usage](https://git.wiki.kernel.org/index.php/Git-p4_Usage) - [git-p4 example usage](https://git.wiki.kernel.org/index.php/Git-p4_Usage)
- [Git book migration guide](https://git-scm.com/book/en/v2/Git-and-Other-Systems-Migrating-to-Git#_perforce_import) - [Git book migration guide](https://git-scm.com/book/en/v2/Git-and-Other-Systems-Migrating-to-Git#_perforce_import)
Note that `git p4` and `git filter-branch` are not very good at
creating small and efficient Git pack files. So it might be a good
idea to spend time and CPU to properly repack your repository before
sending it for the first time to your GitLab server. See
[this StackOverflow question](https://stackoverflow.com/questions/28720151/git-gc-aggressive-vs-git-repack/).
...@@ -28,6 +28,7 @@ Prometheus needs to be deployed into the cluster and configured properly in orde ...@@ -28,6 +28,7 @@ Prometheus needs to be deployed into the cluster and configured properly in orde
In order to isolate and only display relevant CPU and Memory metrics for a given environment, GitLab needs a method to detect which containers it is running. Because these metrics are tracked at the container level, traditional Kubernetes labels are not available. In order to isolate and only display relevant CPU and Memory metrics for a given environment, GitLab needs a method to detect which containers it is running. Because these metrics are tracked at the container level, traditional Kubernetes labels are not available.
Instead, the [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) name should begin with [CI_ENVIRONMENT_SLUG](../../../../ci/variables/README.md#predefined-variables-environment-variables). It can be followed by a `-` and additional content if desired. For example, a deployment name of `review-homepage-5620p5` would match the `review/homepage` environment. Instead, the [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) name should begin with [CI_ENVIRONMENT_SLUG](../../../../ci/variables/README.md#predefined-variables-environment-variables). It can be followed by a `-` and additional content if desired. For example, a deployment name of `review-homepage-5620p5` would match the `review/homepage` environment.
<<<<<<< HEAD
## Displaying Canary metrics ## Displaying Canary metrics
...@@ -43,3 +44,5 @@ These metrics expect the [Deployment](https://kubernetes.io/docs/concepts/worklo ...@@ -43,3 +44,5 @@ These metrics expect the [Deployment](https://kubernetes.io/docs/concepts/worklo
| ---- | ----- | | ---- | ----- |
| Average Memory Usage (MB) | avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024 | | Average Memory Usage (MB) | avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024 |
| Average CPU Utilization (%) | avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name)) | | Average CPU Utilization (%) | avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name)) |
=======
>>>>>>> upstream/master
...@@ -204,6 +204,7 @@ module API ...@@ -204,6 +204,7 @@ module API
optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse)) optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse))
optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse)) optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse))
optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse)) optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse))
optional 'file.sha256', type: String, desc: %q(sha256 checksum of the file)
optional 'metadata.path', type: String, desc: %q(path to locally stored body (generated by Workhorse)) optional 'metadata.path', type: String, desc: %q(path to locally stored body (generated by Workhorse))
optional 'metadata.name', type: String, desc: %q(filename (generated by Workhorse)) optional 'metadata.name', type: String, desc: %q(filename (generated by Workhorse))
end end
...@@ -224,7 +225,7 @@ module API ...@@ -224,7 +225,7 @@ module API
expire_in = params['expire_in'] || expire_in = params['expire_in'] ||
Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in
job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, expire_in: expire_in) job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, file_sha256: params['file.sha256'], expire_in: expire_in)
job.build_job_artifacts_metadata(project: job.project, file_type: :metadata, file: metadata, expire_in: expire_in) if metadata job.build_job_artifacts_metadata(project: job.project, file_type: :metadata, file: metadata, expire_in: expire_in) if metadata
job.artifacts_expire_in = expire_in job.artifacts_expire_in = expire_in
......
module Gitlab module Gitlab
module Git module Git
class Branch < Ref class Branch < Ref
STALE_BRANCH_THRESHOLD = 3.months
def self.find(repo, branch_name) def self.find(repo, branch_name)
if branch_name.is_a?(Gitlab::Git::Branch) if branch_name.is_a?(Gitlab::Git::Branch)
branch_name branch_name
...@@ -12,6 +14,18 @@ module Gitlab ...@@ -12,6 +14,18 @@ module Gitlab
def initialize(repository, name, target, target_commit) def initialize(repository, name, target, target_commit)
super(repository, name, target, target_commit) super(repository, name, target, target_commit)
end end
def active?
self.dereferenced_target.committed_date >= STALE_BRANCH_THRESHOLD.ago
end
def stale?
!active?
end
def state
active? ? :active : :stale
end
end end
end end
end end
...@@ -7,7 +7,7 @@ require 'gitlab' ...@@ -7,7 +7,7 @@ require 'gitlab'
# #
Gitlab.configure do |config| Gitlab.configure do |config|
config.endpoint = 'https://gitlab.com/api/v4' config.endpoint = 'https://gitlab.com/api/v4'
config.private_token = ENV["DOCS_API_TOKEN"] # GitLab Docs bot access token which has only Developer access to gitlab-docs config.private_token = ENV["DOCS_API_TOKEN"] # GitLab Docs bot access token with Developer access to gitlab-docs
end end
# #
...@@ -31,13 +31,24 @@ def docs_branch ...@@ -31,13 +31,24 @@ def docs_branch
end end
# #
# Create a remote branch in gitlab-docs # Create a remote branch in gitlab-docs and immediately cancel the pipeline
# to avoid race conditions, since a triggered pipeline will also run right
# after the branch creation. This only happens the very first time a branch
# is created and will be skipped in subsequent runs. Read more in
# https://gitlab.com/gitlab-com/gitlab-docs/issues/154.
# #
def create_remote_branch def create_remote_branch
Gitlab.create_branch(GITLAB_DOCS_REPO, docs_branch, 'master') Gitlab.create_branch(GITLAB_DOCS_REPO, docs_branch, 'master')
puts "Remote branch '#{docs_branch}' created" puts "=> Remote branch '#{docs_branch}' created"
# Get the latest pipeline ID which is also the first
pipeline_id = Gitlab.pipelines(GITLAB_DOCS_REPO, { ref: docs_branch }).last.id
# Cancel the pipeline
Gitlab.cancel_pipeline(GITLAB_DOCS_REPO, pipeline_id)
puts "=> Canceled uneeded pipeline #{pipeline_id} for '#{docs_branch}'"
rescue Gitlab::Error::BadRequest rescue Gitlab::Error::BadRequest
puts "Remote branch '#{docs_branch}' already exists" puts "=> Remote branch '#{docs_branch}' already exists"
end end
# #
...@@ -45,7 +56,7 @@ end ...@@ -45,7 +56,7 @@ end
# #
def remove_remote_branch def remove_remote_branch
Gitlab.delete_branch(GITLAB_DOCS_REPO, docs_branch) Gitlab.delete_branch(GITLAB_DOCS_REPO, docs_branch)
puts "Remote branch '#{docs_branch}' deleted" puts "=> Remote branch '#{docs_branch}' deleted"
end end
# #
...@@ -78,18 +89,22 @@ def trigger_pipeline ...@@ -78,18 +89,22 @@ def trigger_pipeline
# The review app URL # The review app URL
app_url = "http://#{docs_branch}.#{ENV["DOCS_REVIEW_APPS_DOMAIN"]}/#{slug}" app_url = "http://#{docs_branch}.#{ENV["DOCS_REVIEW_APPS_DOMAIN"]}/#{slug}"
# Create the pipeline # Create the cross project pipeline using CI_JOB_TOKEN
puts "=> Triggering a pipeline..."
pipeline = Gitlab.run_trigger(GITLAB_DOCS_REPO, ENV["CI_JOB_TOKEN"], docs_branch, { param_name => ENV["CI_COMMIT_REF_NAME"] }) pipeline = Gitlab.run_trigger(GITLAB_DOCS_REPO, ENV["CI_JOB_TOKEN"], docs_branch, { param_name => ENV["CI_COMMIT_REF_NAME"] })
puts "=> Pipeline created:" puts "=> Follow the status of the triggered pipeline:"
puts "" puts ""
puts "https://gitlab.com/gitlab-com/gitlab-docs/pipelines/#{pipeline.id}" puts "https://gitlab.com/gitlab-com/gitlab-docs/pipelines/#{pipeline.id}"
puts "" puts ""
puts "=> Preview your changes live at:" puts "=> In a few minutes, you will be able to preview your changes under the following URL:"
puts "" puts ""
puts app_url puts app_url
puts "" puts ""
puts "=> For more information, read the documentation"
puts "=> https://docs.gitlab.com/ee/development/writing_documentation.html#previewing-the-changes-live"
puts ""
puts "=> If something doesn't work, drop a line in the #docs chat channel."
puts ""
end end
# #
......
...@@ -425,10 +425,43 @@ describe Projects::BranchesController do ...@@ -425,10 +425,43 @@ describe Projects::BranchesController do
get :index, get :index,
namespace_id: project.namespace, namespace_id: project.namespace,
project_id: project, project_id: project,
state: 'all',
format: :html format: :html
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
end end
context 'when depreated sort/search/page parameters are specified' do
it 'returns with a status 301 when sort specified' do
get :index,
namespace_id: project.namespace,
project_id: project,
sort: 'updated_asc',
format: :html
expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
end
it 'returns with a status 301 when search specified' do
get :index,
namespace_id: project.namespace,
project_id: project,
search: 'feature',
format: :html
expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
end
it 'returns with a status 301 when page specified' do
get :index,
namespace_id: project.namespace,
project_id: project,
page: 2,
format: :html
expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
end
end
end end
end end
...@@ -29,7 +29,7 @@ feature 'Download buttons in branches page' do ...@@ -29,7 +29,7 @@ feature 'Download buttons in branches page' do
describe 'when checking branches' do describe 'when checking branches' do
context 'with artifacts' do context 'with artifacts' do
before do before do
visit project_branches_path(project, search: 'binary-encoding') visit project_branches_filtered_path(project, state: 'all', search: 'binary-encoding')
end end
scenario 'shows download artifacts button' do scenario 'shows download artifacts button' do
......
...@@ -13,15 +13,109 @@ describe 'Branches' do ...@@ -13,15 +13,109 @@ describe 'Branches' do
project.add_developer(user) project.add_developer(user)
end end
describe 'Initial branches page' do context 'on the projects with 6 active branches and 4 stale branches' do
it 'shows all the branches sorted by last updated by default' do let(:project) { create(:project, :public, :empty_repo) }
let(:repository) { project.repository }
let(:threshold) { Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD }
before do
# Add 4 stale branches
(1..4).reverse_each do |i|
Timecop.freeze((threshold + i).ago) { create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}") }
end
# Add 6 active branches
(1..6).each do |i|
Timecop.freeze((threshold - i).ago) { create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}") }
end
end
describe 'Overview page of the branches' do
it 'shows the first 5 active branches and the first 4 stale branches sorted by last updated' do
visit project_branches_path(project)
expect(page).to have_content(sorted_branches(repository, count: 5, sort_by: :updated_desc, state: 'active'))
expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale'))
expect(page).to have_link('Show more active branches', href: project_branches_filtered_path(project, state: 'active'))
expect(page).not_to have_content('Show more stale branches')
end
end
describe 'Active branches page' do
it 'shows 6 active branches sorted by last updated' do
visit project_branches_filtered_path(project, state: 'active')
expect(page).to have_content(sorted_branches(repository, count: 6, sort_by: :updated_desc, state: 'active'))
end
end
describe 'Stale branches page' do
it 'shows 4 active branches sorted by last updated' do
visit project_branches_filtered_path(project, state: 'stale')
expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale'))
end
end
describe 'All branches page' do
it 'shows 10 branches sorted by last updated' do
visit project_branches_filtered_path(project, state: 'all')
expect(page).to have_content(sorted_branches(repository, count: 10, sort_by: :updated_desc))
end
end
context 'with branches over more than one page' do
before do
allow(Kaminari.config).to receive(:default_per_page).and_return(5)
end
it 'shows only default_per_page active branches sorted by last updated' do
visit project_branches_filtered_path(project, state: 'active')
expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc, state: 'active'))
end
it 'shows only default_per_page branches sorted by last updated on All branches' do
visit project_branches_filtered_path(project, state: 'all')
expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc))
end
end
end
describe 'Find branches' do
it 'shows filtered branches', :js do
visit project_branches_path(project) visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
end
end
describe 'Delete unprotected branch on Overview' do
it 'removes branch after confirmation', :js do
visit project_branches_filtered_path(project, state: 'all')
expect(all('.all-branches').last).to have_selector('li', count: 20)
accept_confirm { find('.js-branch-add-pdf-text-binary .btn-remove').click }
expect(all('.all-branches').last).to have_selector('li', count: 19)
end
end
describe 'All branches page' do
it 'shows all the branches sorted by last updated by default' do
visit project_branches_filtered_path(project, state: 'all')
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc)) expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc))
end end
it 'sorts the branches by name' do it 'sorts the branches by name' do
visit project_branches_path(project) visit project_branches_filtered_path(project, state: 'all')
click_button "Last updated" # Open sorting dropdown click_button "Last updated" # Open sorting dropdown
click_link "Name" click_link "Name"
...@@ -30,7 +124,7 @@ describe 'Branches' do ...@@ -30,7 +124,7 @@ describe 'Branches' do
end end
it 'sorts the branches by oldest updated' do it 'sorts the branches by oldest updated' do
visit project_branches_path(project) visit project_branches_filtered_path(project, state: 'all')
click_button "Last updated" # Open sorting dropdown click_button "Last updated" # Open sorting dropdown
click_link "Oldest updated" click_link "Oldest updated"
...@@ -43,13 +137,13 @@ describe 'Branches' do ...@@ -43,13 +137,13 @@ describe 'Branches' do
%w(one two three four five).each { |ref| repository.add_branch(user, ref, 'master') } %w(one two three four five).each { |ref| repository.add_branch(user, ref, 'master') }
expect { visit project_branches_path(project) }.not_to exceed_query_limit(control_count) expect { visit project_branches_filtered_path(project, state: 'all') }.not_to exceed_query_limit(control_count)
end end
end end
describe 'Find branches' do describe 'Find branches on All branches' do
it 'shows filtered branches', :js do it 'shows filtered branches', :js do
visit project_branches_path(project) visit project_branches_filtered_path(project, state: 'all')
fill_in 'branch-search', with: 'fix' fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter) find('#branch-search').native.send_keys(:enter)
...@@ -59,9 +153,9 @@ describe 'Branches' do ...@@ -59,9 +153,9 @@ describe 'Branches' do
end end
end end
describe 'Delete unprotected branch' do describe 'Delete unprotected branch on All branches' do
it 'removes branch after confirmation', :js do it 'removes branch after confirmation', :js do
visit project_branches_path(project) visit project_branches_filtered_path(project, state: 'all')
fill_in 'branch-search', with: 'fix' fill_in 'branch-search', with: 'fix'
...@@ -75,6 +169,19 @@ describe 'Branches' do ...@@ -75,6 +169,19 @@ describe 'Branches' do
expect(find('.all-branches')).to have_selector('li', count: 0) expect(find('.all-branches')).to have_selector('li', count: 0)
end end
end end
context 'on project with 0 branch' do
let(:project) { create(:project, :public, :empty_repo) }
let(:repository) { project.repository }
describe '0 branches on Overview' do
it 'shows warning' do
visit project_branches_path(project)
expect(page).not_to have_selector('.all-branches')
end
end
end
end end
context 'logged in as master' do context 'logged in as master' do
...@@ -85,7 +192,7 @@ describe 'Branches' do ...@@ -85,7 +192,7 @@ describe 'Branches' do
describe 'Initial branches page' do describe 'Initial branches page' do
it 'shows description for admin' do it 'shows description for admin' do
visit project_branches_path(project) visit project_branches_filtered_path(project, state: 'all')
expect(page).to have_content("Protected branches can be managed in project settings") expect(page).to have_content("Protected branches can be managed in project settings")
end end
...@@ -104,12 +211,18 @@ describe 'Branches' do ...@@ -104,12 +211,18 @@ describe 'Branches' do
end end
end end
def sorted_branches(repository, count:, sort_by:) def sorted_branches(repository, count:, sort_by:, state: nil)
branches = repository.branches_sorted_by(sort_by)
branches = branches.select { |b| state == 'active' ? b.active? : b.stale? } if state
sorted_branches = sorted_branches =
repository.branches_sorted_by(sort_by).first(count).map do |branch| branches.first(count).map do |branch|
Regexp.escape(branch.name) Regexp.escape(branch.name)
end end
Regexp.new(sorted_branches.join('.*')) Regexp.new(sorted_branches.join('.*'))
end end
def create_file(message: 'message', branch_name:)
repository.create_file(user, generate(:branch), 'content', message: message, branch_name: branch_name)
end
end end
...@@ -234,7 +234,7 @@ feature 'Environment' do ...@@ -234,7 +234,7 @@ feature 'Environment' do
end end
scenario 'user deletes the branch with running environment' do scenario 'user deletes the branch with running environment' do
visit project_branches_path(project, search: 'feature') visit project_branches_filtered_path(project, state: 'all', search: 'feature')
remove_branch_with_hooks(project, user, 'feature') do remove_branch_with_hooks(project, user, 'feature') do
page.within('.js-branch-feature') { find('a.btn-remove').click } page.within('.js-branch-feature') { find('a.btn-remove').click }
......
...@@ -81,8 +81,8 @@ feature 'Merge Request button' do ...@@ -81,8 +81,8 @@ feature 'Merge Request button' do
context 'on branches page' do context 'on branches page' do
it_behaves_like 'Merge request button only shown when allowed' do it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Merge request' } let(:label) { 'Merge request' }
let(:url) { project_branches_path(project, search: 'feature') } let(:url) { project_branches_filtered_path(project, state: 'all', search: 'feature') }
let(:fork_url) { project_branches_path(forked_project, search: 'feature') } let(:fork_url) { project_branches_filtered_path(forked_project, state: 'all', search: 'feature') }
end end
end end
......
...@@ -2,12 +2,13 @@ require 'spec_helper' ...@@ -2,12 +2,13 @@ require 'spec_helper'
describe TodosFinder do describe TodosFinder do
describe '#execute' do describe '#execute' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:group) { create(:group) }
let(:finder) { described_class } let(:project) { create(:project, namespace: group) }
let(:finder) { described_class }
before do before do
project.add_developer(user) group.add_developer(user)
end end
describe '#sort' do describe '#sort' do
...@@ -34,17 +35,20 @@ describe TodosFinder do ...@@ -34,17 +35,20 @@ describe TodosFinder do
end end
it "sorts by priority" do it "sorts by priority" do
project_2 = create(:project)
label_1 = create(:label, title: 'label_1', project: project, priority: 1) label_1 = create(:label, title: 'label_1', project: project, priority: 1)
label_2 = create(:label, title: 'label_2', project: project, priority: 2) label_2 = create(:label, title: 'label_2', project: project, priority: 2)
label_3 = create(:label, title: 'label_3', project: project, priority: 3) label_3 = create(:label, title: 'label_3', project: project, priority: 3)
label_1_2 = create(:label, title: 'label_1', project: project_2, priority: 1)
issue_1 = create(:issue, title: 'issue_1', project: project) issue_1 = create(:issue, title: 'issue_1', project: project)
issue_2 = create(:issue, title: 'issue_2', project: project) issue_2 = create(:issue, title: 'issue_2', project: project)
issue_3 = create(:issue, title: 'issue_3', project: project) issue_3 = create(:issue, title: 'issue_3', project: project)
issue_4 = create(:issue, title: 'issue_4', project: project) issue_4 = create(:issue, title: 'issue_4', project: project)
merge_request_1 = create(:merge_request, source_project: project) merge_request_1 = create(:merge_request, source_project: project_2)
merge_request_1.labels << label_1 merge_request_1.labels << label_1_2
# Covers the case where Todo has more than one label # Covers the case where Todo has more than one label
issue_3.labels << label_1 issue_3.labels << label_1
...@@ -57,15 +61,14 @@ describe TodosFinder do ...@@ -57,15 +61,14 @@ describe TodosFinder do
todo_2 = create(:todo, user: user, project: project, target: issue_2) todo_2 = create(:todo, user: user, project: project, target: issue_2)
todo_3 = create(:todo, user: user, project: project, target: issue_3, created_at: 2.hours.ago) todo_3 = create(:todo, user: user, project: project, target: issue_3, created_at: 2.hours.ago)
todo_4 = create(:todo, user: user, project: project, target: issue_1) todo_4 = create(:todo, user: user, project: project, target: issue_1)
todo_5 = create(:todo, user: user, project: project, target: merge_request_1, created_at: 1.hour.ago) todo_5 = create(:todo, user: user, project: project_2, target: merge_request_1, created_at: 1.hour.ago)
project_2.add_developer(user)
todos = finder.new(user, { sort: 'priority' }).execute todos = finder.new(user, { sort: 'priority' }).execute
expect(todos.first).to eq(todo_3) puts todos.to_sql
expect(todos.second).to eq(todo_5) expect(todos).to eq([todo_3, todo_5, todo_4, todo_2, todo_1])
expect(todos.third).to eq(todo_4)
expect(todos.fourth).to eq(todo_2)
expect(todos.fifth).to eq(todo_1)
end end
end end
end end
......
...@@ -129,7 +129,7 @@ describe('AppComponent', () => { ...@@ -129,7 +129,7 @@ describe('AppComponent', () => {
vm.fetchGroups({}); vm.fetchGroups({});
setTimeout(() => { setTimeout(() => {
expect(vm.isLoading).toBeFalsy(); expect(vm.isLoading).toBe(false);
expect($.scrollTo).toHaveBeenCalledWith(0); expect($.scrollTo).toHaveBeenCalledWith(0);
expect(window.Flash).toHaveBeenCalledWith('An error occurred. Please try again.'); expect(window.Flash).toHaveBeenCalledWith('An error occurred. Please try again.');
done(); done();
...@@ -144,10 +144,10 @@ describe('AppComponent', () => { ...@@ -144,10 +144,10 @@ describe('AppComponent', () => {
spyOn(vm, 'updateGroups').and.callThrough(); spyOn(vm, 'updateGroups').and.callThrough();
vm.fetchAllGroups(); vm.fetchAllGroups();
expect(vm.isLoading).toBeTruthy(); expect(vm.isLoading).toBe(true);
expect(vm.fetchGroups).toHaveBeenCalled(); expect(vm.fetchGroups).toHaveBeenCalled();
setTimeout(() => { setTimeout(() => {
expect(vm.isLoading).toBeFalsy(); expect(vm.isLoading).toBe(false);
expect(vm.updateGroups).toHaveBeenCalled(); expect(vm.updateGroups).toHaveBeenCalled();
done(); done();
}, 0); }, 0);
...@@ -181,7 +181,7 @@ describe('AppComponent', () => { ...@@ -181,7 +181,7 @@ describe('AppComponent', () => {
spyOn($, 'scrollTo'); spyOn($, 'scrollTo');
vm.fetchPage(2, null, null, true); vm.fetchPage(2, null, null, true);
expect(vm.isLoading).toBeTruthy(); expect(vm.isLoading).toBe(true);
expect(vm.fetchGroups).toHaveBeenCalledWith({ expect(vm.fetchGroups).toHaveBeenCalledWith({
page: 2, page: 2,
filterGroupsBy: null, filterGroupsBy: null,
...@@ -190,7 +190,7 @@ describe('AppComponent', () => { ...@@ -190,7 +190,7 @@ describe('AppComponent', () => {
archived: true, archived: true,
}); });
setTimeout(() => { setTimeout(() => {
expect(vm.isLoading).toBeFalsy(); expect(vm.isLoading).toBe(false);
expect($.scrollTo).toHaveBeenCalledWith(0); expect($.scrollTo).toHaveBeenCalledWith(0);
expect(utils.mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String)); expect(utils.mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String));
expect(window.history.replaceState).toHaveBeenCalledWith({ expect(window.history.replaceState).toHaveBeenCalledWith({
...@@ -216,7 +216,7 @@ describe('AppComponent', () => { ...@@ -216,7 +216,7 @@ describe('AppComponent', () => {
spyOn(vm.store, 'setGroupChildren'); spyOn(vm.store, 'setGroupChildren');
vm.toggleChildren(groupItem); vm.toggleChildren(groupItem);
expect(groupItem.isChildrenLoading).toBeTruthy(); expect(groupItem.isChildrenLoading).toBe(true);
expect(vm.fetchGroups).toHaveBeenCalledWith({ expect(vm.fetchGroups).toHaveBeenCalledWith({
parentId: groupItem.id, parentId: groupItem.id,
}); });
...@@ -232,7 +232,7 @@ describe('AppComponent', () => { ...@@ -232,7 +232,7 @@ describe('AppComponent', () => {
vm.toggleChildren(groupItem); vm.toggleChildren(groupItem);
expect(vm.fetchGroups).not.toHaveBeenCalled(); expect(vm.fetchGroups).not.toHaveBeenCalled();
expect(groupItem.isOpen).toBeTruthy(); expect(groupItem.isOpen).toBe(true);
}); });
it('should collapse group if it is already expanded', () => { it('should collapse group if it is already expanded', () => {
...@@ -241,16 +241,16 @@ describe('AppComponent', () => { ...@@ -241,16 +241,16 @@ describe('AppComponent', () => {
vm.toggleChildren(groupItem); vm.toggleChildren(groupItem);
expect(vm.fetchGroups).not.toHaveBeenCalled(); expect(vm.fetchGroups).not.toHaveBeenCalled();
expect(groupItem.isOpen).toBeFalsy(); expect(groupItem.isOpen).toBe(false);
}); });
it('should set `isChildrenLoading` back to `false` if load request fails', (done) => { it('should set `isChildrenLoading` back to `false` if load request fails', (done) => {
spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise({}, true)); spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise({}, true));
vm.toggleChildren(groupItem); vm.toggleChildren(groupItem);
expect(groupItem.isChildrenLoading).toBeTruthy(); expect(groupItem.isChildrenLoading).toBe(true);
setTimeout(() => { setTimeout(() => {
expect(groupItem.isChildrenLoading).toBeFalsy(); expect(groupItem.isChildrenLoading).toBe(false);
done(); done();
}, 0); }, 0);
}); });
...@@ -268,10 +268,10 @@ describe('AppComponent', () => { ...@@ -268,10 +268,10 @@ describe('AppComponent', () => {
it('updates props which show modal confirmation dialog', () => { it('updates props which show modal confirmation dialog', () => {
const group = Object.assign({}, mockParentGroupItem); const group = Object.assign({}, mockParentGroupItem);
expect(vm.updateModal).toBeFalsy(); expect(vm.showModal).toBe(false);
expect(vm.groupLeaveConfirmationMessage).toBe(''); expect(vm.groupLeaveConfirmationMessage).toBe('');
vm.showLeaveGroupModal(group, mockParentGroupItem); vm.showLeaveGroupModal(group, mockParentGroupItem);
expect(vm.updateModal).toBeTruthy(); expect(vm.showModal).toBe(true);
expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`); expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`);
}); });
}); });
...@@ -280,9 +280,9 @@ describe('AppComponent', () => { ...@@ -280,9 +280,9 @@ describe('AppComponent', () => {
it('hides modal confirmation which is shown before leaving the group', () => { it('hides modal confirmation which is shown before leaving the group', () => {
const group = Object.assign({}, mockParentGroupItem); const group = Object.assign({}, mockParentGroupItem);
vm.showLeaveGroupModal(group, mockParentGroupItem); vm.showLeaveGroupModal(group, mockParentGroupItem);
expect(vm.updateModal).toBeTruthy(); expect(vm.showModal).toBe(true);
vm.hideLeaveGroupModal(); vm.hideLeaveGroupModal();
expect(vm.updateModal).toBeFalsy(); expect(vm.showModal).toBe(false);
}); });
}); });
...@@ -307,8 +307,8 @@ describe('AppComponent', () => { ...@@ -307,8 +307,8 @@ describe('AppComponent', () => {
spyOn($, 'scrollTo'); spyOn($, 'scrollTo');
vm.leaveGroup(); vm.leaveGroup();
expect(vm.updateModal).toBeFalsy(); expect(vm.showModal).toBe(false);
expect(vm.targetGroup.isBeingRemoved).toBeTruthy(); expect(vm.targetGroup.isBeingRemoved).toBe(true);
expect(vm.service.leaveGroup).toHaveBeenCalledWith(vm.targetGroup.leavePath); expect(vm.service.leaveGroup).toHaveBeenCalledWith(vm.targetGroup.leavePath);
setTimeout(() => { setTimeout(() => {
expect($.scrollTo).toHaveBeenCalledWith(0); expect($.scrollTo).toHaveBeenCalledWith(0);
...@@ -325,12 +325,12 @@ describe('AppComponent', () => { ...@@ -325,12 +325,12 @@ describe('AppComponent', () => {
spyOn(window, 'Flash'); spyOn(window, 'Flash');
vm.leaveGroup(); vm.leaveGroup();
expect(vm.targetGroup.isBeingRemoved).toBeTruthy(); expect(vm.targetGroup.isBeingRemoved).toBe(true);
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath); expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => { setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled(); expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message); expect(window.Flash).toHaveBeenCalledWith(message);
expect(vm.targetGroup.isBeingRemoved).toBeFalsy(); expect(vm.targetGroup.isBeingRemoved).toBe(false);
done(); done();
}, 0); }, 0);
}); });
...@@ -342,12 +342,12 @@ describe('AppComponent', () => { ...@@ -342,12 +342,12 @@ describe('AppComponent', () => {
spyOn(window, 'Flash'); spyOn(window, 'Flash');
vm.leaveGroup(childGroupItem, groupItem); vm.leaveGroup(childGroupItem, groupItem);
expect(vm.targetGroup.isBeingRemoved).toBeTruthy(); expect(vm.targetGroup.isBeingRemoved).toBe(true);
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath); expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => { setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled(); expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message); expect(window.Flash).toHaveBeenCalledWith(message);
expect(vm.targetGroup.isBeingRemoved).toBeFalsy(); expect(vm.targetGroup.isBeingRemoved).toBe(false);
done(); done();
}, 0); }, 0);
}); });
...@@ -379,10 +379,10 @@ describe('AppComponent', () => { ...@@ -379,10 +379,10 @@ describe('AppComponent', () => {
it('should set `isSearchEmpty` prop based on groups count', () => { it('should set `isSearchEmpty` prop based on groups count', () => {
vm.updateGroups(mockGroups); vm.updateGroups(mockGroups);
expect(vm.isSearchEmpty).toBeFalsy(); expect(vm.isSearchEmpty).toBe(false);
vm.updateGroups([]); vm.updateGroups([]);
expect(vm.isSearchEmpty).toBeTruthy(); expect(vm.isSearchEmpty).toBe(true);
}); });
}); });
}); });
...@@ -473,13 +473,16 @@ describe('AppComponent', () => { ...@@ -473,13 +473,16 @@ describe('AppComponent', () => {
}); });
}); });
it('renders modal confirmation dialog', () => { it('renders modal confirmation dialog', (done) => {
vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?'; vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?';
vm.updateModal = true; vm.showModal = true;
const modalDialogEl = vm.$el.querySelector('.modal'); Vue.nextTick(() => {
expect(modalDialogEl).not.toBe(null); const modalDialogEl = vm.$el.querySelector('.modal');
expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?'); expect(modalDialogEl).not.toBe(null);
expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave'); expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
done();
});
}); });
}); });
}); });
...@@ -59,5 +59,69 @@ describe Gitlab::Git::Branch, seed_helper: true do ...@@ -59,5 +59,69 @@ describe Gitlab::Git::Branch, seed_helper: true do
it { expect(branch.dereferenced_target.sha).to eq(SeedRepo::LastCommit::ID) } it { expect(branch.dereferenced_target.sha).to eq(SeedRepo::LastCommit::ID) }
end end
context 'with active, stale and future branches' do
let(:repository) do
Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
end
let(:user) { create(:user) }
let(:committer) do
Gitlab::Git.committer_hash(email: user.email, name: user.name)
end
let(:params) do
parents = [repository.rugged.head.target]
tree = parents.first.tree
{
message: 'commit message',
author: committer,
committer: committer,
tree: tree,
parents: parents
}
end
let(:stale_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago - 5.days) { create_commit } }
let(:active_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago + 5.days) { create_commit } }
let(:future_sha) { Timecop.freeze(100.days.since) { create_commit } }
before do
repository.create_branch('stale-1', stale_sha)
repository.create_branch('active-1', active_sha)
repository.create_branch('future-1', future_sha)
end
after do
ensure_seeds
end
describe 'examine if the branch is active or stale' do
let(:stale_branch) { repository.find_branch('stale-1') }
let(:active_branch) { repository.find_branch('active-1') }
let(:future_branch) { repository.find_branch('future-1') }
describe '#active?' do
it { expect(stale_branch.active?).to be_falsey }
it { expect(active_branch.active?).to be_truthy }
it { expect(future_branch.active?).to be_truthy }
end
describe '#stale?' do
it { expect(stale_branch.stale?).to be_truthy }
it { expect(active_branch.stale?).to be_falsey }
it { expect(future_branch.stale?).to be_falsey }
end
describe '#state' do
it { expect(stale_branch.state).to eq(:stale) }
it { expect(active_branch.state).to eq(:active) }
it { expect(future_branch.state).to eq(:active) }
end
end
end
it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) } it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) }
def create_commit
repository.create_commit(params.merge(committer: committer.merge(time: Time.now)))
end
end end
...@@ -34,6 +34,8 @@ describe Clusters::Applications::Runner do ...@@ -34,6 +34,8 @@ describe Clusters::Applications::Runner do
is_expected.to include('checkInterval') is_expected.to include('checkInterval')
is_expected.to include('rbac') is_expected.to include('rbac')
is_expected.to include('runners') is_expected.to include('runners')
is_expected.to include('privileged: true')
is_expected.to include('image: ubuntu:16.04')
is_expected.to include('resources') is_expected.to include('resources')
is_expected.to include("runnerToken: #{ci_runner.token}") is_expected.to include("runnerToken: #{ci_runner.token}")
is_expected.to include("gitlabUrl: #{Gitlab::Routing.url_helpers.root_url}") is_expected.to include("gitlabUrl: #{Gitlab::Routing.url_helpers.root_url}")
...@@ -61,5 +63,33 @@ describe Clusters::Applications::Runner do ...@@ -61,5 +63,33 @@ describe Clusters::Applications::Runner do
expect(gitlab_runner.runner).not_to be_nil expect(gitlab_runner.runner).not_to be_nil
end end
end end
context 'with duplicated values on vendor/runner/values.yaml' do
let(:values) do
{
"concurrent" => 4,
"checkInterval" => 3,
"rbac" => {
"create" => false
},
"clusterWideAccess" => false,
"runners" => {
"privileged" => false,
"image" => "ubuntu:16.04",
"builds" => {},
"services" => {},
"helpers" => {}
}
}
end
before do
allow(gitlab_runner).to receive(:chart_values).and_return(values)
end
it 'should overwrite values.yaml' do
is_expected.to include("privileged: #{gitlab_runner.privileged}")
end
end
end end
end end
...@@ -1103,11 +1103,13 @@ describe API::Runner do ...@@ -1103,11 +1103,13 @@ describe API::Runner do
context 'posts artifacts file and metadata file' do context 'posts artifacts file and metadata file' do
let!(:artifacts) { file_upload } let!(:artifacts) { file_upload }
let!(:artifacts_sha256) { Digest::SHA256.file(artifacts.path).hexdigest }
let!(:metadata) { file_upload2 } let!(:metadata) { file_upload2 }
let(:stored_artifacts_file) { job.reload.artifacts_file.file } let(:stored_artifacts_file) { job.reload.artifacts_file.file }
let(:stored_metadata_file) { job.reload.artifacts_metadata.file } let(:stored_metadata_file) { job.reload.artifacts_metadata.file }
let(:stored_artifacts_size) { job.reload.artifacts_size } let(:stored_artifacts_size) { job.reload.artifacts_size }
let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 }
before do before do
post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token) post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token)
...@@ -1117,6 +1119,7 @@ describe API::Runner do ...@@ -1117,6 +1119,7 @@ describe API::Runner do
let(:post_data) do let(:post_data) do
{ 'file.path' => artifacts.path, { 'file.path' => artifacts.path,
'file.name' => artifacts.original_filename, 'file.name' => artifacts.original_filename,
'file.sha256' => artifacts_sha256,
'metadata.path' => metadata.path, 'metadata.path' => metadata.path,
'metadata.name' => metadata.original_filename } 'metadata.name' => metadata.original_filename }
end end
...@@ -1126,6 +1129,7 @@ describe API::Runner do ...@@ -1126,6 +1129,7 @@ describe API::Runner do
expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
expect(stored_artifacts_size).to eq(72821) expect(stored_artifacts_size).to eq(72821)
expect(stored_artifacts_sha256).to eq(artifacts_sha256)
end end
end end
......
...@@ -15,10 +15,8 @@ rbac: ...@@ -15,10 +15,8 @@ rbac:
clusterWideAccess: false clusterWideAccess: false
## Configuration for the Pods that that the runner launches for each new job ## Configuration for the Pods that that the runner launches for each new job
##
runners: runners:
image: ubuntu:16.04 image: ubuntu:16.04
privileged: false
builds: {} builds: {}
services: {} services: {}
helpers: {} helpers: {}
......
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