Commit 7b3e7150 authored by Sean Packham's avatar Sean Packham

Merge branch 'master' into add-university-content

parents 1feffcee 23ed83a1
......@@ -12,7 +12,7 @@ variables:
RSPEC_RETRY_RETRY_COUNT: "3"
RAILS_ENV: "test"
SIMPLECOV: "true"
USE_DB: "true"
SETUP_DB: "true"
USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20"
PHANTOMJS_VERSION: "2.1.1"
......@@ -23,7 +23,7 @@ before_script:
- bundle --version
- '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"'
- retry gem install knapsack
- '[ "$USE_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate'
- '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate'
stages:
- prepare
......@@ -35,7 +35,7 @@ stages:
.knapsack-state: &knapsack-state
services: []
variables:
USE_DB: "false"
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
cache:
key: "knapsack"
......@@ -196,7 +196,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
.ruby-static-analysis: &ruby-static-analysis
variables:
SIMPLECOV: "false"
USE_DB: "false"
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
.exec: &exec
......@@ -224,6 +224,23 @@ rake db:migrate:reset:
script:
- rake db:migrate:reset
rake db:seed_fu:
stage: test
<<: *use-db
variables:
SIZE: "1"
SETUP_DB: "false"
RAILS_ENV: "development"
script:
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
/home/git/repositories/gitlab-org/gitlab-test.git
- bundle exec rake db:setup db:seed_fu
artifacts:
when: on_failure
expire_in: 1d
paths:
- log/development.log
teaspoon:
stage: test
<<: *use-db
......@@ -272,7 +289,7 @@ coverage:
stage: post-test
services: []
variables:
USE_DB: "false"
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
script:
- bundle exec scripts/merge-simplecov
......@@ -288,7 +305,7 @@ coverage:
notify:slack:
stage: post-test
variables:
USE_DB: "false"
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
script:
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.12.0 (unreleased)
v 8.13.0 (unreleased)
- Speed-up group milestones show page
v 8.12.2 (unreleased)
v 8.12.1
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
- Fix issue with search filter labels not displaying
v 8.12.0
- Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
- Only check :can_resolve permission if the note is resolvable
- Bump fog-aws to v0.11.0 to support ap-south-1 region
......@@ -81,6 +90,7 @@ v 8.12.0 (unreleased)
- Fix markdown anchor icon interaction (ClemMakesApps)
- Test migration paths from 8.5 until current release !4874
- Replace animateEmoji timeout with eventListener (ClemMakesApps)
- Show badges in Milestone tabs. !5946 (Dan Rowden)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
......
......@@ -322,10 +322,6 @@ group :test do
gem 'timecop', '~> 0.8.0'
end
group :production do
gem 'gitlab_meta', '7.0'
end
gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.3.0'
......
......@@ -284,7 +284,6 @@ GEM
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
rugged (~> 0.24.0)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9)
omniauth (~> 1.0)
......@@ -864,7 +863,6 @@ DEPENDENCIES
github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_git (~> 10.6.6)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2)
......@@ -991,4 +989,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.13.0
1.13.1
......@@ -7,7 +7,7 @@
## Canonical source
The cannonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
## Open source software to collaborate on code
......
((global) => {
const COOKIE_NAME = 'cycle_analytics_help_dismissed';
const store = gl.cycleAnalyticsStore = {
isLoading: true,
hasError: false,
isHelpDismissed: $.cookie(COOKIE_NAME),
analytics: {}
};
gl.CycleAnalytics = class CycleAnalytics {
constructor() {
const that = this;
this.isHelpDismissed = $.cookie(COOKIE_NAME);
this.vue = new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
created: this.fetchData(),
data: this.decorateData({ isLoading: true }),
data: store,
methods: {
dismissLanding() {
that.dismissLanding();
......@@ -21,6 +26,7 @@
}
fetchData(options) {
store.isLoading = true;
options = options || { startDate: 30 };
$.ajax({
......@@ -30,22 +36,20 @@
contentType: 'application/json',
data: { start_date: options.startDate }
}).done((data) => {
this.vue.$data = this.decorateData(data);
this.decorateData(data);
this.initDropdown();
})
.error((data) => {
this.handleError(data);
})
.always(() => {
this.vue.isLoading = false;
store.isLoading = false;
})
}
decorateData(data) {
data.summary = data.summary || [];
data.stats = data.stats || [];
data.isHelpDismissed = this.isHelpDismissed;
data.isLoading = data.isLoading || false;
data.summary.forEach((item) => {
item.value = item.value || '-';
......@@ -53,23 +57,21 @@
data.stats.forEach((item) => {
item.value = item.value || '- - -';
})
});
return data;
store.analytics = data;
}
handleError(data) {
this.vue.$data = {
hasError: true,
isHelpDismissed: this.isHelpDismissed
};
store.hasError = true;
new Flash('There was an error while fetching cycle analytics data.', 'alert');
}
dismissLanding() {
this.vue.isHelpDismissed = true;
$.cookie(COOKIE_NAME, true);
store.isHelpDismissed = true;
$.cookie(COOKIE_NAME, true, {
path: gon.relative_url_root || '/'
});
}
initDropdown() {
......@@ -82,7 +84,6 @@
const value = $target.data('value');
$label.text($target.text().trim());
this.vue.isLoading = true;
this.fetchData({ startDate: value });
})
}
......
......@@ -3,7 +3,6 @@
margin: 0;
margin-bottom: $gl-padding;
font-size: 14px;
z-index: 100;
.flash-notice {
@extend .alert;
......@@ -41,4 +40,3 @@
}
}
}
......@@ -269,6 +269,12 @@ $calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: $gray-light;
/*
* Cycle Analytics
*/
$cycle-analytics-box-padding: 30px;
$cycle-analytics-box-text-color: #8c8c8c;
/*
* Personal Access Tokens
*/
......
#cycle-analytics {
margin: 24px auto 0;
width: 800px;
max-width: 800px;
position: relative;
.panel {
......@@ -9,10 +9,18 @@
padding: 24px 0;
border-bottom: none;
position: relative;
@media (max-width: $screen-sm-min) {
padding: 6px 0 24px;
}
}
.column {
text-align: center;
@media (max-width: $screen-sm-min) {
padding: 15px 0;
}
.header {
font-size: 30px;
......@@ -28,11 +36,14 @@
&:last-child {
text-align: right;
@media (max-width: $screen-sm-min) {
text-align: center;
}
}
}
.dropdown {
position: relative;
top: 13px;
}
}
......@@ -40,7 +51,7 @@
.bordered-box {
border: 1px solid $border-color;
@include border-radius($border-radius-default);
position: relative;
}
.content-list {
......@@ -60,9 +71,15 @@
line-height: 19px;
font-size: 15px;
font-weight: 600;
color: $gl-title-color;
}
&:text {
color: #8c8c8c;
&.text {
color: $layout-link-gray;
&.value-col {
color: $gl-title-color;
}
}
}
}
......@@ -71,7 +88,9 @@
text-align: right;
span {
line-height: 42px;
position: relative;
vertical-align: middle;
top: 3px;
}
}
}
......@@ -82,21 +101,25 @@
.dismiss-icon {
position: absolute;
right: $gl-padding;
right: $cycle-analytics-box-padding;
cursor: pointer;
color: #b2b2b2;
}
svg {
margin: 0 20px;
float: left;
width: 136px;
height: 136px;
.svg-container {
text-align: center;
svg {
width: 136px;
height: 136px;
}
}
.inner-content {
width: 480px;
float: left;
@media (max-width: $screen-sm-min) {
padding: 0 28px;
text-align: center;
}
h4 {
color: $gl-text-color;
......@@ -104,7 +127,7 @@
}
p {
color: #8c8c8c;
color: $cycle-analytics-box-text-color;
margin-bottom: $gl-padding;
}
}
......
......@@ -2,13 +2,17 @@
max-width: 90%;
}
li.milestone {
h4 {
font-weight: bold;
}
.milestones {
.milestone {
padding: 10px 16px;
h4 {
font-weight: bold;
}
.progress {
height: 6px;
.progress {
height: 6px;
}
}
}
......@@ -64,3 +68,14 @@ li.milestone {
border-bottom: 1px solid $border-color;
padding: 20px 0;
}
@media (max-width: $screen-sm-min) {
.milestone-actions {
@include clearfix();
padding-top: $gl-vert-padding;
.btn:first-child {
margin-left: 0;
}
}
}
......@@ -177,6 +177,10 @@
border-bottom: 2px solid $border-color;
}
}
a {
display: block;
}
}
}
......
......@@ -35,6 +35,30 @@ module MilestonesHelper
milestone.issues.with_label(label.title).send(state).size
end
# Returns count of milestones for different states
# Uses explicit hash keys as the 'opened' state URL params differs from the db value
# and we need to add the total
def milestone_counts(milestones)
counts = milestones.reorder(nil).group(:state).count
{
opened: counts['active'] || 0,
closed: counts['closed'] || 0,
all: counts.values.sum || 0
}
end
# Show 'active' class if provided GET param matches check
# `or_blank` allows the function to return 'active' when given an empty param
# Could be refactored to be simpler but that may make it harder to read
def milestone_class_for_state(param, check, match_blank_param = false)
if match_blank_param
'active' if param.blank? || param == check
else
'active' if param == check
end
end
def milestone_progress_bar(milestone)
options = {
class: 'progress-bar progress-bar-success',
......
......@@ -91,7 +91,7 @@ module Ci
sha: build.sha,
ref: build.ref,
tag: build.tag,
options: build.options[:environment],
options: build.options.to_h[:environment],
variables: build.variables)
service.execute(build)
end
......
......@@ -8,7 +8,8 @@ class GlobalMilestone
milestones = milestones.group_by(&:title)
milestones.map do |title, milestones|
new(title, milestones)
milestones_relation = Milestone.where(id: milestones.map(&:id))
new(title, milestones_relation)
end
end
......@@ -31,7 +32,7 @@ class GlobalMilestone
end
def projects
@projects ||= Project.for_milestones(milestones.map(&:id))
@projects ||= Project.for_milestones(milestones.select(:id))
end
def state
......@@ -53,19 +54,19 @@ class GlobalMilestone
end
def issues
@issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
@issues ||= Issue.of_milestones(milestones.select(:id)).includes(:project, :assignee, :labels)
end
def merge_requests
@merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project)
@merge_requests ||= MergeRequest.of_milestones(milestones.select(:id)).includes(:target_project, :assignee, :labels)
end
def participants
@participants ||= milestones.map(&:participants).flatten.compact.uniq
@participants ||= milestones.includes(:participants).map(&:participants).flatten.compact.uniq
end
def labels
@labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
@labels ||= GlobalLabel.build_collection(milestones.includes(:labels).map(&:labels).flatten)
.sort_by!(&:title)
end
......
......@@ -2,18 +2,20 @@
- page_title "Cycle Analytics"
= render "projects/pipelines/head"
#cycle-analytics{"v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
.bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"}
= icon('times', class: 'dismiss-icon', "@click": "dismissLanding()")
= custom_icon('icon_cycle_analytics_splash')
.inner-content
%h4
Introducing Cycle Analytics
%p
Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
.row
.col-sm-3.col-xs-12.svg-container
= custom_icon('icon_cycle_analytics_splash')
.col-sm-8.col-xs-12.inner-content
%h4
Introducing Cycle Analytics
%p
Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
= link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
= link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
= icon("spinner spin", "v-show" => "isLoading")
......@@ -25,11 +27,11 @@
.content-block
.container-fluid
.row
.col-xs-3.column{"v-for" => "item in summary"}
.col-sm-3.col-xs-12.column{"v-for" => "item in analytics.summary"}
%h3.header {{item.value}}
%p.text {{item.title}}
.col-xs-3.column
.col-sm-3.col-xs-12.column
.dropdown.inline.js-ca-dropdown
%button.dropdown-menu-toggle{"data-toggle" => "dropdown", :type => "button"}
%span.dropdown-label Last 30 days
......@@ -44,14 +46,14 @@
.bordered-box
%ul.content-list
%li{"v-for" => "item in stats"}
%li{"v-for" => "item in analytics.stats"}
.container-fluid
.row
.col-xs-10.title-col
.col-xs-8.title-col
%p.title
{{item.title}}
%p.text
{{item.description}}
.col-xs-2.value-col
.col-xs-4.value-col
%span
{{item.value}}
- if @project
- counts = milestone_counts(@project.milestones)
%ul.nav-links
%li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
%li{class: milestone_class_for_state(params[:state], 'opened', true)}
= link_to milestones_filter_path(state: 'opened') do
Open
%li{class: ("active" if params[:state] == 'closed')}
- if @project
%span.badge #{counts[:opened]}
%li{class: milestone_class_for_state(params[:state], 'closed')}
= link_to milestones_filter_path(state: 'closed') do
Closed
%li{class: ("active" if params[:state] == 'all')}
- if @project
%span.badge #{counts[:closed]}
%li{class: milestone_class_for_state(params[:state], 'all')}
= link_to milestones_filter_path(state: 'all') do
All
- if @project
%span.badge #{counts[:all]}
......@@ -33,7 +33,7 @@
- if @project
.row
.col-sm-6= render('shared/milestone_expired', milestone: milestone)
.col-sm-6
.col-sm-6.milestone-actions
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do
Edit
......
......@@ -3,11 +3,11 @@ require 'sidekiq/testing'
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
project_urls = [
'https://github.com/documentcloud/underscore.git',
'https://gitlab.com/gitlab-org/gitlab-test.git',
'https://gitlab.com/gitlab-org/gitlab-ce.git',
'https://gitlab.com/gitlab-org/gitlab-ci.git',
'https://gitlab.com/gitlab-org/gitlab-shell.git',
'https://gitlab.com/gitlab-org/gitlab-test.git',
'https://github.com/documentcloud/underscore.git',
'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.git',
......@@ -38,12 +38,7 @@ Sidekiq::Testing.inline! do
]
# You can specify how many projects you need during seed execution
size = if ENV['SIZE'].present?
ENV['SIZE'].to_i
else
8
end
size = ENV['SIZE'].present? ? ENV['SIZE'].to_i : 8
project_urls.first(size).each_with_index do |url, i|
group_path, project_path = url.split('/')[-2..-1]
......
class AddIndexToLabelsTitle < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def change
add_concurrent_index :labels, :title
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160915042921) do
ActiveRecord::Schema.define(version: 20160920160832) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -521,6 +521,7 @@ ActiveRecord::Schema.define(version: 20160915042921) do
add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
add_index "labels", ["title"], name: "index_labels_on_title", using: :btree
create_table "lfs_objects", force: :cascade do |t|
t.string "oid", null: false
......
......@@ -10,6 +10,7 @@ following locations:
- [Award Emoji](award_emoji.md)
- [Branches](branches.md)
- [Broadcast Messages](broadcast_messages.md)
- [Builds](builds.md)
- [Build Triggers](build_triggers.md)
- [Build Variables](build_variables.md)
......
......@@ -13,7 +13,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
- [Test a Scala application](test-scala-application.md)
- [Using `dpl` as deployment tool](deployment/README.md)
- [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
- [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
[gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml
......@@ -44,7 +44,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`.
| **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project |
| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the build is run |
| **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returnes the address of the registry tied to the specific project |
| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used |
| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab |
| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
......
......@@ -7,7 +7,7 @@ module Banzai
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
def whitelist
whitelist = super.dup
whitelist = super
customize_whitelist(whitelist)
......@@ -42,58 +42,58 @@ module Banzai
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
whitelist[:transformers] = whitelist[:transformers].dup
# ...but then remove links with unsafe protocols
whitelist[:transformers].push(remove_unsafe_links)
whitelist[:transformers].push(self.class.remove_unsafe_links)
# Remove `rel` attribute from `a` elements
whitelist[:transformers].push(remove_rel)
whitelist[:transformers].push(self.class.remove_rel)
# Remove `class` attribute from non-highlight spans
whitelist[:transformers].push(clean_spans)
whitelist[:transformers].push(self.class.clean_spans)
whitelist
end
def remove_unsafe_links
lambda do |env|
node = env[:node]
class << self
def remove_unsafe_links
lambda do |env|
node = env[:node]
return unless node.name == 'a'
return unless node.has_attribute?('href')
return unless node.name == 'a'
return unless node.has_attribute?('href')
begin
uri = Addressable::URI.parse(node['href'])
uri.scheme = uri.scheme.strip.downcase if uri.scheme
begin
uri = Addressable::URI.parse(node['href'])
uri.scheme = uri.scheme.strip.downcase if uri.scheme
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
rescue Addressable::URI::InvalidURIError
node.remove_attribute('href')
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
rescue Addressable::URI::InvalidURIError
node.remove_attribute('href')
end
end
end
end
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
env[:node].remove_attribute('rel')
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
env[:node].remove_attribute('rel')
end
end
end
end
def clean_spans
lambda do |env|
node = env[:node]
def clean_spans
lambda do |env|
node = env[:node]
return unless node.name == 'span'
return unless node.has_attribute?('class')
return unless node.name == 'span'
return unless node.has_attribute?('class')
unless has_ancestor?(node, 'pre')
node.remove_attribute('class')
end
unless node.ancestors.any? { |n| n.name.casecmp('pre').zero? }
node.remove_attribute('class')
end
{ node_whitelist: [node] }
{ node_whitelist: [node] }
end
end
end
end
......
......@@ -59,10 +59,8 @@ module Gitlab
# When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
active_db_connection = ActiveRecord::Base.connection.active? rescue false
ENV['USE_DB'] != 'false' &&
active_db_connection &&
ActiveRecord::Base.connection.table_exists?('application_settings')
ActiveRecord::Base.connection.table_exists?('application_settings')
rescue ActiveRecord::NoDatabaseError
false
end
......
......@@ -9,19 +9,22 @@ module Gitlab
SIDEKIQ_NAMESPACE = 'resque:gitlab'
MAILROOM_NAMESPACE = 'mail_room:gitlab'
DEFAULT_REDIS_URL = 'redis://localhost:6379'
CONFIG_FILE = File.expand_path('../../config/resque.yml', __dir__)
# To be thread-safe we must be careful when writing the class instance
# variables @url and @pool. Because @pool depends on @url we need two
# variables @_raw_config and @pool. Because @pool depends on @_raw_config we need two
# mutexes to prevent deadlock.
PARAMS_MUTEX = Mutex.new
RAW_CONFIG_MUTEX = Mutex.new
POOL_MUTEX = Mutex.new
private_constant :PARAMS_MUTEX, :POOL_MUTEX
private_constant :RAW_CONFIG_MUTEX, :POOL_MUTEX
class << self
# Do NOT cache in an instance variable. Result may be mutated by caller.
def params
@params || PARAMS_MUTEX.synchronize { @params = new.params }
new.params
end
# Do NOT cache in an instance variable. Result may be mutated by caller.
# @deprecated Use .params instead to get sentinel support
def url
new.url
......@@ -36,8 +39,17 @@ module Gitlab
@pool.with { |redis| yield redis }
end
def reset_params!
@params = nil
def _raw_config
return @_raw_config if defined?(@_raw_config)
RAW_CONFIG_MUTEX.synchronize do
begin
@_raw_config = File.read(CONFIG_FILE).freeze
rescue Errno::ENOENT
@_raw_config = false
end
end
@_raw_config
end
end
......@@ -83,12 +95,7 @@ module Gitlab
end
def fetch_config
file = config_file
File.exist?(file) ? YAML.load_file(file)[@rails_env] : false
end
def config_file
File.expand_path('../../../config/resque.yml', __FILE__)
self.class._raw_config ? YAML.load(self.class._raw_config)[@rails_env] : false
end
end
end
......@@ -60,7 +60,7 @@ module Gitlab
def send_git_diff(repository, diff_refs)
params = {
'RepoPath' => repository.path_to_repo,
'ShaFrom' => diff_refs.start_sha,
'ShaFrom' => diff_refs.base_sha,
'ShaTo' => diff_refs.head_sha
}
......@@ -73,7 +73,7 @@ module Gitlab
def send_git_patch(repository, diff_refs)
params = {
'RepoPath' => repository.path_to_repo,
'ShaFrom' => diff_refs.start_sha,
'ShaFrom' => diff_refs.base_sha,
'ShaTo' => diff_refs.head_sha
}
......@@ -107,15 +107,15 @@ module Gitlab
bytes
end
end
def write_secret
bytes = SecureRandom.random_bytes(SECRET_LENGTH)
File.open(secret_path, 'w:BINARY', 0600) do |f|
File.open(secret_path, 'w:BINARY', 0600) do |f|
f.chmod(0600)
f.write(Base64.strict_encode64(bytes))
end
end
def verify_api_request!(request_headers)
JWT.decode(
request_headers[INTERNAL_API_REQUEST_HEADER],
......@@ -128,7 +128,7 @@ module Gitlab
def secret_path
Rails.root.join('.gitlab_workhorse_secret')
end
protected
def encode(hash)
......
......@@ -3,10 +3,15 @@ FactoryGirl.define do
title
project
trait :active do
state "active"
end
trait :closed do
state :closed
state "closed"
end
factory :active_milestone, traits: [:active]
factory :closed_milestone, traits: [:closed]
end
end
require 'spec_helper'
describe MilestonesHelper do
describe '#milestone_counts' do
let(:project) { FactoryGirl.create(:project) }
let(:counts) { helper.milestone_counts(project.milestones) }
context 'when there are milestones' do
let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
let!(:milestone_3) { FactoryGirl.create(:closed_milestone, project: project) }
it 'returns the correct counts' do
expect(counts).to eq(opened: 2, closed: 1, all: 3)
end
end
context 'when there are only milestones of one type' do
let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
it 'returns the correct counts' do
expect(counts).to eq(opened: 2, closed: 0, all: 2)
end
end
context 'when there are no milestones' do
it 'returns the correct counts' do
expect(counts).to eq(opened: 0, closed: 0, all: 0)
end
end
end
end
......@@ -3,19 +3,27 @@ require 'spec_helper'
describe Gitlab::Redis do
let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s }
before(:each) { described_class.reset_params! }
after(:each) { described_class.reset_params! }
before(:each) { clear_raw_config }
after(:each) { clear_raw_config }
describe '.params' do
subject { described_class.params }
it 'withstands mutation' do
params1 = described_class.params
params2 = described_class.params
params1[:foo] = :bar
expect(params2).not_to have_key(:foo)
end
context 'when url contains unix socket reference' do
let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_socket.yml').to_s }
let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_socket.yml').to_s }
context 'with old format' do
it 'returns path key instead' do
expect_any_instance_of(described_class).to receive(:config_file) { config_old }
stub_const("#{described_class}::CONFIG_FILE", config_old)
is_expected.to include(path: '/path/to/old/redis.sock')
is_expected.not_to have_key(:url)
......@@ -24,7 +32,7 @@ describe Gitlab::Redis do
context 'with new format' do
it 'returns path key instead' do
expect_any_instance_of(described_class).to receive(:config_file) { config_new }
stub_const("#{described_class}::CONFIG_FILE", config_new)
is_expected.to include(path: '/path/to/redis.sock')
is_expected.not_to have_key(:url)
......@@ -38,7 +46,7 @@ describe Gitlab::Redis do
context 'with old format' do
it 'returns hash with host, port, db, and password' do
expect_any_instance_of(described_class).to receive(:config_file) { config_old }
stub_const("#{described_class}::CONFIG_FILE", config_old)
is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
......@@ -47,7 +55,7 @@ describe Gitlab::Redis do
context 'with new format' do
it 'returns hash with host, port, db, and password' do
expect_any_instance_of(described_class).to receive(:config_file) { config_new }
stub_const("#{described_class}::CONFIG_FILE", config_new)
is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
......@@ -56,6 +64,30 @@ describe Gitlab::Redis do
end
end
describe '.url' do
it 'withstands mutation' do
url1 = described_class.url
url2 = described_class.url
url1 << 'foobar'
expect(url2).not_to end_with('foobar')
end
end
describe '._raw_config' do
subject { described_class._raw_config }
it 'should be frozen' do
expect(subject).to be_frozen
end
it 'returns false when the file does not exist' do
stub_const("#{described_class}::CONFIG_FILE", '/var/empty/doesnotexist')
expect(subject).to eq(false)
end
end
describe '#raw_config_hash' do
it 'returns default redis url when no config file is present' do
expect(subject).to receive(:fetch_config) { false }
......@@ -71,9 +103,15 @@ describe Gitlab::Redis do
describe '#fetch_config' do
it 'returns false when no config file is present' do
allow(File).to receive(:exist?).with(redis_config) { false }
allow(described_class).to receive(:_raw_config) { false }
expect(subject.send(:fetch_config)).to be_falsey
end
end
def clear_raw_config
described_class.remove_instance_variable(:@_raw_config)
rescue NameError
# raised if @_raw_config was not set; ignore
end
end
require 'spec_helper'
describe Gitlab::Workhorse, lib: true do
let(:project) { create(:project) }
let(:subject) { Gitlab::Workhorse }
let(:project) { create(:project) }
let(:repository) { project.repository }
def decode_workhorse_header(array)
key, value = array
command, encoded_params = value.split(":")
params = JSON.parse(Base64.urlsafe_decode64(encoded_params))
[key, command, params]
end
describe ".send_git_archive" do
context "when the repository doesn't have an archive file path" do
......@@ -11,11 +19,37 @@ describe Gitlab::Workhorse, lib: true do
end
it "raises an error" do
expect { subject.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError)
expect { described_class.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError)
end
end
end
describe '.send_git_patch' do
let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
subject { described_class.send_git_patch(repository, diff_refs) }
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("git-format-patch")
expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
end
end
describe '.send_git_diff' do
let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
subject { described_class.send_git_patch(repository, diff_refs) }
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("git-format-patch")
expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
end
end
describe ".secret" do
subject { described_class.secret }
......
......@@ -50,8 +50,9 @@ describe GlobalMilestone, models: true do
milestone1_project2,
milestone1_project3,
]
milestones_relation = Milestone.where(id: milestones.map(&:id))
@global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones)
@global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones_relation)
end
it 'has exactly one group milestone' do
......@@ -67,7 +68,7 @@ describe GlobalMilestone, models: true do
let(:milestone) { create(:milestone, title: "git / test", project: project1) }
it 'strips out slashes and spaces' do
global_milestone = GlobalMilestone.new(milestone.title, [milestone])
global_milestone = GlobalMilestone.new(milestone.title, Milestone.where(id: milestone.id))
expect(global_milestone.safe_title).to eq('git-test')
end
......
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