Merge remote-tracking branch 'upstream/master' into new-issue-by-email
* upstream/master: (38 commits) Remove useless new route Update gitlab-shell version to 3.2.1 in the 8.9->8.10 update guide Fix typo in Elixir CI template Add a spec for access_for_user_ids Fix typo in comment Rubocop offenses Optimize the invited group link access level check Incorporate review comments Optimize maximum user access level lookup in loading of notes Fix missing schema update for 20160722221922 Whitelist 'Simplified BSD' license Fix a bug where forking a project from a repository storage to another would fail Remove inline scripts from import pages. Make branches sortable without push permission (!5462) Profile requests when a header is passed Upgrade database_cleaner from 1.4.1 to 1.5.3. Show release notes in tag list Fix expand all diffs button in compare view Add route for Import::GithubController#new Update CHANGELOG ...
This commit is contained in:
commit
a1f08a76b5
27
CHANGELOG
27
CHANGELOG
|
@ -1,23 +1,40 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.11.0 (unreleased)
|
||||
- Remove magic comments (`# encoding: UTF-8`) from Ruby files !5456 (winniehell)
|
||||
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
|
||||
- Fix CI status icon link underline (ClemMakesApps)
|
||||
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
|
||||
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
|
||||
- Optimize maximum user access level lookup in loading of notes
|
||||
- Limit git rev-list output count to one in forced push check
|
||||
- Add green outline to New Branch button !5447 (winniehell)
|
||||
- Clean up unused routes (Josef Strzibny)
|
||||
- Add green outline to New Branch button. !5447 (winniehell)
|
||||
- Retrieve rendered HTML from cache in one request
|
||||
- Nokogiri's various parsing methods are now instrumented
|
||||
- Make fork counter always clickable !5463 (winniehell)
|
||||
- Load project invited groups and members eagerly in ProjectTeam#fetch_members
|
||||
- Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
|
||||
- Make fork counter always clickable. !5463 (winniehell)
|
||||
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
|
||||
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
|
||||
- Make branches sortable without push permission !5462 (winniehell)
|
||||
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Add ES6 gem
|
||||
- Add the `sprockets-es6` gem
|
||||
- Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Profile requests when a header is passed
|
||||
|
||||
v 8.10.2 (unreleased)
|
||||
- User can now search branches by name. !5144
|
||||
- Add ENV variable to skip repository storages validations
|
||||
- Fix backup restore. !5459
|
||||
- Rescue Rugged::OSError (lock exists) when creating references. !5497
|
||||
- Disable MySQL foreign key checks before dropping all tables. !5472
|
||||
- Fix a bug where forking a project from a repository storage to another would fail
|
||||
- Show release notes in tags list
|
||||
- Use project ID in repository cache to prevent stale data from persisting across projects. !5460
|
||||
- Ensure relative paths for video are rewritten as we do for images. !5474
|
||||
- Ensure current user can retry a build before showing the 'Retry' button. !5476
|
||||
- Fix expand all diffs button in compare view
|
||||
|
||||
v 8.10.1
|
||||
- Refactor repository storages documentation. !5428
|
||||
|
@ -27,10 +44,6 @@ v 8.10.1
|
|||
- Fix bug where replies to commit notes displayed in the MR discussion tab wouldn't show up on the commit page. !5446
|
||||
- Ignore invalid trusted proxies in X-Forwarded-For header. !5454
|
||||
- Add links to the real markdown.md file for all GFM examples. !5458
|
||||
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view' !5368 (Scott Le)
|
||||
|
||||
v 8.10.1 (unreleased)
|
||||
- Fix bug where replies to commit notes displayed in the MR discussion tab wouldn't show up on the commit page
|
||||
|
||||
v 8.10.0
|
||||
- Fix profile activity heatmap to show correct day name (eanplatter)
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.2.0
|
||||
3.2.1
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -275,7 +275,7 @@ group :development, :test do
|
|||
gem 'awesome_print', '~> 1.2.0', require: false
|
||||
gem 'fuubar', '~> 2.0.0'
|
||||
|
||||
gem 'database_cleaner', '~> 1.4.0'
|
||||
gem 'database_cleaner', '~> 1.5.0'
|
||||
gem 'factory_girl_rails', '~> 4.6.0'
|
||||
gem 'rspec-rails', '~> 3.5.0'
|
||||
gem 'rspec-retry', '~> 0.4.5'
|
||||
|
@ -334,6 +334,8 @@ gem 'mail_room', '~> 0.8'
|
|||
|
||||
gem 'email_reply_parser', '~> 0.5.8'
|
||||
|
||||
gem 'ruby-prof', '~> 0.15.9'
|
||||
|
||||
## CI
|
||||
gem 'activerecord-session_store', '~> 1.0.0'
|
||||
gem 'nested_form', '~> 0.3.2'
|
||||
|
|
|
@ -153,7 +153,7 @@ GEM
|
|||
d3_rails (3.5.11)
|
||||
railties (>= 3.1.0)
|
||||
daemons (1.2.3)
|
||||
database_cleaner (1.4.1)
|
||||
database_cleaner (1.5.3)
|
||||
debug_inspector (0.0.2)
|
||||
debugger-ruby_core_source (1.3.8)
|
||||
default_value_for (3.0.1)
|
||||
|
@ -620,6 +620,7 @@ GEM
|
|||
rubocop (>= 0.40.0)
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-prof (0.15.9)
|
||||
ruby-progressbar (1.8.1)
|
||||
ruby-saml (1.3.0)
|
||||
nokogiri (>= 1.5.10)
|
||||
|
@ -841,7 +842,7 @@ DEPENDENCIES
|
|||
connection_pool (~> 2.0)
|
||||
creole (~> 0.5.0)
|
||||
d3_rails (~> 3.5.0)
|
||||
database_cleaner (~> 1.4.0)
|
||||
database_cleaner (~> 1.5.0)
|
||||
default_value_for (~> 3.0.0)
|
||||
devise (~> 4.0)
|
||||
devise-two-factor (~> 3.0.0)
|
||||
|
@ -948,6 +949,7 @@ DEPENDENCIES
|
|||
rubocop (~> 0.41.2)
|
||||
rubocop-rspec (~> 1.5.0)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
ruby-prof (~> 0.15.9)
|
||||
sanitize (~> 2.0)
|
||||
sass-rails (~> 5.0.0)
|
||||
scss_lint (~> 0.47.0)
|
||||
|
|
|
@ -66,4 +66,12 @@
|
|||
|
||||
})();
|
||||
|
||||
$(function() {
|
||||
if ($('.js-importer-status').length) {
|
||||
var jobsImportPath = $('.js-importer-status').data('jobs-import-path');
|
||||
var importPath = $('.js-importer-status').data('import-path');
|
||||
|
||||
new ImporterStatus(jobsImportPath, importPath);
|
||||
}
|
||||
});
|
||||
}).call(this);
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
class Admin::RequestsProfilesController < Admin::ApplicationController
|
||||
def index
|
||||
@profile_token = Gitlab::RequestProfiler.profile_token
|
||||
@profiles = Gitlab::RequestProfiler::Profile.all.group_by(&:request_path)
|
||||
end
|
||||
|
||||
def show
|
||||
clean_name = Rack::Utils.clean_path_info(params[:name])
|
||||
profile = Gitlab::RequestProfiler::Profile.find(clean_name)
|
||||
|
||||
if profile
|
||||
render text: profile.content
|
||||
else
|
||||
redirect_to admin_requests_profiles_path, alert: 'Profile not found'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,5 @@
|
|||
class Projects::IssuesController < Projects::ApplicationController
|
||||
include NotesHelper
|
||||
include ToggleSubscriptionAction
|
||||
include IssuableActions
|
||||
include ToggleAwardEmoji
|
||||
|
@ -70,6 +71,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
@note = @project.notes.new(noteable: @issue)
|
||||
@noteable = @issue
|
||||
|
||||
preload_max_access_for_authors(@notes, @project)
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
|
|
|
@ -3,6 +3,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
include DiffForPath
|
||||
include DiffHelper
|
||||
include IssuableActions
|
||||
include NotesHelper
|
||||
include ToggleAwardEmoji
|
||||
|
||||
before_action :module_enabled
|
||||
|
@ -385,6 +386,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
@project_wiki,
|
||||
@ref
|
||||
)
|
||||
|
||||
preload_max_access_for_authors(@notes, @project)
|
||||
end
|
||||
|
||||
def define_widget_vars
|
||||
|
|
|
@ -10,11 +10,12 @@ class Projects::TagsController < Projects::ApplicationController
|
|||
@tags = @repository.tags_sorted_by(@sort)
|
||||
@tags = Kaminari.paginate_array(@tags).page(params[:page])
|
||||
|
||||
@releases = project.releases.where(tag: @tags)
|
||||
@releases = project.releases.where(tag: @tags.map(&:name))
|
||||
end
|
||||
|
||||
def show
|
||||
@tag = @repository.find_tag(params[:id])
|
||||
|
||||
@release = @project.releases.find_or_initialize_by(tag: @tag.name)
|
||||
@commit = @repository.commit(@tag.target)
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ module NotesHelper
|
|||
end
|
||||
|
||||
def note_editable?(note)
|
||||
note.editable? && can?(current_user, :admin_note, note)
|
||||
Ability.can_edit_note?(current_user, note)
|
||||
end
|
||||
|
||||
def noteable_json(noteable)
|
||||
|
@ -87,14 +87,13 @@ module NotesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def note_max_access_for_user(note)
|
||||
@max_access_by_user_id ||= Hash.new do |hash, key|
|
||||
project = key[:project]
|
||||
hash[key] = project.team.human_max_access(key[:user_id])
|
||||
end
|
||||
def preload_max_access_for_authors(notes, project)
|
||||
user_ids = notes.map(&:author_id)
|
||||
project.team.max_member_access_for_user_ids(user_ids)
|
||||
end
|
||||
|
||||
full_key = { project: note.project, user_id: note.author_id }
|
||||
@max_access_by_user_id[full_key]
|
||||
def note_max_access_for_user(note)
|
||||
note.project.team.human_max_access(note.author_id)
|
||||
end
|
||||
|
||||
def discussion_diff_path(discussion)
|
||||
|
|
|
@ -388,6 +388,18 @@ class Ability
|
|||
GroupProjectsFinder.new(group).execute(user).any?
|
||||
end
|
||||
|
||||
def can_edit_note?(user, note)
|
||||
return false if !note.editable? || !user.present?
|
||||
return true if note.author == user || user.admin?
|
||||
|
||||
if note.project
|
||||
max_access_level = note.project.team.max_member_access(user.id)
|
||||
max_access_level >= Gitlab::Access::MASTER
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def namespace_abilities(user, namespace)
|
||||
rules = []
|
||||
|
||||
|
|
|
@ -31,6 +31,10 @@ class Blob < SimpleDelegator
|
|||
text? && language && language.name == 'SVG'
|
||||
end
|
||||
|
||||
def video?
|
||||
UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
|
||||
end
|
||||
|
||||
def to_partial_path
|
||||
if lfs_pointer?
|
||||
'download'
|
||||
|
|
|
@ -295,8 +295,8 @@ class Commit
|
|||
def uri_type(path)
|
||||
entry = @raw.tree.path(path)
|
||||
if entry[:type] == :blob
|
||||
blob = Gitlab::Git::Blob.new(name: entry[:name])
|
||||
blob.image? ? :raw : :blob
|
||||
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]))
|
||||
blob.image? || blob.video? ? :raw : :blob
|
||||
else
|
||||
entry[:type]
|
||||
end
|
||||
|
|
|
@ -53,6 +53,10 @@ class Member < ActiveRecord::Base
|
|||
default_value_for :notification_level, NotificationSetting.levels[:global]
|
||||
|
||||
class << self
|
||||
def access_for_user_ids(user_ids)
|
||||
where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h
|
||||
end
|
||||
|
||||
def find_by_invite_token(invite_token)
|
||||
invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
|
||||
find_by(invite_token: invite_token)
|
||||
|
|
|
@ -451,7 +451,9 @@ class Project < ActiveRecord::Base
|
|||
|
||||
def add_import_job
|
||||
if forked?
|
||||
job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
|
||||
job_id = RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
|
||||
forked_from_project.path_with_namespace,
|
||||
self.namespace.path)
|
||||
else
|
||||
job_id = RepositoryImportWorker.perform_async(self.id)
|
||||
end
|
||||
|
|
|
@ -132,39 +132,63 @@ class ProjectTeam
|
|||
Gitlab::Access.options_with_owner.key(max_member_access(user_id))
|
||||
end
|
||||
|
||||
# This method assumes project and group members are eager loaded for optimal
|
||||
# performance.
|
||||
# Determine the maximum access level for a group of users in bulk.
|
||||
#
|
||||
# Returns a Hash mapping user ID -> maximum access level.
|
||||
def max_member_access_for_user_ids(user_ids)
|
||||
user_ids = user_ids.uniq
|
||||
key = "max_member_access:#{project.id}"
|
||||
RequestStore.store[key] ||= {}
|
||||
access = RequestStore.store[key]
|
||||
|
||||
# Lookup only the IDs we need
|
||||
user_ids = user_ids - access.keys
|
||||
|
||||
if user_ids.present?
|
||||
user_ids.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
|
||||
|
||||
member_access = project.members.access_for_user_ids(user_ids)
|
||||
merge_max!(access, member_access)
|
||||
|
||||
if group
|
||||
group_access = group.members.access_for_user_ids(user_ids)
|
||||
merge_max!(access, group_access)
|
||||
end
|
||||
|
||||
# Each group produces a list of maximum access level per user. We take the
|
||||
# max of the values produced by each group.
|
||||
if project.invited_groups.any? && project.allowed_to_share_with_group?
|
||||
project.project_group_links.each do |group_link|
|
||||
invited_access = max_invited_level_for_users(group_link, user_ids)
|
||||
merge_max!(access, invited_access)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
access
|
||||
end
|
||||
|
||||
def max_member_access(user_id)
|
||||
access = []
|
||||
|
||||
access += project.members.where(user_id: user_id).has_access.pluck(:access_level)
|
||||
|
||||
if group
|
||||
access += group.members.where(user_id: user_id).has_access.pluck(:access_level)
|
||||
end
|
||||
|
||||
if project.invited_groups.any? && project.allowed_to_share_with_group?
|
||||
access << max_invited_level(user_id)
|
||||
end
|
||||
|
||||
access.compact.max
|
||||
max_member_access_for_user_ids([user_id])[user_id]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def max_invited_level(user_id)
|
||||
project.project_group_links.map do |group_link|
|
||||
invited_group = group_link.group
|
||||
access = invited_group.group_members.find_by(user_id: user_id).try(:access_field)
|
||||
# For a given group, return the maximum access level for the user. This is the min of
|
||||
# the invited access level of the group and the access level of the user within the group.
|
||||
# For example, if the group has been given DEVELOPER access but the member has MASTER access,
|
||||
# the user should receive only DEVELOPER access.
|
||||
def max_invited_level_for_users(group_link, user_ids)
|
||||
invited_group = group_link.group
|
||||
capped_access_level = group_link.group_access
|
||||
access = invited_group.group_members.access_for_user_ids(user_ids)
|
||||
|
||||
# If group member has higher access level we should restrict it
|
||||
# to max allowed access level
|
||||
if access && access > group_link.group_access
|
||||
access = group_link.group_access
|
||||
end
|
||||
# If the user is not in the list, assume he/she does not have access
|
||||
missing_users = user_ids - access.keys
|
||||
missing_users.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
|
||||
|
||||
access
|
||||
end.compact.max
|
||||
# Cap the maximum access by the invited level access
|
||||
access.each { |key, value| access[key] = [value, capped_access_level].min }
|
||||
end
|
||||
|
||||
def fetch_members(level = nil)
|
||||
|
@ -215,4 +239,8 @@ class ProjectTeam
|
|||
def group
|
||||
project.group
|
||||
end
|
||||
|
||||
def merge_max!(first_hash, second_hash)
|
||||
first_hash.merge!(second_hash) { |_key, old, new| old > new ? old : new }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -211,6 +211,9 @@ class Repository
|
|||
rugged.references.create(keep_around_ref_name(sha), sha, force: true)
|
||||
rescue Rugged::ReferenceError => ex
|
||||
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
|
||||
rescue Rugged::OSError => ex
|
||||
raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
|
||||
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,3 +16,7 @@
|
|||
= link_to admin_health_check_path, title: 'Health Check' do
|
||||
%span
|
||||
Health Check
|
||||
= nav_link(controller: :requests_profiles) do
|
||||
= link_to admin_requests_profiles_path, title: 'Requests Profiles' do
|
||||
%span
|
||||
Requests Profiles
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
- @no_container = true
|
||||
- page_title 'Requests Profiles'
|
||||
= render 'admin/background_jobs/head'
|
||||
|
||||
%div{ class: container_class }
|
||||
%h3.page-title
|
||||
= page_title
|
||||
|
||||
.bs-callout.clearfix
|
||||
Pass the header
|
||||
%code X-Profile-Token: #{@profile_token}
|
||||
to profile the request
|
||||
|
||||
- if @profiles.present?
|
||||
.prepend-top-default
|
||||
- @profiles.each do |path, profiles|
|
||||
.panel.panel-default.panel-small
|
||||
.panel-heading
|
||||
%code= path
|
||||
%ul.content-list
|
||||
- profiles.each do |profile|
|
||||
%li
|
||||
= link_to profile.time.to_s(:long), admin_requests_profile_path(profile), data: {no_turbolink: true}
|
||||
- else
|
||||
%p
|
||||
No profiles found
|
|
@ -74,6 +74,4 @@
|
|||
= link_to "import flow", status_import_bitbucket_path, "data-no-turbolink" => "true"
|
||||
again.
|
||||
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}");
|
||||
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
|
||||
|
|
|
@ -56,5 +56,4 @@
|
|||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
|
||||
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
|
||||
|
|
|
@ -55,5 +55,4 @@
|
|||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
|
||||
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_github_path}", import_path: "#{import_github_path}" } }
|
||||
|
|
|
@ -51,5 +51,4 @@
|
|||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
|
||||
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
|
||||
|
|
|
@ -51,5 +51,4 @@
|
|||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
|
||||
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitorious_path}", import_path: "#{import_gitorious_path}" } }
|
||||
|
|
|
@ -77,5 +77,4 @@
|
|||
= link_to "import flow", new_import_google_code_path
|
||||
again.
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}");
|
||||
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } }
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
|
||||
%span
|
||||
Overview
|
||||
= nav_link(controller: %w(system_info background_jobs logs health_check)) do
|
||||
= nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
|
||||
= link_to admin_system_info_path, title: 'Monitoring' do
|
||||
%span
|
||||
Monitoring
|
||||
|
|
|
@ -7,28 +7,31 @@
|
|||
.nav-text
|
||||
Protected branches can be managed in project settings
|
||||
|
||||
- if can? current_user, :push_code, @project
|
||||
.nav-controls
|
||||
= form_tag(filter_branches_path, method: :get) do
|
||||
= search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
|
||||
.dropdown.inline
|
||||
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
%span.light
|
||||
- if params[:sort].present?
|
||||
= params[:sort].humanize
|
||||
- else
|
||||
Name
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
%li
|
||||
= link_to filter_branches_path(sort: nil) do
|
||||
= sort_title_name
|
||||
= link_to filter_branches_path(sort: 'recently_updated') do
|
||||
= sort_title_recently_updated
|
||||
= link_to filter_branches_path(sort: 'last_updated') do
|
||||
= sort_title_oldest_updated
|
||||
.nav-controls
|
||||
= form_tag(filter_branches_path, method: :get) do
|
||||
= search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
|
||||
|
||||
.dropdown.inline
|
||||
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
%span.light
|
||||
- if params[:sort].present?
|
||||
= params[:sort].humanize
|
||||
- else
|
||||
Name
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
%li
|
||||
= link_to filter_branches_path(sort: nil) do
|
||||
= sort_title_name
|
||||
= link_to filter_branches_path(sort: 'recently_updated') do
|
||||
= sort_title_recently_updated
|
||||
= link_to filter_branches_path(sort: 'last_updated') do
|
||||
= sort_title_oldest_updated
|
||||
|
||||
- if can? current_user, :push_code, @project
|
||||
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
|
||||
New branch
|
||||
|
||||
- if @branches.any?
|
||||
%ul.content-list.all-branches
|
||||
- @branches.each do |branch|
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
.block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) }
|
||||
.title
|
||||
Build details
|
||||
- if @build.retryable?
|
||||
- if can?(current_user, :update_build, @build) && @build.retryable?
|
||||
= link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
|
||||
- if @build.merge_request
|
||||
%p.build-detail-row
|
||||
|
@ -88,8 +88,9 @@
|
|||
%p
|
||||
%span.build-light-text Variables:
|
||||
|
||||
%code
|
||||
- @build.trigger_request.variables.each do |key, value|
|
||||
|
||||
- @build.trigger_request.variables.each do |key, value|
|
||||
%code
|
||||
#{key}=#{value}
|
||||
|
||||
.block
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
- if defined?(ref) && ref
|
||||
- if build.ref
|
||||
.icon-container
|
||||
= build.tag? ? icon('tag') : icon('code-fork')
|
||||
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
|
||||
- else
|
||||
.light none
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
.content-block.oneline-block.files-changed
|
||||
.inline-parallel-buttons
|
||||
- if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? }
|
||||
= link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: 'html')), class: 'btn btn-default'
|
||||
= link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: nil)), class: 'btn btn-default'
|
||||
- if show_whitespace_toggle
|
||||
- if current_controller?(:commit)
|
||||
= commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
|
||||
|
|
|
@ -19,4 +19,9 @@
|
|||
]
|
||||
}
|
||||
var ctx = $("#build_timesChart").get(0).getContext("2d");
|
||||
new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false});
|
||||
var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
|
||||
if (window.innerWidth < 768) {
|
||||
// Scale fonts if window width lower than 768px (iPad portrait)
|
||||
options.scaleFontSize = 8
|
||||
}
|
||||
new Chart(ctx).Bar(data, options);
|
||||
|
|
|
@ -48,4 +48,9 @@
|
|||
]
|
||||
}
|
||||
var ctx = $("##{scope}Chart").get(0).getContext("2d");
|
||||
new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false});
|
||||
var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
|
||||
if (window.innerWidth < 768) {
|
||||
// Scale fonts if window width lower than 768px (iPad portrait)
|
||||
options.scaleFontSize = 8
|
||||
}
|
||||
new Chart(ctx).Line(data, options);
|
||||
|
|
|
@ -59,6 +59,10 @@
|
|||
var container = $(selector).parent();
|
||||
var generateChart = function() {
|
||||
selector.attr('width', $(container).width());
|
||||
if (window.innerWidth < 768) {
|
||||
// Scale fonts if window width lower than 768px (iPad portrait)
|
||||
options.scaleFontSize = 8
|
||||
}
|
||||
return new Chart(ctx).Bar(data, options);
|
||||
};
|
||||
// enabling auto-resizing
|
||||
|
|
|
@ -4,7 +4,7 @@ class RepositoryForkWorker
|
|||
|
||||
sidekiq_options queue: :gitlab_shell
|
||||
|
||||
def perform(project_id, source_path, target_path)
|
||||
def perform(project_id, forked_from_repository_storage_path, source_path, target_path)
|
||||
project = Project.find_by_id(project_id)
|
||||
|
||||
unless project.present?
|
||||
|
@ -12,7 +12,8 @@ class RepositoryForkWorker
|
|||
return
|
||||
end
|
||||
|
||||
result = gitlab_shell.fork_repository(project.repository_storage_path, source_path, target_path)
|
||||
result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_path,
|
||||
project.repository_storage_path, target_path)
|
||||
unless result
|
||||
logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}")
|
||||
project.mark_import_as_failed('The project could not be forked.')
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
class RequestsProfilesWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options queue: :default
|
||||
|
||||
def perform
|
||||
Gitlab::RequestProfiler.remove_all_profiles
|
||||
end
|
||||
end
|
|
@ -68,6 +68,25 @@
|
|||
:why: https://opensource.org/licenses/BSD-2-Clause
|
||||
:versions: []
|
||||
:when: 2016-05-02 05:55:09.796363000 Z
|
||||
- - :whitelist
|
||||
- LGPLv2+
|
||||
- :who: Stan Hu
|
||||
:why: Equivalent to LGPLv2
|
||||
:versions: []
|
||||
:when: 2016-06-07 17:14:10.907682000 Z
|
||||
- - :whitelist
|
||||
- Artistic 2.0
|
||||
- :who: Josh Frye
|
||||
:why: Disk/mount information display on Admin pages
|
||||
:versions: []
|
||||
:when: 2016-06-29 16:32:45.432113000 Z
|
||||
- - :whitelist
|
||||
- Simplified BSD
|
||||
- :who: Douwe Maan
|
||||
:why: https://opensource.org/licenses/BSD-2-Clause
|
||||
:versions: []
|
||||
:when: 2016-07-26 21:24:07.248480000 Z
|
||||
|
||||
|
||||
# LICENSE BLACKLIST
|
||||
- - :blacklist
|
||||
|
@ -175,15 +194,3 @@
|
|||
:why: https://github.com/jmcnevin/rubypants/blob/master/LICENSE.rdoc
|
||||
:versions: []
|
||||
:when: 2016-05-02 05:56:50.696858000 Z
|
||||
- - :whitelist
|
||||
- LGPLv2+
|
||||
- :who: Stan Hu
|
||||
:why: Equivalent to LGPLv2
|
||||
:versions: []
|
||||
:when: 2016-06-07 17:14:10.907682000 Z
|
||||
- - :whitelist
|
||||
- Artistic 2.0
|
||||
- :who: Josh Frye
|
||||
:why: Disk/mount information display on Admin pages
|
||||
:versions: []
|
||||
:when: 2016-06-29 16:32:45.432113000 Z
|
||||
|
|
|
@ -290,6 +290,9 @@ Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'Repository
|
|||
Settings.cron_jobs['gitlab_remove_project_export_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *'
|
||||
Settings.cron_jobs['gitlab_remove_project_export_worker']['job_class'] = 'GitlabRemoveProjectExportWorker'
|
||||
Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *'
|
||||
Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker'
|
||||
|
||||
#
|
||||
# GitLab Shell
|
||||
|
|
|
@ -26,4 +26,4 @@ def validate_storages
|
|||
end
|
||||
end
|
||||
|
||||
validate_storages unless Rails.env.test?
|
||||
validate_storages unless Rails.env.test? || ENV['SKIP_STORAGE_VALIDATION'] == 'true'
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Rails.application.configure do |config|
|
||||
config.middleware.use(Gitlab::RequestProfiler::Middleware)
|
||||
end
|
|
@ -7,6 +7,7 @@ Sidekiq.configure_server do |config|
|
|||
config.server_middleware do |chain|
|
||||
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
|
||||
chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
|
||||
chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
|
||||
end
|
||||
|
||||
# Sidekiq-cron: load recurring jobs from gitlab.yml
|
||||
|
|
|
@ -42,10 +42,9 @@ Rails.application.routes.draw do
|
|||
|
||||
resource :lint, only: [:show, :create]
|
||||
|
||||
resources :projects do
|
||||
resources :projects, only: [:index, :show] do
|
||||
member do
|
||||
get :status, to: 'projects#badge'
|
||||
get :integration
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -144,13 +143,13 @@ Rails.application.routes.draw do
|
|||
get :jobs
|
||||
end
|
||||
|
||||
resource :gitlab, only: [:create, :new], controller: :gitlab do
|
||||
resource :gitlab, only: [:create], controller: :gitlab do
|
||||
get :status
|
||||
get :callback
|
||||
get :jobs
|
||||
end
|
||||
|
||||
resource :bitbucket, only: [:create, :new], controller: :bitbucket do
|
||||
resource :bitbucket, only: [:create], controller: :bitbucket do
|
||||
get :status
|
||||
get :callback
|
||||
get :jobs
|
||||
|
@ -243,7 +242,6 @@ Rails.application.routes.draw do
|
|||
get :projects
|
||||
get :keys
|
||||
get :groups
|
||||
put :team_update
|
||||
put :block
|
||||
put :unblock
|
||||
put :unlock
|
||||
|
@ -281,6 +279,7 @@ Rails.application.routes.draw do
|
|||
resource :health_check, controller: 'health_check', only: [:show]
|
||||
resource :background_jobs, controller: 'background_jobs', only: [:show]
|
||||
resource :system_info, controller: 'system_info', only: [:show]
|
||||
resources :requests_profiles, only: [:index, :show], param: :name
|
||||
|
||||
resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
|
||||
root to: 'projects#index', as: :projects
|
||||
|
@ -300,7 +299,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resource :appearances, path: 'appearance' do
|
||||
resource :appearances, only: [:show, :create, :update], path: 'appearance' do
|
||||
member do
|
||||
get :preview
|
||||
delete :logo
|
||||
|
@ -309,7 +308,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
|
||||
resource :application_settings, only: [:show, :update] do
|
||||
resources :services
|
||||
resources :services, only: [:index, :edit, :update]
|
||||
put :reset_runners_token
|
||||
put :reset_health_check_token
|
||||
put :clear_repository_check_states
|
||||
|
@ -346,7 +345,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
|
||||
scope module: :profiles do
|
||||
resource :account, only: [:show, :update] do
|
||||
resource :account, only: [:show] do
|
||||
member do
|
||||
delete :unlink
|
||||
end
|
||||
|
@ -358,7 +357,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
resource :preferences, only: [:show, :update]
|
||||
resources :keys
|
||||
resources :keys, only: [:index, :show, :new, :create, :destroy]
|
||||
resources :emails, only: [:index, :create, :destroy]
|
||||
resource :avatar, only: [:destroy]
|
||||
|
||||
|
@ -660,7 +659,7 @@ Rails.application.routes.draw do
|
|||
post '/wikis/*id/markdown_preview', to: 'wikis#markdown_preview', constraints: WIKI_SLUG_ID, as: 'wiki_markdown_preview'
|
||||
end
|
||||
|
||||
resource :repository, only: [:show, :create] do
|
||||
resource :repository, only: [:create] do
|
||||
member do
|
||||
get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
|
||||
end
|
||||
|
@ -782,7 +781,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :labels, constraints: { id: /\d+/ } do
|
||||
resources :labels, except: [:show], constraints: { id: /\d+/ } do
|
||||
collection do
|
||||
post :generate
|
||||
post :set_priorities
|
||||
|
@ -807,7 +806,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
|
||||
resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
|
||||
collection do
|
||||
delete :leave
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20160721081015) do
|
||||
ActiveRecord::Schema.define(version: 20160722221922) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
|
|
@ -38,7 +38,7 @@ GitLab Runner then executes build scripts as the `gitlab-runner` user.
|
|||
$ sudo gitlab-ci-multi-runner register -n \
|
||||
--url https://gitlab.com/ci \
|
||||
--registration-token REGISTRATION_TOKEN \
|
||||
--executor shell
|
||||
--executor shell \
|
||||
--description "My Runner"
|
||||
```
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-10-stable-ee
|
|||
```bash
|
||||
cd /home/git/gitlab-shell
|
||||
sudo -u git -H git fetch --all --tags
|
||||
sudo -u git -H git checkout v3.2.0
|
||||
sudo -u git -H git checkout v3.2.1
|
||||
```
|
||||
|
||||
### 5. Update gitlab-workhorse
|
||||
|
|
|
@ -20,7 +20,7 @@ module Banzai
|
|||
process_link_attr el.attribute('href')
|
||||
end
|
||||
|
||||
doc.search('img').each do |el|
|
||||
doc.css('img, video').each do |el|
|
||||
process_link_attr el.attribute('src')
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ module Gitlab
|
|||
module Access
|
||||
class AccessDeniedError < StandardError; end
|
||||
|
||||
NO_ACCESS = 0
|
||||
GUEST = 10
|
||||
REPORTER = 20
|
||||
DEVELOPER = 30
|
||||
|
|
|
@ -60,16 +60,18 @@ module Gitlab
|
|||
end
|
||||
|
||||
# Fork repository to new namespace
|
||||
# storage - project's storage path
|
||||
# forked_from_storage - forked-from project's storage path
|
||||
# path - project path with namespace
|
||||
# forked_to_storage - forked-to project's storage path
|
||||
# fork_namespace - namespace for forked project
|
||||
#
|
||||
# Ex.
|
||||
# fork_repository("/path/to/storage", "gitlab/gitlab-ci", "randx")
|
||||
# fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx")
|
||||
#
|
||||
def fork_repository(storage, path, fork_namespace)
|
||||
def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace)
|
||||
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
|
||||
storage, "#{path}.git", fork_namespace])
|
||||
forked_from_storage, "#{path}.git", forked_to_storage,
|
||||
fork_namespace])
|
||||
end
|
||||
|
||||
# Remove repository from file system
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
require 'fileutils'
|
||||
|
||||
module Gitlab
|
||||
module RequestProfiler
|
||||
PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles"
|
||||
|
||||
def profile_token
|
||||
Rails.cache.fetch('profile-token') do
|
||||
Devise.friendly_token
|
||||
end
|
||||
end
|
||||
module_function :profile_token
|
||||
|
||||
def remove_all_profiles
|
||||
FileUtils.rm_rf(PROFILES_DIR)
|
||||
end
|
||||
module_function :remove_all_profiles
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
require 'ruby-prof'
|
||||
|
||||
module Gitlab
|
||||
module RequestProfiler
|
||||
class Middleware
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if profile?(env)
|
||||
call_with_profiling(env)
|
||||
else
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
def profile?(env)
|
||||
header_token = env['HTTP_X_PROFILE_TOKEN']
|
||||
return unless header_token.present?
|
||||
|
||||
profile_token = RequestProfiler.profile_token
|
||||
return unless profile_token.present?
|
||||
|
||||
header_token == profile_token
|
||||
end
|
||||
|
||||
def call_with_profiling(env)
|
||||
ret = nil
|
||||
result = RubyProf::Profile.profile do
|
||||
ret = @app.call(env)
|
||||
end
|
||||
|
||||
printer = RubyProf::CallStackPrinter.new(result)
|
||||
file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}.html"
|
||||
file_path = "#{PROFILES_DIR}/#{file_name}"
|
||||
|
||||
FileUtils.mkdir_p(PROFILES_DIR)
|
||||
File.open(file_path, 'wb') do |file|
|
||||
printer.print(file)
|
||||
end
|
||||
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
module Gitlab
|
||||
module RequestProfiler
|
||||
class Profile
|
||||
attr_reader :name, :time, :request_path
|
||||
|
||||
alias_method :to_param, :name
|
||||
|
||||
def self.all
|
||||
Dir["#{PROFILES_DIR}/*.html"].map do |path|
|
||||
new(File.basename(path))
|
||||
end
|
||||
end
|
||||
|
||||
def self.find(name)
|
||||
name_dup = name.dup
|
||||
name_dup << '.html' unless name.end_with?('.html')
|
||||
|
||||
file_path = "#{PROFILES_DIR}/#{name_dup}"
|
||||
return unless File.exist?(file_path)
|
||||
|
||||
new(name_dup)
|
||||
end
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
|
||||
set_attributes
|
||||
end
|
||||
|
||||
def content
|
||||
File.read("#{PROFILES_DIR}/#{name}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_attributes
|
||||
_, path, timestamp = name.split(/(.*)_(\d+)\.html$/)
|
||||
@request_path = path.tr('|', '/')
|
||||
@time = Time.at(timestamp.to_i).utc
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
module Gitlab
|
||||
module SidekiqMiddleware
|
||||
class RequestStoreMiddleware
|
||||
def call(worker, job, queue)
|
||||
RequestStore.begin!
|
||||
yield
|
||||
ensure
|
||||
RequestStore.end!
|
||||
RequestStore.clear!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -25,6 +25,10 @@ namespace :gitlab do
|
|||
desc 'Drop all tables'
|
||||
task :drop_tables => :environment do
|
||||
connection = ActiveRecord::Base.connection
|
||||
|
||||
# If MySQL, turn off foreign key checks
|
||||
connection.execute('SET FOREIGN_KEY_CHECKS=0') if Gitlab::Database.mysql?
|
||||
|
||||
tables = connection.tables
|
||||
tables.delete 'schema_migrations'
|
||||
# Truncate schema_migrations to ensure migrations re-run
|
||||
|
@ -35,6 +39,9 @@ namespace :gitlab do
|
|||
# MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html
|
||||
# Add `IF EXISTS` because cascade could have already deleted a table.
|
||||
tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
|
||||
|
||||
# If MySQL, re-enable foreign key checks
|
||||
connection.execute('SET FOREIGN_KEY_CHECKS=1') if Gitlab::Database.mysql?
|
||||
end
|
||||
|
||||
desc 'Configures the database by running migrate, or by loading the schema and seeding if needed'
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Projects::TagsController do
|
||||
let(:project) { create(:project, :public) }
|
||||
let!(:release) { create(:release, project: project) }
|
||||
let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
|
||||
|
||||
describe 'GET index' do
|
||||
before { get :index, namespace_id: project.namespace.to_param, project_id: project.to_param }
|
||||
|
||||
it 'returns the tags for the page' do
|
||||
expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0'])
|
||||
end
|
||||
|
||||
it 'returns releases matching those tags' do
|
||||
expect(assigns(:releases)).to include(release)
|
||||
expect(assigns(:releases)).not_to include(invalid_release)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,7 +5,8 @@ FactoryGirl.define do
|
|||
|
||||
variables do
|
||||
{
|
||||
TRIGGER_KEY: 'TRIGGER_VALUE'
|
||||
TRIGGER_KEY_1: 'TRIGGER_VALUE_1',
|
||||
TRIGGER_KEY_2: 'TRIGGER_VALUE_2'
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -199,9 +199,13 @@ describe "Builds" do
|
|||
click_link 'Retry'
|
||||
end
|
||||
|
||||
it { expect(page.status_code).to eq(200) }
|
||||
it { expect(page).to have_content 'pending' }
|
||||
it { expect(page).to have_content 'Cancel' }
|
||||
it 'shows the right status and buttons' do
|
||||
expect(page).to have_http_status(200)
|
||||
expect(page).to have_content 'pending'
|
||||
page.within('aside.right-sidebar') do
|
||||
expect(page).to have_content 'Cancel'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "Build from other project" do
|
||||
|
@ -212,7 +216,25 @@ describe "Builds" do
|
|||
page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2))
|
||||
end
|
||||
|
||||
it { expect(page.status_code).to eq(404) }
|
||||
it { expect(page).to have_http_status(404) }
|
||||
end
|
||||
|
||||
context "Build that current user is not allowed to retry" do
|
||||
before do
|
||||
@build.run!
|
||||
@build.cancel!
|
||||
@project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
|
||||
logout_direct
|
||||
login_with(create(:user))
|
||||
visit namespace_project_build_path(@project.namespace, @project, @build)
|
||||
end
|
||||
|
||||
it 'does not show the Retry button' do
|
||||
page.within('aside.right-sidebar') do
|
||||
expect(page).not_to have_content 'Retry'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Branches', feature: true do
|
||||
let(:project) { create(:project) }
|
||||
let(:repository) { project.repository }
|
||||
|
||||
before do
|
||||
login_as :user
|
||||
project.team << [@user, :developer]
|
||||
end
|
||||
|
||||
describe 'Initial branches page' do
|
||||
it 'shows all the branches' do
|
||||
visit namespace_project_branches_path(project.namespace, project)
|
||||
|
||||
repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
|
||||
expect(page).to have_content("Protected branches can be managed in project settings")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Find branches' do
|
||||
it 'shows filtered branches', js: true do
|
||||
visit namespace_project_branches_path(project.namespace, project, project.id)
|
||||
|
||||
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
|
||||
end
|
|
@ -20,7 +20,7 @@ describe BranchesFinder do
|
|||
|
||||
result = branches_finder.execute
|
||||
|
||||
expect(result.first.name).to eq('expand-collapse-lines')
|
||||
expect(result.first.name).to eq('video')
|
||||
end
|
||||
|
||||
it 'sorts by last_updated' do
|
||||
|
|
|
@ -1,37 +1,30 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe NotesHelper do
|
||||
let(:owner) { create(:owner) }
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:empty_project, namespace: group) }
|
||||
let(:master) { create(:user) }
|
||||
let(:reporter) { create(:user) }
|
||||
let(:guest) { create(:user) }
|
||||
|
||||
let(:owner_note) { create(:note, author: owner, project: project) }
|
||||
let(:master_note) { create(:note, author: master, project: project) }
|
||||
let(:reporter_note) { create(:note, author: reporter, project: project) }
|
||||
let!(:notes) { [owner_note, master_note, reporter_note] }
|
||||
|
||||
before do
|
||||
group.add_owner(owner)
|
||||
project.team << [master, :master]
|
||||
project.team << [reporter, :reporter]
|
||||
project.team << [guest, :guest]
|
||||
end
|
||||
|
||||
describe "#notes_max_access_for_users" do
|
||||
let(:owner) { create(:owner) }
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:empty_project, namespace: group) }
|
||||
let(:master) { create(:user) }
|
||||
let(:reporter) { create(:user) }
|
||||
let(:guest) { create(:user) }
|
||||
|
||||
let(:owner_note) { create(:note, author: owner, project: project) }
|
||||
let(:master_note) { create(:note, author: master, project: project) }
|
||||
let(:reporter_note) { create(:note, author: reporter, project: project) }
|
||||
let!(:notes) { [owner_note, master_note, reporter_note] }
|
||||
|
||||
before do
|
||||
group.add_owner(owner)
|
||||
project.team << [master, :master]
|
||||
project.team << [reporter, :reporter]
|
||||
project.team << [guest, :guest]
|
||||
end
|
||||
|
||||
it 'return human access levels' do
|
||||
original_method = project.team.method(:human_max_access)
|
||||
expect_any_instance_of(ProjectTeam).to receive(:human_max_access).exactly(3).times do |*args|
|
||||
original_method.call(args[1])
|
||||
end
|
||||
|
||||
expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
|
||||
expect(helper.note_max_access_for_user(master_note)).to eq('Master')
|
||||
expect(helper.note_max_access_for_user(reporter_note)).to eq('Reporter')
|
||||
# Call it again to ensure value is cached
|
||||
expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
|
||||
end
|
||||
|
||||
it 'handles access in different projects' do
|
||||
|
@ -43,4 +36,16 @@ describe NotesHelper do
|
|||
expect(helper.note_max_access_for_user(other_note)).to eq('Reporter')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#preload_max_access_for_authors' do
|
||||
it 'loads multiple users' do
|
||||
expected_access = {
|
||||
owner.id => Gitlab::Access::OWNER,
|
||||
master.id => Gitlab::Access::MASTER,
|
||||
reporter.id => Gitlab::Access::REPORTER
|
||||
}
|
||||
|
||||
expect(helper.preload_max_access_for_authors(notes, project)).to eq(expected_access)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,6 +17,10 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
|
|||
%(<img src="#{path}" />)
|
||||
end
|
||||
|
||||
def video(path)
|
||||
%(<video src="#{path}"></video>)
|
||||
end
|
||||
|
||||
def link(path)
|
||||
%(<a href="#{path}">#{path}</a>)
|
||||
end
|
||||
|
@ -37,6 +41,12 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
|
|||
doc = filter(image('files/images/logo-black.png'))
|
||||
expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
|
||||
end
|
||||
|
||||
it 'does not modify any relative URL in video' do
|
||||
doc = filter(video('files/videos/intro.mp4'), commit: project.commit('video'), ref: 'video')
|
||||
|
||||
expect(doc.at_css('video')['src']).to eq 'files/videos/intro.mp4'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples :relative_to_requested do
|
||||
|
@ -111,11 +121,26 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
|
|||
end
|
||||
|
||||
it 'rebuilds relative URL for an image in the repo' do
|
||||
doc = filter(image('files/images/logo-black.png'))
|
||||
|
||||
expect(doc.at_css('img')['src']).
|
||||
to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for link to an image in the repo' do
|
||||
doc = filter(link('files/images/logo-black.png'))
|
||||
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for a video in the repo' do
|
||||
doc = filter(video('files/videos/intro.mp4'), commit: project.commit('video'), ref: 'video')
|
||||
|
||||
expect(doc.at_css('video')['src']).
|
||||
to eq "/#{project_path}/raw/video/files/videos/intro.mp4"
|
||||
end
|
||||
|
||||
it 'does not modify relative URL with an anchor only' do
|
||||
doc = filter(link('#section-1'))
|
||||
expect(doc.at_css('a')['href']).to eq '#section-1'
|
||||
|
|
|
@ -1,6 +1,62 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Ability, lib: true do
|
||||
describe '.can_edit_note?' do
|
||||
let(:project) { create(:empty_project) }
|
||||
let!(:note) { create(:note_on_issue, project: project) }
|
||||
|
||||
context 'using an anonymous user' do
|
||||
it 'returns false' do
|
||||
expect(described_class.can_edit_note?(nil, note)).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'using a system note' do
|
||||
it 'returns false' do
|
||||
system_note = create(:note, system: true)
|
||||
user = create(:user)
|
||||
|
||||
expect(described_class.can_edit_note?(user, system_note)).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'using users with different access levels' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'returns true for the author' do
|
||||
expect(described_class.can_edit_note?(note.author, note)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false for a guest user' do
|
||||
project.team << [user, :guest]
|
||||
|
||||
expect(described_class.can_edit_note?(user, note)).to be_falsy
|
||||
end
|
||||
|
||||
it 'returns false for a developer' do
|
||||
project.team << [user, :developer]
|
||||
|
||||
expect(described_class.can_edit_note?(user, note)).to be_falsy
|
||||
end
|
||||
|
||||
it 'returns true for a master' do
|
||||
project.team << [user, :master]
|
||||
|
||||
expect(described_class.can_edit_note?(user, note)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true for a group owner' do
|
||||
group = create(:group)
|
||||
project.project_group_links.create(
|
||||
group: group,
|
||||
group_access: Gitlab::Access::MASTER)
|
||||
group.add_owner(user)
|
||||
|
||||
expect(described_class.can_edit_note?(user, note)).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.users_that_can_read_project' do
|
||||
context 'using a public project' do
|
||||
it 'returns all the users' do
|
||||
|
|
|
@ -33,6 +33,22 @@ describe Blob do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#video?' do
|
||||
it 'is falsey with image extension' do
|
||||
git_blob = Gitlab::Git::Blob.new(name: 'image.png')
|
||||
|
||||
expect(described_class.decorate(git_blob)).not_to be_video
|
||||
end
|
||||
|
||||
UploaderHelper::VIDEO_EXT.each do |ext|
|
||||
it "is truthy when extension is .#{ext}" do
|
||||
git_blob = Gitlab::Git::Blob.new(name: "video.#{ext}")
|
||||
|
||||
expect(described_class.decorate(git_blob)).to be_video
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_partial_path' do
|
||||
def stubbed_blob(overrides = {})
|
||||
overrides.reverse_merge!(
|
||||
|
|
|
@ -259,7 +259,7 @@ describe Ci::Build, models: true do
|
|||
let(:trigger) { create(:ci_trigger, project: project) }
|
||||
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
|
||||
let(:user_trigger_variable) do
|
||||
{ key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
|
||||
{ key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
|
||||
end
|
||||
let(:predefined_trigger_variable) do
|
||||
{ key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
|
||||
|
|
|
@ -212,6 +212,7 @@ eos
|
|||
it 'returns the URI type at the given path' do
|
||||
expect(commit.uri_type('files/html')).to be(:tree)
|
||||
expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
|
||||
expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw)
|
||||
expect(commit.uri_type('files/js/application.js')).to be(:blob)
|
||||
end
|
||||
|
||||
|
|
|
@ -79,6 +79,18 @@ describe Member, models: true do
|
|||
@accepted_request_member = project.requesters.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request }
|
||||
end
|
||||
|
||||
describe '.access_for_user_ids' do
|
||||
it 'returns the right access levels' do
|
||||
users = [@owner_user.id, @master_user.id]
|
||||
expected = {
|
||||
@owner_user.id => Gitlab::Access::OWNER,
|
||||
@master_user.id => Gitlab::Access::MASTER
|
||||
}
|
||||
|
||||
expect(described_class.access_for_user_ids(users)).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.invite' do
|
||||
it { expect(described_class.invite).not_to include @master }
|
||||
it { expect(described_class.invite).to include @invited_member }
|
||||
|
|
|
@ -1272,6 +1272,32 @@ describe Project, models: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#add_import_job' do
|
||||
context 'forked' do
|
||||
let(:forked_project_link) { create(:forked_project_link) }
|
||||
let(:forked_from_project) { forked_project_link.forked_from_project }
|
||||
let(:project) { forked_project_link.forked_to_project }
|
||||
|
||||
it 'schedules a RepositoryForkWorker job' do
|
||||
expect(RepositoryForkWorker).to receive(:perform_async).
|
||||
with(project.id, forked_from_project.repository_storage_path,
|
||||
forked_from_project.path_with_namespace, project.namespace.path)
|
||||
|
||||
project.add_import_job
|
||||
end
|
||||
end
|
||||
|
||||
context 'not forked' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'schedules a RepositoryImportWorker job' do
|
||||
expect(RepositoryImportWorker).to receive(:perform_async).with(project.id)
|
||||
|
||||
project.add_import_job
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.where_paths_in' do
|
||||
context 'without any paths' do
|
||||
it 'returns an empty relation' do
|
||||
|
|
|
@ -151,8 +151,8 @@ describe ProjectTeam, models: true do
|
|||
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) }
|
||||
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
|
||||
it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) }
|
||||
it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
|
||||
it { expect(project.team.max_member_access(requester.id)).to be_nil }
|
||||
it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
|
||||
it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
|
||||
end
|
||||
|
||||
context 'when project is shared with group' do
|
||||
|
@ -168,14 +168,14 @@ describe ProjectTeam, models: true do
|
|||
|
||||
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) }
|
||||
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
|
||||
it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
|
||||
it { expect(project.team.max_member_access(requester.id)).to be_nil }
|
||||
it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
|
||||
it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
|
||||
|
||||
context 'but share_with_group_lock is true' do
|
||||
before { project.namespace.update(share_with_group_lock: true) }
|
||||
|
||||
it { expect(project.team.max_member_access(master.id)).to be_nil }
|
||||
it { expect(project.team.max_member_access(reporter.id)).to be_nil }
|
||||
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::NO_ACCESS) }
|
||||
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::NO_ACCESS) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -194,8 +194,53 @@ describe ProjectTeam, models: true do
|
|||
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) }
|
||||
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
|
||||
it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) }
|
||||
it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
|
||||
it { expect(project.team.max_member_access(requester.id)).to be_nil }
|
||||
it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
|
||||
it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#max_member_access_for_users" do
|
||||
it 'returns correct roles for different users' do
|
||||
master = create(:user)
|
||||
reporter = create(:user)
|
||||
promoted_guest = create(:user)
|
||||
guest = create(:user)
|
||||
project = create(:project)
|
||||
|
||||
project.team << [master, :master]
|
||||
project.team << [reporter, :reporter]
|
||||
project.team << [promoted_guest, :guest]
|
||||
project.team << [guest, :guest]
|
||||
|
||||
group = create(:group)
|
||||
group_developer = create(:user)
|
||||
second_developer = create(:user)
|
||||
project.project_group_links.create(
|
||||
group: group,
|
||||
group_access: Gitlab::Access::DEVELOPER)
|
||||
|
||||
group.add_master(promoted_guest)
|
||||
group.add_developer(group_developer)
|
||||
group.add_developer(second_developer)
|
||||
|
||||
second_group = create(:group)
|
||||
project.project_group_links.create(
|
||||
group: second_group,
|
||||
group_access: Gitlab::Access::MASTER)
|
||||
second_group.add_master(second_developer)
|
||||
|
||||
users = [master, reporter, promoted_guest, guest, group_developer, second_developer].map(&:id)
|
||||
|
||||
expected = {
|
||||
master.id => Gitlab::Access::MASTER,
|
||||
reporter.id => Gitlab::Access::REPORTER,
|
||||
promoted_guest.id => Gitlab::Access::DEVELOPER,
|
||||
guest.id => Gitlab::Access::GUEST,
|
||||
group_developer.id => Gitlab::Access::DEVELOPER,
|
||||
second_developer.id => Gitlab::Access::MASTER
|
||||
}
|
||||
|
||||
expect(project.team.max_member_access_for_user_ids(users)).to eq(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -98,7 +98,7 @@ describe Ci::API::API do
|
|||
{ "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
|
||||
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
|
||||
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
|
||||
{ "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false }
|
||||
{ "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false }
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
require 'spec_helper'
|
||||
|
||||
# team_update_admin_user PUT /admin/users/:id/team_update(.:format) admin/users#team_update
|
||||
# block_admin_user PUT /admin/users/:id/block(.:format) admin/users#block
|
||||
# unblock_admin_user PUT /admin/users/:id/unblock(.:format) admin/users#unblock
|
||||
# admin_users GET /admin/users(.:format) admin/users#index
|
||||
|
@ -11,10 +10,6 @@ require 'spec_helper'
|
|||
# PUT /admin/users/:id(.:format) admin/users#update
|
||||
# DELETE /admin/users/:id(.:format) admin/users#destroy
|
||||
describe Admin::UsersController, "routing" do
|
||||
it "to #team_update" do
|
||||
expect(put("/admin/users/1/team_update")).to route_to('admin/users#team_update', id: '1')
|
||||
end
|
||||
|
||||
it "to #block" do
|
||||
expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1')
|
||||
end
|
||||
|
|
|
@ -135,10 +135,6 @@ describe Projects::RepositoriesController, 'routing' do
|
|||
it 'to #archive format:tar.bz2' do
|
||||
expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2')
|
||||
end
|
||||
|
||||
it 'to #show' do
|
||||
expect(get('/gitlab/gitlabhq/repository')).to route_to('projects/repositories#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
|
||||
end
|
||||
end
|
||||
|
||||
describe Projects::BranchesController, 'routing' do
|
||||
|
|
|
@ -176,18 +176,10 @@ describe Profiles::KeysController, "routing" do
|
|||
expect(post("/profile/keys")).to route_to('profiles/keys#create')
|
||||
end
|
||||
|
||||
it "to #edit" do
|
||||
expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1')
|
||||
end
|
||||
|
||||
it "to #show" do
|
||||
expect(get("/profile/keys/1")).to route_to('profiles/keys#show', id: '1')
|
||||
end
|
||||
|
||||
it "to #update" do
|
||||
expect(put("/profile/keys/1")).to route_to('profiles/keys#update', id: '1')
|
||||
end
|
||||
|
||||
it "to #destroy" do
|
||||
expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1')
|
||||
end
|
||||
|
|
|
@ -20,7 +20,8 @@ module TestEnv
|
|||
'gitattributes' => '5a62481',
|
||||
'expand-collapse-diffs' => '4842455',
|
||||
'expand-collapse-files' => '025db92',
|
||||
'expand-collapse-lines' => '238e82d'
|
||||
'expand-collapse-lines' => '238e82d',
|
||||
'video' => '8879059'
|
||||
}
|
||||
|
||||
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
|
||||
|
|
|
@ -49,4 +49,24 @@ describe 'projects/builds/show' do
|
|||
text: /\A\n#{Regexp.escape(commit_title)}\n\Z/)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'shows trigger variables in sidebar' do
|
||||
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline) }
|
||||
|
||||
before do
|
||||
build.trigger_request = trigger_request
|
||||
render
|
||||
end
|
||||
|
||||
it 'shows trigger variables in separate lines' do
|
||||
expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_1', 'TRIGGER_VALUE_1'))
|
||||
expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_2', 'TRIGGER_VALUE_2'))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def variable_regexp(key, value)
|
||||
/\A#{Regexp.escape("#{key}=#{value}")}\Z/
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,21 +14,24 @@ describe RepositoryForkWorker do
|
|||
describe "#perform" do
|
||||
it "creates a new repository from a fork" do
|
||||
expect(shell).to receive(:fork_repository).with(
|
||||
project.repository_storage_path,
|
||||
'/test/path',
|
||||
project.path_with_namespace,
|
||||
project.repository_storage_path,
|
||||
fork_project.namespace.path
|
||||
).and_return(true)
|
||||
|
||||
subject.perform(
|
||||
project.id,
|
||||
'/test/path',
|
||||
project.path_with_namespace,
|
||||
fork_project.namespace.path)
|
||||
end
|
||||
|
||||
it 'flushes various caches' do
|
||||
expect(shell).to receive(:fork_repository).with(
|
||||
project.repository_storage_path,
|
||||
'/test/path',
|
||||
project.path_with_namespace,
|
||||
project.repository_storage_path,
|
||||
fork_project.namespace.path
|
||||
).and_return(true)
|
||||
|
||||
|
@ -38,7 +41,7 @@ describe RepositoryForkWorker do
|
|||
expect_any_instance_of(Repository).to receive(:expire_exists_cache).
|
||||
and_call_original
|
||||
|
||||
subject.perform(project.id, project.path_with_namespace,
|
||||
subject.perform(project.id, '/test/path', project.path_with_namespace,
|
||||
fork_project.namespace.path)
|
||||
end
|
||||
|
||||
|
@ -49,6 +52,7 @@ describe RepositoryForkWorker do
|
|||
|
||||
subject.perform(
|
||||
project.id,
|
||||
'/test/path',
|
||||
project.path_with_namespace,
|
||||
fork_project.namespace.path)
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# The image already has Hex installed. You might want to consider to use `elixir:latest`
|
||||
image: trenpixster/elixir:latest
|
||||
|
||||
# Pic zero or more services to be used on all builds.
|
||||
# Pick zero or more services to be used on all builds.
|
||||
# Only needed when using a docker container to run your tests in.
|
||||
# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
|
||||
services:
|
||||
|
|
Loading…
Reference in New Issue