Commit 365adcf6 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into breadcrumbs-improvements

parents 78b0c35c d4ec6318
......@@ -125,6 +125,7 @@ stages:
- export KNAPSACK_GENERATE_REPORT=true
- export CACHE_CLASSES=true
- cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
- scripts/gitaly-test-spawn
- knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts:
expire_in: 31d
......
import Cookies from 'js-cookie';
import _ from 'underscore';
import {
getCookieName,
getSelector,
hidePopover,
setupDismissButton,
mouseenter,
mouseleave,
} from './feature_highlight_helper';
export const setupFeatureHighlightPopover = (id, debounceTimeout = 300) => {
const $selector = $(getSelector(id));
const $parent = $selector.parent();
const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
const hideOnScroll = hidePopover.bind($selector);
const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout);
$selector
// Setup popover
.data('content', $popoverContent.prop('outerHTML'))
.popover({
html: true,
// Override the existing template to add custom CSS classes
template: `
<div class="popover feature-highlight-popover" role="tooltip">
<div class="arrow"></div>
<div class="popover-content"></div>
</div>
`,
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave)
.on('inserted.bs.popover', setupDismissButton)
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll);
})
.on('hide.bs.popover', () => {
window.removeEventListener('scroll', hideOnScroll);
})
// Display feature highlight
.removeAttr('disabled');
};
export const shouldHighlightFeature = (id) => {
const element = document.querySelector(getSelector(id));
const previouslyDismissed = Cookies.get(getCookieName(id)) === 'true';
return element && !previouslyDismissed;
};
export const highlightFeatures = (highlightOrder) => {
const featureId = highlightOrder.find(shouldHighlightFeature);
if (featureId) {
setupFeatureHighlightPopover(featureId);
return true;
}
return false;
};
import Cookies from 'js-cookie';
export const getCookieName = cookieId => `feature-highlighted-${cookieId}`;
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export const showPopover = function showPopover() {
if (this.hasClass('js-popover-show')) {
return false;
}
this.popover('show');
this.addClass('disable-animation js-popover-show');
return true;
};
export const hidePopover = function hidePopover() {
if (!this.hasClass('js-popover-show')) {
return false;
}
this.popover('hide');
this.removeClass('disable-animation js-popover-show');
return true;
};
export const dismiss = function dismiss(cookieId) {
Cookies.set(getCookieName(cookieId), true);
hidePopover.call(this);
this.hide();
};
export const mouseleave = function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $featureHighlight = $(this);
hidePopover.call($featureHighlight);
}
};
export const mouseenter = function mouseenter() {
const $featureHighlight = $(this);
const showedPopover = showPopover.call($featureHighlight);
if (showedPopover) {
$('.popover')
.on('mouseleave', mouseleave.bind($featureHighlight));
}
};
export const setupDismissButton = function setupDismissButton() {
const popoverId = this.getAttribute('aria-describedby');
const cookieId = this.dataset.highlight;
const $popover = $(this);
const dismissWrapper = dismiss.bind($popover, cookieId);
$(`#${popoverId} .dismiss-feature-highlight`)
.on('click', dismissWrapper);
};
import { highlightFeatures } from './feature_highlight';
import bp from '../breakpoints';
const highlightOrder = ['issue-boards'];
export default function domContentLoaded(order) {
if (bp.getBreakpointSize() === 'lg') {
highlightFeatures(order);
}
}
document.addEventListener('DOMContentLoaded', domContentLoaded.bind(this, highlightOrder));
......@@ -102,6 +102,7 @@ import './label_manager';
import './labels';
import './labels_select';
import './layout_nav';
import './feature_highlight/feature_highlight_options';
import LazyLoader from './lazy_loader';
import './line_highlighter';
import './logo';
......
......@@ -51,3 +51,4 @@
@import "framework/snippets";
@import "framework/memory_graph";
@import "framework/responsive-tables";
@import "framework/feature_highlight";
......@@ -46,6 +46,15 @@
}
}
@mixin btn-svg {
svg {
height: 15px;
width: 15px;
position: relative;
top: 2px;
}
}
@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) {
background-color: $light;
border-color: $border-light;
......@@ -123,6 +132,7 @@
.btn {
@include btn-default;
@include btn-white;
@include btn-svg;
color: $gl-text-color;
......@@ -222,13 +232,6 @@
}
}
svg {
height: 15px;
width: 15px;
position: relative;
top: 2px;
}
svg,
.fa {
&:not(:last-child) {
......
......@@ -737,6 +737,8 @@
@mixin new-style-dropdown($selector: '') {
#{$selector}.dropdown-menu,
#{$selector}.dropdown-menu-nav {
margin-bottom: 24px;
li {
display: block;
padding: 0 1px;
......@@ -768,7 +770,7 @@
// make sure the text color is not overriden
&.text-danger {
// @extend .text-danger;
color: $brand-danger;
}
&.is-focused,
......@@ -777,6 +779,11 @@
&:focus {
background-color: $dropdown-item-hover-bg;
color: $gl-text-color;
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
}
}
&.is-active {
......
.feature-highlight {
position: relative;
margin-left: $gl-padding;
width: 20px;
height: 20px;
cursor: pointer;
&::before {
content: '';
display: block;
position: absolute;
top: 6px;
left: 6px;
width: 8px;
height: 8px;
background-color: $blue-500;
border-radius: 50%;
box-shadow: 0 0 0 rgba($blue-500, 0.4);
animation: pulse-highlight 2s infinite;
}
&:hover::before,
&.disable-animation::before {
animation: none;
}
&[disabled]::before {
display: none;
}
}
.is-showing-fly-out {
.feature-highlight {
display: none;
}
}
.feature-highlight-popover-content {
display: none;
hr {
margin: $gl-padding * 0.5 0;
}
.btn-link {
@include btn-svg;
svg path {
fill: currentColor;
}
}
.dismiss-feature-highlight {
padding: 0;
}
svg:first-child {
width: 100%;
background-color: $indigo-50;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
border-bottom: 1px solid darken($gray-normal, 8%);
}
}
.popover .feature-highlight-popover-content {
display: block;
}
.feature-highlight-popover {
padding: 0;
.popover-content {
padding: 0;
}
}
.feature-highlight-popover-sub-content {
padding: 9px 14px;
}
@include keyframes(pulse-highlight) {
0% {
box-shadow: 0 0 0 0 rgba($blue-200, 0.4);
}
70% {
box-shadow: 0 0 0 10px transparent;
}
100% {
box-shadow: 0 0 0 0 transparent;
}
}
......@@ -267,14 +267,23 @@
// TODO: change global style
.ajax-project-dropdown,
body[data-page="projects:edit"] #select2-drop,
body[data-page="projects:new"] #select2-drop,
body[data-page="projects:merge_requests:edit"] #select2-drop,
body[data-page="projects:blob:new"] #select2-drop,
body[data-page="profiles:show"] #select2-drop,
body[data-page="projects:blob:edit"] #select2-drop {
&.select2-drop {
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
color: $gl-text-color;
}
&.select2-drop-above {
border-top: none;
margin-top: -4px;
}
.select2-results {
.select2-no-results,
.select2-searching,
......
......@@ -143,8 +143,12 @@ ul.related-merge-requests > li {
}
}
.issue-form .select2-container {
.issue-form {
@include new-style-dropdown;
.select2-container {
width: 250px !important;
}
}
.issues-footer {
......
......@@ -23,6 +23,8 @@
.new-note,
.note-edit-form {
.note-form-actions {
@include new-style-dropdown;
position: relative;
margin: $gl-padding 0 0;
}
......
......@@ -800,8 +800,10 @@ pre.light-well {
}
}
.new_protected_branch,
.new-protected-branch,
.new-protected-tag {
@include new-style-dropdown;
label {
margin-top: 6px;
font-weight: $gl-font-weight-normal;
......@@ -821,19 +823,9 @@ pre.light-well {
.protected-branches-list,
.protected-tags-list {
margin-bottom: 30px;
a {
color: $gl-text-color;
&:hover {
color: $gl-link-color;
}
@include new-style-dropdown;
&.is-active {
font-weight: $gl-font-weight-bold;
}
}
margin-bottom: 30px;
.settings-message {
margin: 0;
......
......@@ -305,14 +305,6 @@ module IssuablesHelper
cookies[:collapsed_gutter] == 'true'
end
def issuable_state_scope(issuable)
if issuable.respond_to?(:merged?) && issuable.merged?
:merged
else
issuable.open? ? :opened : :closed
end
end
def issuable_templates(issuable)
@issuable_templates ||=
case issuable
......
......@@ -47,13 +47,6 @@ module IssuesHelper
end
end
def bulk_update_milestone_options
milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
end
def milestone_options(object)
milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(object.milestone) if object.milestone.present? && object.milestone.closed?
......@@ -93,14 +86,6 @@ module IssuesHelper
return 'hidden' if issue.closed? == closed
end
def merge_requests_sentence(merge_requests)
# Sorting based on the `!123` or `group/project!123` reference will sort
# local merge requests first.
merge_requests.map do |merge_request|
merge_request.to_reference(@project)
end.sort.to_sentence(last_word_connector: ', or ')
end
def confidential_icon(issue)
icon('eye-slash') if issue.confidential?
end
......@@ -148,18 +133,6 @@ module IssuesHelper
end.to_h
end
def due_date_options
options = [
Issue::AnyDueDate,
Issue::NoDueDate,
Issue::DueThisWeek,
Issue::DueThisMonth,
Issue::Overdue
]
options_from_collection_for_select(options, 'name', 'title', params[:due_date])
end
def link_to_discussions_to_resolve(merge_request, single_discussion = nil)
link_text = merge_request.to_reference
link_text += " (discussion #{single_discussion.first_note.id})" if single_discussion
......
......@@ -305,6 +305,10 @@ module Ci
@stage_seeds ||= config_processor.stage_seeds(self)
end
def has_kubernetes_active?
project.kubernetes_service&.active?
end
def has_stage_seeds?
stage_seeds.any?
end
......
......@@ -957,13 +957,6 @@ class MergeRequest < ActiveRecord::Base
private
def write_ref
target_project.repository.with_repo_branch_commit(
source_project.repository, source_branch) do |commit|
if commit
target_project.repository.write_ref(ref_path, commit.sha)
else
raise Rugged::ReferenceError, 'source repository is empty'
end
end
target_project.repository.fetch_source_branch(source_project.repository, source_branch, ref_path)
end
end
......@@ -68,7 +68,6 @@ class Project < ActiveRecord::Base
acts_as_taggable
attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
attr_accessor :template_name
attr_writer :pipeline_status
......
......@@ -20,7 +20,6 @@ class Repository
delegate :ref_name_for_sha, to: :raw_repository
CommitError = Class.new(StandardError)
CreateTreeError = Class.new(StandardError)
# Methods that cache data from the Git repository.
......@@ -95,19 +94,6 @@ class Repository
"#<#{self.class.name}:#{@disk_path}>"
end
#
# Git repository can contains some hidden refs like:
# /refs/notes/*
# /refs/git-as-svn/*
# /refs/pulls/*
# This refs by default not visible in project page and not cloned to client side.
#
# This method return true if repository contains some content visible in project page.
#
def has_visible_content?
branch_count > 0
end
def commit(ref = 'HEAD')
return nil unless exists?
......@@ -184,7 +170,7 @@ class Repository
return false unless newrev
GitOperationService.new(user, self).add_branch(branch_name, newrev)
Gitlab::Git::OperationService.new(user, raw_repository).add_branch(branch_name, newrev)
after_create_branch
find_branch(branch_name)
......@@ -196,7 +182,7 @@ class Repository
return false unless newrev
GitOperationService.new(user, self).add_tag(tag_name, newrev, options)
Gitlab::Git::OperationService.new(user, raw_repository).add_tag(tag_name, newrev, options)
find_tag(tag_name)
end
......@@ -205,7 +191,7 @@ class Repository
before_remove_branch
branch = find_branch(branch_name)
GitOperationService.new(user, self).rm_branch(branch)
Gitlab::Git::OperationService.new(user, raw_repository).rm_branch(branch)
after_remove_branch
true
......@@ -215,7 +201,7 @@ class Repository
before_remove_tag
tag = find_tag(tag_name)
GitOperationService.new(user, self).rm_tag(tag)
Gitlab::Git::OperationService.new(user, raw_repository).rm_tag(tag)
after_remove_tag
true
......@@ -784,16 +770,30 @@ class Repository
multi_action(**options)
end
def with_branch(user, *args)
result = Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit|
yield start_commit
end
newrev, should_run_after_create, should_run_after_create_branch = result
after_create if should_run_after_create
after_create_branch if should_run_after_create_branch
newrev
end
# rubocop:disable Metrics/ParameterLists
def multi_action(
user:, branch_name:, message:, actions:,
author_email: nil, author_name: nil,
start_branch_name: nil, start_project: project)
GitOperationService.new(user, self).with_branch(
with_branch(
user,
branch_name,
start_branch_name: start_branch_name,
start_project: start_project) do |start_commit|
start_repository: start_project.repository.raw_repository) do |start_commit|
index = Gitlab::Git::Index.new(raw_repository)
......@@ -846,7 +846,8 @@ class Repository
end
def merge(user, source, merge_request, options = {})
GitOperationService.new(user, self).with_branch(
with_branch(
user,
merge_request.target_branch) do |start_commit|
our_commit = start_commit.sha
their_commit = source
......@@ -866,17 +867,18 @@ class Repository
merge_request.update(in_progress_merge_commit_sha: commit_id)
commit_id
end
rescue Repository::CommitError # when merge_index.conflicts?
rescue Gitlab::Git::CommitError # when merge_index.conflicts?
false
end
def revert(
user, commit, branch_name,
start_branch_name: nil, start_project: project)
GitOperationService.new(user, self).with_branch(
with_branch(
user,
branch_name,
start_branch_name: start_branch_name,
start_project: start_project) do |start_commit|
start_repository: start_project.repository.raw_repository) do |start_commit|
revert_tree_id = check_revert_content(commit, start_commit.sha)
unless revert_tree_id
......@@ -896,10 +898,11 @@ class Repository
def cherry_pick(
user, commit, branch_name,
start_branch_name: nil, start_project: project)
GitOperationService.new(user, self).with_branch(
with_branch(
user,
branch_name,
start_branch_name: start_branch_name,
start_project: start_project) do |start_commit|
start_repository: start_project.repository.raw_repository) do |start_commit|
cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha)
unless cherry_pick_tree_id
......@@ -921,7 +924,7 @@ class Repository
end
def resolve_conflicts(user, branch_name, params)
GitOperationService.new(user, self).with_branch(branch_name) do
with_branch(user, branch_name) do
committer = user_to_committer(user)
create_commit(params.merge(author: committer, committer: committer))
......@@ -1011,25 +1014,6 @@ class Repository
run_git(args).first.lines.map(&:strip)
end
def with_repo_branch_commit(start_repository, start_branch_name)
return yield nil if start_repository.empty_repo?
if start_repository == self
yield commit(start_branch_name)
else
sha = start_repository.commit(start_branch_name).sha
if branch_commit = commit(sha)
yield branch_commit
else
with_repo_tmp_commit(
start_repository, start_branch_name, sha) do |tmp_commit|
yield tmp_commit
end
end
end
end
def add_remote(name, url)
raw_repository.remote_add(name, url)
rescue Rugged::ConfigError
......@@ -1047,14 +1031,12 @@ class Repository
gitlab_shell.fetch_remote(raw_repository, remote, forced: forced, no_tags: no_tags)
end
def fetch_ref(source_path, source_ref, target_ref)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
message, status = run_git(args)
# Make sure ref was created, and raise Rugged::ReferenceError when not
raise Rugged::ReferenceError, message if status != 0
def fetch_source_branch(source_repository, source_branch, local_ref)
raw_repository.fetch_source_branch(source_repository.raw_repository, source_branch, local_ref)
end
target_ref
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
raw_repository.compare_source_branch(target_branch_name, source_repository.raw_repository, source_branch_name, straight: straight)
end
def create_ref(ref, ref_path)
......@@ -1135,12 +1117,6 @@ class Repository
private
def run_git(args)
circuit_breaker.perform do
Gitlab::Popen.popen([Gitlab.config.git.bin_path, *args], path_to_repo)
end
end
def blob_data_at(sha, path)
blob = blob_at(sha, path)
return unless blob
......@@ -1236,16 +1212,4 @@ class Repository
.commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
.map { |c| commit(c) }
end
def with_repo_tmp_commit(start_repository, start_branch_name, sha)
tmp_ref = fetch_ref(
start_repository.path_to_repo,
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
"refs/tmp/#{SecureRandom.hex}/head"
)
yield commit(sha)
ensure
delete_refs(tmp_ref) if tmp_ref
end
end
......@@ -644,11 +644,6 @@ class User < ActiveRecord::Base
@personal_projects_count ||= personal_projects.count
end
def projects_limit_percent
return 100 if projects_limit.zero?
(personal_projects.count.to_f / projects_limit) * 100
end
def recent_push(project_ids = nil)
# Get push events not earlier than 2 hours ago
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
......@@ -666,10 +661,6 @@ class User < ActiveRecord::Base
end
end
def projects_sorted_by_activity
authorized_projects.sorted_by_activity
end
def several_namespaces?
owned_groups.any? || masters_groups.any?
end
......
......@@ -17,7 +17,7 @@ module Commits
new_commit = create_commit!
success(result: new_commit)
rescue ValidationError, ChangeError, Gitlab::Git::Index::IndexError, Repository::CommitError, Gitlab::Git::HooksService::PreReceiveError => ex
rescue ValidationError, ChangeError, Gitlab::Git::Index::IndexError, Gitlab::Git::CommitError, Gitlab::Git::HooksService::PreReceiveError => ex
error(ex.message)
end
......
......@@ -11,26 +11,8 @@ class CompareService
end
def execute(target_project, target_branch, straight: false)
# If compare with other project we need to fetch ref first
target_project.repository.with_repo_branch_commit(
start_project.repository,
start_branch_name) do |commit|
break unless commit
raw_compare = target_project.repository.compare_source_branch(target_branch, start_project.repository, start_branch_name, straight: straight)
compare(commit.sha, target_project, target_branch, straight: straight)
end
end
private
def compare(source_sha, target_project, target_branch, straight:)
raw_compare = Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
source_sha,
straight: straight
)
Compare.new(raw_compare, target_project, straight: straight)
Compare.new(raw_compare, target_project, straight: straight) if raw_compare
end
end
class GitOperationService
attr_reader :committer, :repository
def initialize(committer, new_repository)
committer = Gitlab::Git::Committer.from_user(committer) if committer.is_a?(User)
@committer = committer
@repository = new_repository
end
def add_branch(branch_name, newrev)
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
oldrev = Gitlab::Git::BLANK_SHA
update_ref_in_hooks(ref, newrev, oldrev)
end
def rm_branch(branch)
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name
oldrev = branch.target
newrev = Gitlab::Git::BLANK_SHA
update_ref_in_hooks(ref, newrev, oldrev)
end
def add_tag(tag_name, newrev, options = {})
ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
oldrev = Gitlab::Git::BLANK_SHA
with_hooks(ref, newrev, oldrev) do |service|
# We want to pass the OID of the tag object to the hooks. For an
# annotated tag we don't know that OID until after the tag object
# (raw_tag) is created in the repository. That is why we have to
# update the value after creating the tag object. Only the
# "post-receive" hook will receive the correct value in this case.
raw_tag = repository.rugged.tags.create(tag_name, newrev, options)
service.newrev = raw_tag.target_id
end
end
def rm_tag(tag)
ref = Gitlab::Git::TAG_REF_PREFIX + tag.name
oldrev = tag.target
newrev = Gitlab::Git::BLANK_SHA
update_ref_in_hooks(ref, newrev, oldrev) do
repository.rugged.tags.delete(tag_name)
end
end
# Whenever `start_branch_name` is passed, if `branch_name` doesn't exist,
# it would be created from `start_branch_name`.
# If `start_project` is passed, and the branch doesn't exist,
# it would try to find the commits from it instead of current repository.
def with_branch(
branch_name,
start_branch_name: nil,
start_project: repository.project,
&block)
start_repository = start_project.repository
start_branch_name = nil if start_repository.empty_repo?
if start_branch_name && !start_repository.branch_exists?(start_branch_name)
raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.full_path}"
end
update_branch_with_hooks(branch_name) do
repository.with_repo_branch_commit(
start_repository,
start_branch_name || branch_name,
&block)
end
end
private
def update_branch_with_hooks(branch_name)
update_autocrlf_option
was_empty = repository.empty?
# Make commit
newrev = yield
unless newrev
raise Repository::CommitError.new('Failed to create commit')
end
branch = repository.find_branch(branch_name)
oldrev = find_oldrev_from_branch(newrev, branch)
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
update_ref_in_hooks(ref, newrev, oldrev)
# If repo was empty expire cache
repository.after_create if was_empty
repository.after_create_branch if
was_empty || Gitlab::Git.blank_ref?(oldrev)
newrev
end
def find_oldrev_from_branch(newrev, branch)
return Gitlab::Git::BLANK_SHA unless branch
oldrev = branch.target
if oldrev == repository.rugged.merge_base(newrev, branch.target)
oldrev
else
raise Repository::CommitError.new('Branch diverged')
end
end
def update_ref_in_hooks(ref, newrev, oldrev)
with_hooks(ref, newrev, oldrev) do
update_ref(ref, newrev, oldrev)
end
end
def with_hooks(ref, newrev, oldrev)
Gitlab::Git::HooksService.new.execute(
committer,
repository,
oldrev,
newrev,
ref) do |service|
yield(service)
end
end
# Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites.
def update_ref(ref, newrev, oldrev)
# We use 'git update-ref' because libgit2/rugged currently does not
# offer 'compare and swap' ref updates. Without compare-and-swap we can
# (and have!) accidentally reset the ref to an earlier state, clobbering
# commits. See also https://github.com/libgit2/libgit2/issues/1534.
command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
_, status = Gitlab::Popen.popen(
command,
repository.path_to_repo) do |stdin|
stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
end
unless status.zero?
raise Repository::CommitError.new(
"Could not update branch #{Gitlab::Git.branch_name(ref)}." \
" Please refresh and try again.")
end
end
def update_autocrlf_option
if repository.raw_repository.autocrlf != :input
repository.raw_repository.autocrlf = :input
end
end
end
......@@ -22,10 +22,10 @@
%b Tag list:
= build[:tag_list].to_a.join(", ")
%br
%b Refs only:
%b Only policy:
= @jobs[build[:name].to_sym][:only].to_a.join(", ")
%br
%b Refs except:
%b Except policy:
= @jobs[build[:name].to_sym][:except].to_a.join(", ")
%br
%b Environment:
......
<svg xmlns="http://www.w3.org/2000/svg" width="214" height="102" viewBox="0 0 214 102" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path id="b" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,27 C48,28.1045695 47.1045695,29 46,29 L2,29 C0.8954305,29 1.3527075e-16,28.1045695 0,27 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="a" width="102.1%" height="106.9%" x="-1%" y="-1.7%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="d" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="c" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="e" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
<path id="h" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="g" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="j" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="i" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="l" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="k" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="n" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="m" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="p" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="o" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
</defs>
<g fill="none" fill-rule="evenodd">
<path fill="#D6D4DE" d="M14,21 L62,21 C64.7614237,21 67,23.2385763 67,26 L67,112 C67,114.761424 64.7614237,117 62,117 L14,117 C11.2385763,117 9,114.761424 9,112 L9,26 C9,23.2385763 11.2385763,21 14,21 Z"/>
<g transform="translate(11 23)">
<path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
<path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/>
<g transform="translate(5 10)">
<use fill="black" filter="url(#a)" xlink:href="#b"/>
<use fill="#F9F9F9" xlink:href="#b"/>
</g>
<g transform="translate(5 42)">
<use fill="black" filter="url(#c)" xlink:href="#d"/>
<use fill="#FEF0E8" xlink:href="#d"/>
<path fill="#FEE1D3" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/>
<path fill="#FDC4A8" d="M9,17 L17,17 C18.1045695,17 19,17.8954305 19,19 C19,20.1045695 18.1045695,21 17,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/>
<path fill="#FC6D26" d="M24,17 L32,17 C33.1045695,17 34,17.8954305 34,19 C34,20.1045695 33.1045695,21 32,21 L24,21 C22.8954305,21 22,20.1045695 22,19 C22,17.8954305 22.8954305,17 24,17 Z"/>
</g>
</g>
<path fill="#D6D4DE" d="M148,26 L196,26 C198.761424,26 201,28.2385763 201,31 L201,117 C201,119.761424 198.761424,122 196,122 L148,122 C145.238576,122 143,119.761424 143,117 L143,31 C143,28.2385763 145.238576,26 148,26 Z"/>
<g transform="translate(145 28)">
<mask id="f" fill="white">
<use xlink:href="#e"/>
</mask>
<use fill="#FFFFFF" xlink:href="#e"/>
<path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z" mask="url(#f)"/>
<g transform="translate(5 10)">
<use fill="black" filter="url(#g)" xlink:href="#h"/>
<use fill="#F9F9F9" xlink:href="#h"/>
</g>
<g transform="translate(5 42)">
<use fill="black" filter="url(#i)" xlink:href="#j"/>
<use fill="#FEF0E8" xlink:href="#j"/>
<path fill="#FEE1D3" d="M9 8L33 8C34.1045695 8 35 8.8954305 35 10 35 11.1045695 34.1045695 12 33 12L9 12C7.8954305 12 7 11.1045695 7 10 7 8.8954305 7.8954305 8 9 8zM9 17L13 17C14.1045695 17 15 17.8954305 15 19 15 20.1045695 14.1045695 21 13 21L9 21C7.8954305 21 7 20.1045695 7 19 7 17.8954305 7.8954305 17 9 17z"/>
<path fill="#FC6D26" d="M20,17 L24,17 C25.1045695,17 26,17.8954305 26,19 C26,20.1045695 25.1045695,21 24,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/>
<path fill="#FDC4A8" d="M31,17 L35,17 C36.1045695,17 37,17.8954305 37,19 C37,20.1045695 36.1045695,21 35,21 L31,21 C29.8954305,21 29,20.1045695 29,19 C29,17.8954305 29.8954305,17 31,17 Z"/>
</g>
</g>
<path fill="#D6D4DE" d="M81,14 L129,14 C131.761424,14 134,16.2385763 134,19 L134,105 C134,107.761424 131.761424,110 129,110 L81,110 C78.2385763,110 76,107.761424 76,105 L76,19 C76,16.2385763 78.2385763,14 81,14 Z"/>
<g transform="translate(78 16)">
<path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
<g transform="translate(5 10)">
<use fill="black" filter="url(#k)" xlink:href="#l"/>
<use fill="#EFEDF8" xlink:href="#l"/>
<path fill="#E1DBF1" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/>
<path fill="#6B4FBB" d="M9,17 L13,17 C14.1045695,17 15,17.8954305 15,19 C15,20.1045695 14.1045695,21 13,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/>
<path fill="#C3B8E3" d="M20,17 L28,17 C29.1045695,17 30,17.8954305 30,19 C30,20.1045695 29.1045695,21 28,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/>
</g>
<g transform="translate(5 42)">
<use fill="black" filter="url(#m)" xlink:href="#n"/>
<use fill="#F9F9F9" xlink:href="#n"/>
</g>
<g transform="translate(5 74)">
<rect width="34" height="4" x="7" y="7" fill="#E1DBF1" rx="2"/>
<use fill="black" filter="url(#o)" xlink:href="#p"/>
<use fill="#F9F9F9" xlink:href="#p"/>
</g>
<path fill="#6B4FBB" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/>
</g>
</g>
</svg>
......@@ -99,6 +99,20 @@
= link_to project_boards_path(@project), title: 'Board' do
%span
Board
.feature-highlight.js-feature-highlight{ disabled: true, data: { trigger: 'manual', container: 'body', toggle: 'popover', placement: 'right', highlight: 'issue-boards' } }
.feature-highlight-popover-content
= render 'feature_highlight/issue_boards.svg'
.feature-highlight-popover-sub-content
%span= _('Use')
= link_to 'Issue Boards', project_boards_path(@project)
%span= _('to create customized software development workflows like')
%strong= _('Scrum')
%span= _('or')
%strong= _('Kanban')
%hr
%button.btn-link.dismiss-feature-highlight{ type: 'button' }
%span= _("Got it! Don't show this again")
= custom_icon('thumbs_up')
= nav_link(controller: :labels) do
= link_to project_labels_path(@project), title: 'Labels' do
......
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.104 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.486.5l.138.137a1 1 0 0 1 .28.87L8.33 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></svg>
---
title: Deprecate custom SSH client configuration for the git user
merge_request: 13930
author:
type: deprecated
---
title: Add CI/CD active kubernetes job policy
merge_request: 13849
author:
type: added
......@@ -410,3 +410,9 @@
:why: https://gitlab.com/gitlab-com/organization/issues/116
:versions: []
:when: 2017-09-01 17:17:51.996511844 Z
- - :blacklist
- Facebook BSD+PATENTS
- :who: Nick Thomas <nick@gitlab.com>
:why: https://gitlab.com/gitlab-com/organization/issues/117
:versions: []
:when: 2017-09-04 12:59:51.150798717 Z
......@@ -508,7 +508,7 @@ production: &base
failure_count_threshold: 10 # number of failures before stopping attempts
failure_wait_time: 30 # Seconds after an access failure before allowing access again
failure_reset_time: 1800 # Time in seconds to expire failures
storage_timeout: 5 # Time in seconds to wait before aborting a storage access attempt
storage_timeout: 30 # Time in seconds to wait before aborting a storage access attempt
## Backup settings
......
......@@ -427,16 +427,16 @@ a "key: value" pair. Be careful when using special characters:
are executed in `parallel`. For more info about the use of `stage` please check
[stages](#stages).
### only and except
### only and except (simplified)
`only` and `except` are two parameters that set a refs policy to limit when
jobs are built:
`only` and `except` are two parameters that set a job policy to limit when
jobs are created:
1. `only` defines the names of branches and tags for which the job will run.
2. `except` defines the names of branches and tags for which the job will
**not** run.
There are a few rules that apply to the usage of refs policy:
There are a few rules that apply to the usage of job policy:
* `only` and `except` are inclusive. If both `only` and `except` are defined
in a job specification, the ref is filtered by `only` and `except`.
......@@ -497,6 +497,36 @@ job:
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except master.
### only and except (complex)
> Introduced in GitLab 10.0
> This an _alpha_ feature, and it it subject to change at any time without
prior notice!
Since GitLab 10.0 it is possible to define a more elaborate only/except job
policy configuration.
GitLab now supports both, simple and complex strategies, so it is possible to
use an array and a hash configuration scheme.
Two keys are now available: `refs` and `kubernetes`. Refs strategy equals to
simplified only/except configuration, whereas kubernetes strategy accepts only
`active` keyword.
See the example below. Job is going to be created only when pipeline has been
scheduled or runs for a `master` branch, and only if kubernetes service is
active in the project.
```yaml
job:
only:
refs:
- master
- schedules
kubernetes: active
```
### Job variables
It is possible to define job variables using a `variables` keyword on a job
......
This diff is collapsed.
......@@ -64,6 +64,7 @@ Libraries with the following licenses are unacceptable for use:
- [GNU GPL][GPL] (version 1, [version 2][GPLv2], [version 3][GPLv3], or any future versions): GPL-licensed libraries cannot be linked to from non-GPL projects.
- [GNU AGPLv3][AGPLv3]: AGPL-licensed libraries cannot be linked to from non-GPL projects.
- [Open Software License (OSL)][OSL]: is a copyleft license. In addition, the FSF [recommend against its use][OSL-GNU].
- [Facebook BSD + PATENTS][Facebook]: is a 3-clause BSD license with a patent grant that has been deemed [Category X][x-list] by the Apache foundation.
## Requesting Approval for Licenses
......@@ -103,5 +104,7 @@ Gems which are included only in the "development" or "test" groups by Bundler ar
[OSL-GNU]: https://www.gnu.org/licenses/license-list.en.html#OSL
[Org-Repo]: https://gitlab.com/gitlab-com/organization
[UNLICENSE]: https://unlicense.org
[Facebook]: https://code.facebook.com/pages/850928938376556
[x-list]: https://www.apache.org/legal/resolved.html#category-x
[Acceptable-Licenses]: #acceptable-licenses
[Unacceptable-Licenses]: #unacceptable-licenses
......@@ -193,6 +193,38 @@ How to add your SSH key to Eclipse: https://wiki.eclipse.org/EGit/User_Guide#Ecl
[winputty]: https://the.earth.li/~sgtatham/putty/0.67/htmldoc/Chapter8.html#pubkey-puttygen
## SSH on the GitLab server
GitLab integrates with the system-installed SSH daemon, designating a user
(typically named `git`) through which all access requests are handled. Users
connecting to the GitLab server over SSH are identified by their SSH key instead
of their username.
SSH *client* operations performed on the GitLab server wil be executed as this
user. Although it is possible to modify the SSH configuration for this user to,
e.g., provide a private SSH key to authenticate these requests by, this practice
is **not supported** and is strongly discouraged as it presents significant
security risks.
The GitLab check process includes a check for this condition, and will direct you
to this section if your server is configured like this, e.g.:
```
$ gitlab-rake gitlab:check
# ...
Git user has default SSH configuration? ... no
Try fixing it:
mkdir ~/gitlab-check-backup-1504540051
sudo mv /var/lib/git/.ssh/id_rsa ~/gitlab-check-backup-1504540051
sudo mv /var/lib/git/.ssh/id_rsa.pub ~/gitlab-check-backup-1504540051
For more information see:
doc/ssh/README.md in section "SSH on the GitLab server"
Please fix the error above and rerun the checks.
```
Remove the custom configuration as soon as you're able to. These customizations
are *explicitly not supported* and may stop working at any time.
## Troubleshooting
If on Git clone you are prompted for a password like `git@gitlab.com's password:`
......
......@@ -20,24 +20,6 @@ module Ci
raise ValidationError, e.message
end
def jobs_for_ref(ref, tag = false, source = nil)
@jobs.select do |_, job|
process?(job[:only], job[:except], ref, tag, source)
end
end
def jobs_for_stage_and_ref(stage, ref, tag = false, source = nil)
jobs_for_ref(ref, tag, source).select do |_, job|
job[:stage] == stage
end
end
def builds_for_ref(ref, tag = false, source = nil)
jobs_for_ref(ref, tag, source).map do |name, _|
build_attributes(name)
end
end
def builds_for_stage_and_ref(stage, ref, tag = false, source = nil)
jobs_for_stage_and_ref(stage, ref, tag, source).map do |name, _|
build_attributes(name)
......@@ -52,8 +34,7 @@ module Ci
def stage_seeds(pipeline)
seeds = @stages.uniq.map do |stage|
builds = builds_for_stage_and_ref(
stage, pipeline.ref, pipeline.tag?, pipeline.source)
builds = pipeline_stage_builds(stage, pipeline)
Gitlab::Ci::Stage::Seed.new(pipeline, stage, builds) if builds.any?
end
......@@ -101,6 +82,34 @@ module Ci
private
def pipeline_stage_builds(stage, pipeline)
builds = builds_for_stage_and_ref(
stage, pipeline.ref, pipeline.tag?, pipeline.source)
builds.select do |build|
job = @jobs[build.fetch(:name).to_sym]
has_kubernetes = pipeline.has_kubernetes_active?
only_kubernetes = job.dig(:only, :kubernetes)
except_kubernetes = job.dig(:except, :kubernetes)
[!only_kubernetes && !except_kubernetes,
only_kubernetes && has_kubernetes,
except_kubernetes && !has_kubernetes].any?
end
end
def jobs_for_ref(ref, tag = false, source = nil)
@jobs.select do |_, job|
process?(job.dig(:only, :refs), job.dig(:except, :refs), ref, tag, source)
end
end
def jobs_for_stage_and_ref(stage, ref, tag = false, source = nil)
jobs_for_ref(ref, tag, source).select do |_, job|
job[:stage] == stage
end
end
def initial_parsing
##
# Global config
......
......@@ -7,6 +7,7 @@ module Gitlab
#
class Policy < Simplifiable
strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) }
strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) }
class RefsPolicy < Entry::Node
include Entry::Validatable
......@@ -14,6 +15,27 @@ module Gitlab
validations do
validates :config, array_of_strings_or_regexps: true
end
def value
{ refs: @config }
end
end
class ComplexPolicy < Entry::Node
include Entry::Validatable
include Entry::Attributable
attributes :refs, :kubernetes
validations do
validates :config, presence: true
validates :config, allowed_keys: %i[refs kubernetes]
with_options allow_nil: true do
validates :refs, array_of_strings_or_regexps: true
validates :kubernetes, allowed_values: %w[active]
end
end
end
class UnknownStrategy < Entry::Node
......
......@@ -14,6 +14,14 @@ module Gitlab
end
end
class AllowedValuesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless options[:in].include?(value.to_s)
record.errors.add(attribute, "unknown value: #{value}")
end
end
end
class ArrayOfStringsValidator < ActiveModel::EachValidator
include LegacyValidationHelpers
......
......@@ -18,7 +18,7 @@ module Gitlab
new(merge_request, project).tap do |file_collection|
project
.repository
.with_repo_branch_commit(merge_request.target_project.repository, merge_request.target_branch) do
.with_repo_branch_commit(merge_request.target_project.repository.raw_repository, merge_request.target_branch) do
yield file_collection
end
......
......@@ -5,6 +5,7 @@ module Gitlab
BRANCH_REF_PREFIX = "refs/heads/".freeze
CommandError = Class.new(StandardError)
CommitError = Class.new(StandardError)
class << self
include Gitlab::EncodingHelper
......
module Gitlab
module Git
class OperationService
attr_reader :committer, :repository
def initialize(committer, new_repository)
committer = Gitlab::Git::Committer.from_user(committer) if committer.is_a?(User)
@committer = committer
# Refactoring aid
unless new_repository.is_a?(Gitlab::Git::Repository)
raise "expected a Gitlab::Git::Repository, got #{new_repository}"
end
@repository = new_repository
end
def add_branch(branch_name, newrev)
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
oldrev = Gitlab::Git::BLANK_SHA
update_ref_in_hooks(ref, newrev, oldrev)
end
def rm_branch(branch)
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name
oldrev = branch.target
newrev = Gitlab::Git::BLANK_SHA
update_ref_in_hooks(ref, newrev, oldrev)
end
def add_tag(tag_name, newrev, options = {})
ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
oldrev = Gitlab::Git::BLANK_SHA
with_hooks(ref, newrev, oldrev) do |service|
# We want to pass the OID of the tag object to the hooks. For an
# annotated tag we don't know that OID until after the tag object
# (raw_tag) is created in the repository. That is why we have to
# update the value after creating the tag object. Only the
# "post-receive" hook will receive the correct value in this case.
raw_tag = repository.rugged.tags.create(tag_name, newrev, options)
service.newrev = raw_tag.target_id
end
end
def rm_tag(tag)
ref = Gitlab::Git::TAG_REF_PREFIX + tag.name
oldrev = tag.target
newrev = Gitlab::Git::BLANK_SHA
update_ref_in_hooks(ref, newrev, oldrev) do
repository.rugged.tags.delete(tag_name)
end
end
# Whenever `start_branch_name` is passed, if `branch_name` doesn't exist,
# it would be created from `start_branch_name`.
# If `start_project` is passed, and the branch doesn't exist,
# it would try to find the commits from it instead of current repository.
def with_branch(
branch_name,
start_branch_name: nil,
start_repository: repository,
&block)
# Refactoring aid
unless start_repository.is_a?(Gitlab::Git::Repository)
raise "expected a Gitlab::Git::Repository, got #{start_repository}"
end
start_branch_name = nil if start_repository.empty_repo?
if start_branch_name && !start_repository.branch_exists?(start_branch_name)
raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.full_path}"
end
update_branch_with_hooks(branch_name) do
repository.with_repo_branch_commit(
start_repository,
start_branch_name || branch_name,
&block)
end
end
private
# Returns [newrev, should_run_after_create, should_run_after_create_branch]
def update_branch_with_hooks(branch_name)
update_autocrlf_option
was_empty = repository.empty?
# Make commit
newrev = yield
unless newrev
raise Gitlab::Git::CommitError.new('Failed to create commit')
end
branch = repository.find_branch(branch_name)
oldrev = find_oldrev_from_branch(newrev, branch)
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
update_ref_in_hooks(ref, newrev, oldrev)
[newrev, was_empty, was_empty || Gitlab::Git.blank_ref?(oldrev)]
end
def find_oldrev_from_branch(newrev, branch)
return Gitlab::Git::BLANK_SHA unless branch
oldrev = branch.target
if oldrev == repository.rugged.merge_base(newrev, branch.target)
oldrev
else
raise Gitlab::Git::CommitError.new('Branch diverged')
end
end
def update_ref_in_hooks(ref, newrev, oldrev)
with_hooks(ref, newrev, oldrev) do
update_ref(ref, newrev, oldrev)
end
end
def with_hooks(ref, newrev, oldrev)
Gitlab::Git::HooksService.new.execute(
committer,
repository,
oldrev,
newrev,
ref) do |service|
yield(service)
end
end
# Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites.
def update_ref(ref, newrev, oldrev)
# We use 'git update-ref' because libgit2/rugged currently does not
# offer 'compare and swap' ref updates. Without compare-and-swap we can
# (and have!) accidentally reset the ref to an earlier state, clobbering
# commits. See also https://github.com/libgit2/libgit2/issues/1534.
command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
_, status = Gitlab::Popen.popen(
command,
repository.path) do |stdin|
stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
end
unless status.zero?
raise Gitlab::Git::CommitError.new(
"Could not update branch #{Gitlab::Git.branch_name(ref)}." \
" Please refresh and try again.")
end
end
def update_autocrlf_option
if repository.autocrlf != :input
repository.autocrlf = :input
end
end
end
end
end
......@@ -73,6 +73,10 @@ module Gitlab
delegate :exists?, to: :gitaly_repository_client
def ==(other)
path == other.path
end
# Default branch in the repository
def root_ref
@root_ref ||= gitaly_migrate(:root_ref) do |is_enabled|
......@@ -740,6 +744,106 @@ module Gitlab
end
end
def with_repo_branch_commit(start_repository, start_branch_name)
raise "expected Gitlab::Git::Repository, got #{start_repository}" unless start_repository.is_a?(Gitlab::Git::Repository)
return yield nil if start_repository.empty_repo?
if start_repository == self
yield commit(start_branch_name)
else
sha = start_repository.commit(start_branch_name).sha
if branch_commit = commit(sha)
yield branch_commit
else
with_repo_tmp_commit(
start_repository, start_branch_name, sha) do |tmp_commit|
yield tmp_commit
end
end
end
end
def with_repo_tmp_commit(start_repository, start_branch_name, sha)
tmp_ref = fetch_ref(
start_repository.path,
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
"refs/tmp/#{SecureRandom.hex}/head"
)
yield commit(sha)
ensure
delete_refs(tmp_ref) if tmp_ref
end
def fetch_source_branch(source_repository, source_branch, local_ref)
with_repo_branch_commit(source_repository, source_branch) do |commit|
if commit
write_ref(local_ref, commit.sha)
else
raise Rugged::ReferenceError, 'source repository is empty'
end
end
end
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
with_repo_branch_commit(source_repository, source_branch_name) do |commit|
break unless commit
Gitlab::Git::Compare.new(
self,
target_branch_name,
commit.sha,
straight: straight
)
end
end
def write_ref(ref_path, sha)
rugged.references.create(ref_path, sha, force: true)
end
def fetch_ref(source_path, source_ref, target_ref)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
message, status = run_git(args)
# Make sure ref was created, and raise Rugged::ReferenceError when not
raise Rugged::ReferenceError, message if status != 0
target_ref
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def run_git(args)
circuit_breaker.perform do
popen([Gitlab.config.git.bin_path, *args], path)
end
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def commit(ref = 'HEAD')
Gitlab::Git::Commit.find(self, ref)
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def empty_repo?
!exists? || !has_visible_content?
end
#
# Git repository can contains some hidden refs like:
# /refs/notes/*
# /refs/git-as-svn/*
# /refs/pulls/*
# This refs by default not visible in project page and not cloned to client side.
#
# This method return true if repository contains some content visible in project page.
#
def has_visible_content?
branch_count > 0
end
def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path)
end
......
require 'simple_po_parser'
module Gitlab
module I18n
class PoLinter
......
module SystemCheck
module App
class GitUserDefaultSSHConfigCheck < SystemCheck::BaseCheck
# These files are allowed in the .ssh directory. The `config` file is not
# whitelisted as it may change the SSH client's behaviour dramatically.
WHITELIST = %w[
authorized_keys
authorized_keys2
known_hosts
].freeze
set_name 'Git user has default SSH configuration?'
set_skip_reason 'skipped (git user is not present or configured)'
def skip?
!home_dir || !File.directory?(home_dir)
end
def check?
forbidden_files.empty?
end
def show_error
backup_dir = "~/gitlab-check-backup-#{Time.now.to_i}"
instructions = forbidden_files.map do |filename|
"sudo mv #{Shellwords.escape(filename)} #{backup_dir}"
end
try_fixing_it("mkdir #{backup_dir}", *instructions)
for_more_information('doc/ssh/README.md in section "SSH on the GitLab server"')
fix_and_rerun
end
private
def git_user
Gitlab.config.gitlab.user
end
def home_dir
return @home_dir if defined?(@home_dir)
@home_dir =
begin
File.expand_path("~#{git_user}")
rescue ArgumentError
nil
end
end
def ssh_dir
return nil unless home_dir
File.join(home_dir, '.ssh')
end
def forbidden_files
@forbidden_files ||=
begin
present = Dir[File.join(ssh_dir, '*')]
whitelisted = WHITELIST.map { |basename| File.join(ssh_dir, basename) }
present - whitelisted
end
end
end
end
end
require "gettext_i18n_rails/tasks"
require 'simple_po_parser'
namespace :gettext do
# Customize list of translatable files
......
......@@ -33,6 +33,7 @@ namespace :gitlab do
SystemCheck::App::RedisVersionCheck,
SystemCheck::App::RubyVersionCheck,
SystemCheck::App::GitVersionCheck,
SystemCheck::App::GitUserDefaultSSHConfigCheck,
SystemCheck::App::ActiveUsersCheck
]
......
......@@ -18,23 +18,25 @@ feature 'Import/Export - project import integration test', js: true do
context 'when selecting the namespace' do
let(:user) { create(:admin) }
let!(:namespace) { create(:namespace, name: "asd", owner: user) }
let!(:namespace) { create(:namespace, name: 'asd', owner: user) }
let(:project_path) { 'test-project-path' + SecureRandom.hex }
context 'prefilled the path' do
scenario 'user imports an exported project successfully' do
visit new_project_path
select2(namespace.id, from: '#project_namespace_id')
fill_in :project_path, with: 'test-project-path', visible: true
fill_in :project_path, with: project_path, visible: true
click_link 'GitLab export'
expect(page).to have_content('Import an exported GitLab project')
expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=test-project-path")
expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\z/).and_call_original
expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}")
expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\h*\z/).and_call_original
attach_file('file', file)
click_on 'Import project'
expect { click_on 'Import project' }.to change { Project.count }.by(1)
expect(Project.count).to eq(1)
project = Project.last
expect(project).not_to be_nil
......@@ -64,7 +66,7 @@ feature 'Import/Export - project import integration test', js: true do
end
scenario 'invalid project' do
namespace = create(:namespace, name: "asd", owner: user)
namespace = create(:namespace, name: 'asdf', owner: user)
project = create(:project, namespace: namespace)
visit new_project_path
......
......@@ -58,16 +58,6 @@ describe IssuesHelper do
end
end
describe "merge_requests_sentence" do
subject { merge_requests_sentence(merge_requests)}
let(:merge_requests) do
[build(:merge_request, iid: 1), build(:merge_request, iid: 2),
build(:merge_request, iid: 3)]
end
it { is_expected.to eq("!1, !2, or !3") }
end
describe '#award_user_list' do
it "returns a comma-separated list of the first X users" do
user = build_stubbed(:user, name: 'Joe')
......
import Cookies from 'js-cookie';
import {
getCookieName,
getSelector,
showPopover,
hidePopover,
dismiss,
mouseleave,
mouseenter,
setupDismissButton,
} from '~/feature_highlight/feature_highlight_helper';
describe('feature highlight helper', () => {
describe('getCookieName', () => {
it('returns `feature-highlighted-` prefix', () => {
const cookieId = 'cookieId';
expect(getCookieName(cookieId)).toEqual(`feature-highlighted-${cookieId}`);
});
});
describe('getSelector', () => {
it('returns js-feature-highlight selector', () => {
const highlightId = 'highlightId';
expect(getSelector(highlightId)).toEqual(`.js-feature-highlight[data-highlight=${highlightId}]`);
});
});
describe('showPopover', () => {
it('returns true when popover is shown', () => {
const context = {
hasClass: () => false,
popover: () => {},
addClass: () => {},
};
expect(showPopover.call(context)).toEqual(true);
});
it('returns false when popover is already shown', () => {
const context = {
hasClass: () => true,
};
expect(showPopover.call(context)).toEqual(false);
});
it('shows popover', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
addClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('show');
done();
});
showPopover.call(context);
});
it('adds disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
addClass: () => {},
};
spyOn(context, 'addClass').and.callFake((classNames) => {
expect(classNames).toEqual('disable-animation js-popover-show');
done();
});
showPopover.call(context);
});
});
describe('hidePopover', () => {
it('returns true when popover is hidden', () => {
const context = {
hasClass: () => true,
popover: () => {},
removeClass: () => {},
};
expect(hidePopover.call(context)).toEqual(true);
});
it('returns false when popover is already hidden', () => {
const context = {
hasClass: () => false,
};
expect(hidePopover.call(context)).toEqual(false);
});
it('hides popover', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
removeClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('hide');
done();
});
hidePopover.call(context);
});
it('removes disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
removeClass: () => {},
};
spyOn(context, 'removeClass').and.callFake((classNames) => {
expect(classNames).toEqual('disable-animation js-popover-show');
done();
});
hidePopover.call(context);
});
});
describe('dismiss', () => {
const context = {
hide: () => {},
};
beforeEach(() => {
spyOn(Cookies, 'set').and.callFake(() => {});
spyOn(hidePopover, 'call').and.callFake(() => {});
spyOn(context, 'hide').and.callFake(() => {});
dismiss.call(context);
});
it('sets cookie to true', () => {
expect(Cookies.set).toHaveBeenCalled();
});
it('calls hide popover', () => {
expect(hidePopover.call).toHaveBeenCalled();
});
it('calls hide', () => {
expect(context.hide).toHaveBeenCalled();
});
});
describe('mouseleave', () => {
it('calls hide popover if .popover:hover is false', () => {
const fakeJquery = {
length: 0,
};
spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
spyOn(hidePopover, 'call');
mouseleave();
expect(hidePopover.call).toHaveBeenCalled();
});
it('does not call hide popover if .popover:hover is true', () => {
const fakeJquery = {
length: 1,
};
spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
spyOn(hidePopover, 'call');
mouseleave();
expect(hidePopover.call).not.toHaveBeenCalled();
});
});
describe('mouseenter', () => {
const context = {};
it('shows popover', () => {
spyOn(showPopover, 'call').and.returnValue(false);
mouseenter.call(context);
expect(showPopover.call).toHaveBeenCalled();
});
it('registers mouseleave event if popover is showed', (done) => {
spyOn(showPopover, 'call').and.returnValue(true);
spyOn($.fn, 'on').and.callFake((eventName) => {
expect(eventName).toEqual('mouseleave');
done();
});
mouseenter.call(context);
});
it('does not register mouseleave event if popover is not showed', () => {
spyOn(showPopover, 'call').and.returnValue(false);
const spy = spyOn($.fn, 'on').and.callFake(() => {});
mouseenter.call(context);
expect(spy).not.toHaveBeenCalled();
});
});
describe('setupDismissButton', () => {
it('registers click event callback', (done) => {
const context = {
getAttribute: () => 'popoverId',
dataset: {
highlight: 'cookieId',
},
};
spyOn($.fn, 'on').and.callFake((event) => {
expect(event).toEqual('click');
done();
});
setupDismissButton.call(context);
});
});
});
import domContentLoaded from '~/feature_highlight/feature_highlight_options';
import bp from '~/breakpoints';
describe('feature highlight options', () => {
describe('domContentLoaded', () => {
const highlightOrder = [];
beforeEach(() => {
// Check for when highlightFeatures is called
spyOn(highlightOrder, 'find').and.callFake(() => {});
});
it('should not call highlightFeatures when breakpoint is xs', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('xs');
domContentLoaded(highlightOrder);
expect(bp.getBreakpointSize).toHaveBeenCalled();
expect(highlightOrder.find).not.toHaveBeenCalled();
});
it('should not call highlightFeatures when breakpoint is sm', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
domContentLoaded(highlightOrder);
expect(bp.getBreakpointSize).toHaveBeenCalled();
expect(highlightOrder.find).not.toHaveBeenCalled();
});
it('should not call highlightFeatures when breakpoint is md', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('md');
domContentLoaded(highlightOrder);
expect(bp.getBreakpointSize).toHaveBeenCalled();
expect(highlightOrder.find).not.toHaveBeenCalled();
});
it('should call highlightFeatures when breakpoint is lg', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('lg');
domContentLoaded(highlightOrder);
expect(bp.getBreakpointSize).toHaveBeenCalled();
expect(highlightOrder.find).toHaveBeenCalled();
});
});
});
import Cookies from 'js-cookie';
import * as featureHighlightHelper from '~/feature_highlight/feature_highlight_helper';
import * as featureHighlight from '~/feature_highlight/feature_highlight';
describe('feature highlight', () => {
describe('setupFeatureHighlightPopover', () => {
const selector = '.js-feature-highlight[data-highlight=test]';
beforeEach(() => {
setFixtures(`
<div>
<div class="js-feature-highlight" data-highlight="test" disabled>
Trigger
</div>
</div>
<div class="feature-highlight-popover-content">
Content
<div class="dismiss-feature-highlight">
Dismiss
</div>
</div>
`);
spyOn(window, 'addEventListener');
spyOn(window, 'removeEventListener');
featureHighlight.setupFeatureHighlightPopover('test', 0);
});
it('setups popover content', () => {
const $popoverContent = $('.feature-highlight-popover-content');
const outerHTML = $popoverContent.prop('outerHTML');
expect($(selector).data('content')).toEqual(outerHTML);
});
it('setups mouseenter', () => {
const showSpy = spyOn(featureHighlightHelper.showPopover, 'call');
$(selector).trigger('mouseenter');
expect(showSpy).toHaveBeenCalled();
});
it('setups debounced mouseleave', (done) => {
const hideSpy = spyOn(featureHighlightHelper.hidePopover, 'call');
$(selector).trigger('mouseleave');
// Even though we've set the debounce to 0ms, setTimeout is needed for the debounce
setTimeout(() => {
expect(hideSpy).toHaveBeenCalled();
done();
}, 0);
});
it('setups inserted.bs.popover', () => {
$(selector).trigger('mouseenter');
const popoverId = $(selector).attr('aria-describedby');
const spyEvent = spyOnEvent(`#${popoverId} .dismiss-feature-highlight`, 'click');
$(`#${popoverId} .dismiss-feature-highlight`).click();
expect(spyEvent).toHaveBeenTriggered();
});
it('setups show.bs.popover', () => {
$(selector).trigger('show.bs.popover');
expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
});
it('setups hide.bs.popover', () => {
$(selector).trigger('hide.bs.popover');
expect(window.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
});
it('removes disabled attribute', () => {
expect($('.js-feature-highlight').is(':disabled')).toEqual(false);
});
it('displays popover', () => {
expect($(selector).attr('aria-describedby')).toBeFalsy();
$(selector).trigger('mouseenter');
expect($(selector).attr('aria-describedby')).toBeTruthy();
});
});
describe('shouldHighlightFeature', () => {
it('should return false if element is not found', () => {
spyOn(document, 'querySelector').and.returnValue(null);
spyOn(Cookies, 'get').and.returnValue(null);
expect(featureHighlight.shouldHighlightFeature()).toBeFalsy();
});
it('should return false if previouslyDismissed', () => {
spyOn(document, 'querySelector').and.returnValue(document.createElement('div'));
spyOn(Cookies, 'get').and.returnValue('true');
expect(featureHighlight.shouldHighlightFeature()).toBeFalsy();
});
it('should return true if element is found and not previouslyDismissed', () => {
spyOn(document, 'querySelector').and.returnValue(document.createElement('div'));
spyOn(Cookies, 'get').and.returnValue(null);
expect(featureHighlight.shouldHighlightFeature()).toBeTruthy();
});
});
describe('highlightFeatures', () => {
it('calls setupFeatureHighlightPopover if shouldHighlightFeature returns true', () => {
// Mimic shouldHighlightFeature set to true
const highlightOrder = ['issue-boards'];
spyOn(highlightOrder, 'find').and.returnValue(highlightOrder[0]);
expect(featureHighlight.highlightFeatures(highlightOrder)).toEqual(true);
});
it('does not call setupFeatureHighlightPopover if shouldHighlightFeature returns false', () => {
// Mimic shouldHighlightFeature set to false
const highlightOrder = ['issue-boards'];
spyOn(highlightOrder, 'find').and.returnValue(null);
expect(featureHighlight.highlightFeatures(highlightOrder)).toEqual(false);
});
});
});
......@@ -164,9 +164,46 @@ module Ci
expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
end
end
context 'when kubernetes policy is specified' do
let(:pipeline) { create(:ci_empty_pipeline) }
let(:config) do
YAML.dump(
spinach: { stage: 'test', script: 'spinach' },
production: {
stage: 'deploy',
script: 'cap',
only: { kubernetes: 'active' }
}
)
end
context 'when kubernetes is active' do
let(:project) { create(:kubernetes_project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
it 'returns seeds for kubernetes dependent job' do
seeds = subject.stage_seeds(pipeline)
expect(seeds.size).to eq 2
expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
expect(seeds.second.builds.dig(0, :name)).to eq 'production'
end
end
context 'when kubernetes is not active' do
it 'does not return seeds for kubernetes dependent job' do
seeds = subject.stage_seeds(pipeline)
expect(seeds.size).to eq 1
expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
end
end
end
end
describe "#builds_for_ref" do
describe "#builds_for_stage_and_ref" do
let(:type) { 'test' }
it "returns builds if no branch specified" do
......
......@@ -16,8 +16,8 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
describe '#value' do
it 'returns key value' do
expect(entry.value).to eq config
it 'returns refs hash' do
expect(entry.value).to eq(refs: config)
end
end
end
......@@ -56,6 +56,50 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
end
context 'when using complex policy' do
context 'when specifiying refs policy' do
let(:config) { { refs: ['master'] } }
it 'is a correct configuraton' do
expect(entry).to be_valid
expect(entry.value).to eq(refs: %w[master])
end
end
context 'when specifying kubernetes policy' do
let(:config) { { kubernetes: 'active' } }
it 'is a correct configuraton' do
expect(entry).to be_valid
expect(entry.value).to eq(kubernetes: 'active')
end
end
context 'when specifying invalid kubernetes policy' do
let(:config) { { kubernetes: 'something' } }
it 'reports an error about invalid policy' do
expect(entry.errors).to include /unknown value: something/
end
end
context 'when specifying unknown policy' do
let(:config) { { refs: ['master'], invalid: :something } }
it 'returns error about invalid key' do
expect(entry.errors).to include /unknown keys: invalid/
end
end
context 'when policy is empty' do
let(:config) { {} }
it 'is not a valid configuration' do
expect(entry.errors).to include /can't be blank/
end
end
end
context 'when policy strategy does not match' do
let(:config) { 'string strategy' }
......
require 'spec_helper'
require 'simple_po_parser'
describe Gitlab::I18n::PoLinter do
let(:linter) { described_class.new(po_path) }
......
require 'spec_helper'
describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
let(:username) { '_this_user_will_not_exist_unless_it_is_stubbed' }
let(:base_dir) { Dir.mktmpdir }
let(:home_dir) { File.join(base_dir, "/var/lib/#{username}") }
let(:ssh_dir) { File.join(home_dir, '.ssh') }
let(:forbidden_file) { 'id_rsa' }
before do
allow(Gitlab.config.gitlab).to receive(:user).and_return(username)
end
after do
FileUtils.rm_rf(base_dir)
end
it 'only whitelists safe files' do
expect(described_class::WHITELIST).to contain_exactly('authorized_keys', 'authorized_keys2', 'known_hosts')
end
describe '#skip?' do
subject { described_class.new.skip? }
where(user_exists: [true, false], home_dir_exists: [true, false])
with_them do
let(:expected_result) { !user_exists || !home_dir_exists }
before do
stub_user if user_exists
stub_home_dir if home_dir_exists
end
it { is_expected.to eq(expected_result) }
end
end
describe '#check?' do
subject { described_class.new.check? }
before do
stub_user
end
it 'fails if a forbidden file exists' do
stub_ssh_file(forbidden_file)
is_expected.to be_falsy
end
it "succeeds if the SSH directory doesn't exist" do
FileUtils.rm_rf(ssh_dir)
is_expected.to be_truthy
end
it 'succeeds if all the whitelisted files exist' do
described_class::WHITELIST.each do |filename|
stub_ssh_file(filename)
end
is_expected.to be_truthy
end
end
def stub_user
allow(File).to receive(:expand_path).with("~#{username}").and_return(home_dir)
end
def stub_home_dir
FileUtils.mkdir_p(home_dir)
end
def stub_ssh_file(filename)
FileUtils.mkdir_p(ssh_dir)
FileUtils.touch(File.join(ssh_dir, filename))
end
end
......@@ -546,6 +546,22 @@ describe Ci::Pipeline, :mailer do
end
end
describe '#has_kubernetes_active?' do
context 'when kubernetes is active' do
let(:project) { create(:kubernetes_project) }
it 'returns true' do
expect(pipeline).to have_kubernetes_active
end
end
context 'when kubernetes is not active' do
it 'returns false' do
expect(pipeline).not_to have_kubernetes_active
end
end
end
describe '#has_stage_seeds?' do
context 'when pipeline has stage seeds' do
subject { build(:ci_pipeline_with_one_job) }
......
......@@ -203,18 +203,13 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
describe '#predefined_variables' do
let(:kubeconfig) do
config =
YAML.load(File.read(expand_fixture_path('config/kubeconfig.yml')))
config.dig('users', 0, 'user')['token'] =
'token'
config_file = expand_fixture_path('config/kubeconfig.yml')
config = YAML.load(File.read(config_file))
config.dig('users', 0, 'user')['token'] = 'token'
config.dig('contexts', 0, 'context')['namespace'] = namespace
config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
Base64.encode64('CA PEM DATA')
config.dig('contexts', 0, 'context')['namespace'] =
namespace
YAML.dump(config)
end
......
......@@ -886,7 +886,7 @@ describe Repository, models: true do
context 'when pre hooks were successful' do
it 'runs without errors' do
expect_any_instance_of(Gitlab::Git::HooksService).to receive(:execute)
.with(committer, repository, old_rev, blank_sha, 'refs/heads/feature')
.with(committer, repository.raw_repository, old_rev, blank_sha, 'refs/heads/feature')
expect { repository.rm_branch(user, 'feature') }.not_to raise_error
end
......@@ -932,20 +932,20 @@ describe Repository, models: true do
service = Gitlab::Git::HooksService.new
expect(Gitlab::Git::HooksService).to receive(:new).and_return(service)
expect(service).to receive(:execute)
.with(committer, target_repository, old_rev, new_rev, updating_ref)
.with(committer, target_repository.raw_repository, old_rev, new_rev, updating_ref)
.and_yield(service).and_return(true)
end
it 'runs without errors' do
expect do
GitOperationService.new(committer, repository).with_branch('feature') do
Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch('feature') do
new_rev
end
end.not_to raise_error
end
it 'ensures the autocrlf Git option is set to :input' do
service = GitOperationService.new(committer, repository)
service = Gitlab::Git::OperationService.new(committer, repository.raw_repository)
expect(service).to receive(:update_autocrlf_option)
......@@ -956,7 +956,7 @@ describe Repository, models: true do
it 'updates the head' do
expect(repository.find_branch('feature').dereferenced_target.id).to eq(old_rev)
GitOperationService.new(committer, repository).with_branch('feature') do
Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch('feature') do
new_rev
end
......@@ -971,13 +971,13 @@ describe Repository, models: true do
let(:updating_ref) { 'refs/heads/master' }
it 'fetch_ref and create the branch' do
expect(target_project.repository).to receive(:fetch_ref)
expect(target_project.repository.raw_repository).to receive(:fetch_ref)
.and_call_original
GitOperationService.new(committer, target_repository)
Gitlab::Git::OperationService.new(committer, target_repository.raw_repository)
.with_branch(
'master',
start_project: project,
start_repository: project.repository.raw_repository,
start_branch_name: 'feature') { new_rev }
expect(target_repository.branch_names).to contain_exactly('master')
......@@ -990,8 +990,8 @@ describe Repository, models: true do
it 'does not fetch_ref and just pass the commit' do
expect(target_repository).not_to receive(:fetch_ref)
GitOperationService.new(committer, target_repository)
.with_branch('feature', start_project: project) { new_rev }
Gitlab::Git::OperationService.new(committer, target_repository.raw_repository)
.with_branch('feature', start_repository: project.repository.raw_repository) { new_rev }
end
end
end
......@@ -1000,7 +1000,7 @@ describe Repository, models: true do
let(:target_project) { create(:project, :empty_repo) }
before do
expect(target_project.repository).to receive(:run_git)
expect(target_project.repository.raw_repository).to receive(:run_git)
end
it 'raises Rugged::ReferenceError' do
......@@ -1009,9 +1009,9 @@ describe Repository, models: true do
end
expect do
GitOperationService.new(committer, target_project.repository)
Gitlab::Git::OperationService.new(committer, target_project.repository.raw_repository)
.with_branch('feature',
start_project: project,
start_repository: project.repository.raw_repository,
&:itself)
end.to raise_reference_error
end
......@@ -1031,7 +1031,7 @@ describe Repository, models: true do
repository.add_branch(user, branch, old_rev)
expect do
GitOperationService.new(committer, repository).with_branch(branch) do
Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch(branch) do
new_rev
end
end.not_to raise_error
......@@ -1049,10 +1049,10 @@ describe Repository, models: true do
# Updating 'master' to new_rev would lose the commits on 'master' that
# are not contained in new_rev. This should not be allowed.
expect do
GitOperationService.new(committer, repository).with_branch(branch) do
Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch(branch) do
new_rev
end
end.to raise_error(Repository::CommitError)
end.to raise_error(Gitlab::Git::CommitError)
end
end
......@@ -1061,7 +1061,7 @@ describe Repository, models: true do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
GitOperationService.new(committer, repository).with_branch('feature') do
Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch('feature') do
new_rev
end
end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
......@@ -1079,8 +1079,7 @@ describe Repository, models: true do
expect(repository).not_to receive(:expire_emptiness_caches)
expect(repository).to receive(:expire_branches_cache)
GitOperationService.new(committer, repository)
.with_branch('new-feature') do
repository.with_branch(user, 'new-feature') do
new_rev
end
end
......@@ -1139,7 +1138,7 @@ describe Repository, models: true do
describe 'when there are no branches' do
before do
allow(repository).to receive(:branch_count).and_return(0)
allow(repository.raw_repository).to receive(:branch_count).and_return(0)
end
it { is_expected.to eq(false) }
......@@ -1147,7 +1146,7 @@ describe Repository, models: true do
describe 'when there are branches' do
it 'returns true' do
expect(repository).to receive(:branch_count).and_return(3)
expect(repository.raw_repository).to receive(:branch_count).and_return(3)
expect(subject).to eq(true)
end
......@@ -1161,7 +1160,7 @@ describe Repository, models: true do
end
it 'sets autocrlf to :input' do
GitOperationService.new(nil, repository).send(:update_autocrlf_option)
Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
expect(repository.raw_repository.autocrlf).to eq(:input)
end
......@@ -1176,7 +1175,7 @@ describe Repository, models: true do
expect(repository.raw_repository).not_to receive(:autocrlf=)
.with(:input)
GitOperationService.new(nil, repository).send(:update_autocrlf_option)
Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
end
end
end
......@@ -1762,15 +1761,15 @@ describe Repository, models: true do
describe '#update_ref' do
it 'can create a ref' do
GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
expect(repository.find_branch('foobar')).not_to be_nil
end
it 'raises CommitError when the ref update fails' do
expect do
GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
end.to raise_error(Repository::CommitError)
Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
end.to raise_error(Gitlab::Git::CommitError)
end
end
......
......@@ -224,7 +224,7 @@ describe API::Files do
it "returns a 400 if editor fails to create file" do
allow_any_instance_of(Repository).to receive(:create_file)
.and_raise(Repository::CommitError, 'Cannot create file')
.and_raise(Gitlab::Git::CommitError, 'Cannot create file')
post api(route("any%2Etxt"), user), valid_params
......@@ -339,7 +339,7 @@ describe API::Files do
end
it "returns a 400 if fails to delete file" do
allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Repository::CommitError, 'Cannot delete file')
allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file')
delete api(route(file_path), user), valid_params
......
......@@ -127,7 +127,7 @@ describe API::V3::Files do
it "returns a 400 if editor fails to create file" do
allow_any_instance_of(Repository).to receive(:create_file)
.and_raise(Repository::CommitError, 'Cannot create file')
.and_raise(Gitlab::Git::CommitError, 'Cannot create file')
post v3_api("/projects/#{project.id}/repository/files", user), valid_params
......@@ -228,7 +228,7 @@ describe API::V3::Files do
end
it "returns a 400 if fails to delete file" do
allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Repository::CommitError, 'Cannot delete file')
allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file')
delete v3_api("/projects/#{project.id}/repository/files", user), valid_params
......
......@@ -73,8 +73,8 @@ describe 'ci/lints/show' do
render
expect(rendered).to have_content('Tag list: dotnet')
expect(rendered).to have_content('Refs only: test@dude/repo')
expect(rendered).to have_content('Refs except: deploy')
expect(rendered).to have_content('Only policy: refs, test@dude/repo')
expect(rendered).to have_content('Except policy: refs, deploy')
expect(rendered).to have_content('Environment: testing')
expect(rendered).to have_content('When: on_success')
end
......
......@@ -23,8 +23,8 @@ describe GitGarbageCollectWorker do
expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
expect_any_instance_of(Repository).to receive(:branch_count).and_call_original
expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original
expect_any_instance_of(Gitlab::Git::Repository).to receive(:branch_count).and_call_original
expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original
subject.perform(project.id)
end
......@@ -143,7 +143,7 @@ describe GitGarbageCollectWorker do
tree: old_commit.tree,
parents: [old_commit]
)
GitOperationService.new(nil, project.repository).send(
Gitlab::Git::OperationService.new(nil, project.repository.raw_repository).send(
:update_ref,
"refs/heads/#{SecureRandom.hex(6)}",
new_commit_sha,
......
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