Commit 442a268f authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 'add-svg-loader'

# Conflicts:
#   app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
parents 8c9b1379 7d15f36b
...@@ -6,8 +6,6 @@ ...@@ -6,8 +6,6 @@
/* global AwardsHandler */ /* global AwardsHandler */
/* global Aside */ /* global Aside */
function requireAll(context) { return context.keys().map(context); }
window.$ = window.jQuery = require('jquery'); window.$ = window.jQuery = require('jquery');
require('jquery-ui/ui/draggable'); require('jquery-ui/ui/draggable');
require('jquery-ui/ui/sortable'); require('jquery-ui/ui/sortable');
...@@ -44,15 +42,176 @@ require('./shortcuts_dashboard_navigation'); ...@@ -44,15 +42,176 @@ require('./shortcuts_dashboard_navigation');
require('./shortcuts_issuable'); require('./shortcuts_issuable');
require('./shortcuts_network'); require('./shortcuts_network');
require('vendor/jquery.nicescroll'); require('vendor/jquery.nicescroll');
requireAll(require.context('./behaviors', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./blob', false, /^\.\/.*\.(js|es6)$/)); // behaviors
requireAll(require.context('./templates', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/autosize');
requireAll(require.context('./commit', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/details_behavior');
requireAll(require.context('./extensions', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/quick_submit');
requireAll(require.context('./lib/utils', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/requires_input');
requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/toggler_behavior');
requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/)); // blob
require('./blob/blob_ci_yaml');
require('./blob/blob_dockerfile_selector');
require('./blob/blob_dockerfile_selectors');
require('./blob/blob_file_dropzone');
require('./blob/blob_gitignore_selector');
require('./blob/blob_gitignore_selectors');
require('./blob/blob_license_selector');
require('./blob/blob_license_selectors');
require('./blob/template_selector');
// templates
require('./templates/issuable_template_selector');
require('./templates/issuable_template_selectors');
// commit
require('./commit/file.js');
require('./commit/image_file.js');
// extensions
require('./extensions/array');
require('./extensions/custom_event');
require('./extensions/element');
require('./extensions/jquery');
require('./extensions/object');
// lib/utils
require('./lib/utils/animate');
require('./lib/utils/bootstrap_linked_tabs');
require('./lib/utils/common_utils');
require('./lib/utils/datetime_utility');
require('./lib/utils/notify');
require('./lib/utils/pretty_time');
require('./lib/utils/text_utility');
require('./lib/utils/type_utility');
require('./lib/utils/url_utility');
// u2f
require('./u2f/authenticate');
require('./u2f/error');
require('./u2f/register');
require('./u2f/util');
// droplab
require('./droplab/droplab');
require('./droplab/droplab_ajax');
require('./droplab/droplab_ajax_filter');
require('./droplab/droplab_filter');
// everything else
require('./abuse_reports');
require('./activities');
require('./admin');
require('./ajax_loading_spinner');
require('./api');
require('./aside');
require('./autosave');
require('./awards_handler');
require('./breakpoints');
require('./broadcast_message');
require('./build');
require('./build_artifacts');
require('./build_variables');
require('./ci_lint_editor');
require('./commit');
require('./commits');
require('./compare');
require('./compare_autocomplete');
require('./confirm_danger_modal');
require('./copy_as_gfm');
require('./copy_to_clipboard');
require('./create_label');
require('./diff');
require('./dispatcher');
require('./dropzone_input');
require('./due_date_select');
require('./files_comment_button');
require('./flash');
require('./gfm_auto_complete');
require('./gl_dropdown');
require('./gl_field_error');
require('./gl_field_errors');
require('./gl_form');
require('./group_avatar');
require('./group_label_subscription');
require('./groups_select');
require('./header');
require('./importer_status');
require('./issuable');
require('./issuable_context');
require('./issuable_form');
require('./issue');
require('./issue_status_select');
require('./issues_bulk_assignment');
require('./label_manager');
require('./labels');
require('./labels_select');
require('./layout_nav');
require('./line_highlighter');
require('./logo');
require('./member_expiration_date');
require('./members');
require('./merge_request');
require('./merge_request_tabs');
require('./merge_request_widget');
require('./merged_buttons');
require('./milestone');
require('./milestone_select');
require('./mini_pipeline_graph_dropdown');
require('./namespace_select');
require('./new_branch_form');
require('./new_commit_form');
require('./notes');
require('./notifications_dropdown');
require('./notifications_form');
require('./pager');
require('./pipelines');
require('./preview_markdown');
require('./project');
require('./project_avatar');
require('./project_find_file');
require('./project_fork');
require('./project_import');
require('./project_label_subscription');
require('./project_new');
require('./project_select');
require('./project_show');
require('./project_variables');
require('./projects_list');
require('./render_gfm');
require('./render_math');
require('./right_sidebar');
require('./search');
require('./search_autocomplete');
require('./shortcuts');
require('./shortcuts_blob');
require('./shortcuts_dashboard_navigation');
require('./shortcuts_find_file');
require('./shortcuts_issuable');
require('./shortcuts_navigation');
require('./shortcuts_network');
require('./signin_tabs_memoizer');
require('./single_file_diff');
require('./smart_interval');
require('./snippets_list');
require('./star');
require('./subbable_resource');
require('./subscription');
require('./subscription_select');
require('./syntax_highlight');
require('./task_list');
require('./todos');
require('./tree');
require('./user');
require('./user_tabs');
require('./username_validator');
require('./users_select');
require('./version_check_image');
require('./visibility_select');
require('./wikis');
require('./zen_mode');
require('vendor/fuzzaldrin-plus'); require('vendor/fuzzaldrin-plus');
require('es6-promise').polyfill(); require('es6-promise').polyfill();
......
...@@ -123,14 +123,18 @@ class List { ...@@ -123,14 +123,18 @@ class List {
if (listFrom) { if (listFrom) {
this.issuesSize += 1; this.issuesSize += 1;
gl.boardService.moveIssue(issue.id, listFrom.id, this.id) this.updateIssueLabel(issue, listFrom);
.then(() => {
listFrom.getIssues(false);
});
} }
} }
} }
updateIssueLabel(issue, listFrom) {
gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
.then(() => {
listFrom.getIssues(false);
});
}
findIssue (id) { findIssue (id) {
return this.issues.filter(issue => issue.id === id)[0]; return this.issues.filter(issue => issue.id === id)[0];
} }
......
...@@ -92,9 +92,12 @@ ...@@ -92,9 +92,12 @@
const issueLists = issue.getLists(); const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label); const listLabels = issueLists.map(listIssue => listIssue.label);
// Add to new lists issues if it doesn't already exist
if (!issueTo) { if (!issueTo) {
// Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex); listTo.addIssue(issue, listFrom, newIndex);
} else {
listTo.updateIssueLabel(issue, listFrom);
issueTo.removeLabel(listFrom.label);
} }
if (listTo.type === 'done') { if (listTo.type === 'done') {
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
/* global Shortcuts */ /* global Shortcuts */
const ShortcutsBlob = require('./shortcuts_blob'); const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -277,6 +278,9 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -277,6 +278,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'ci:lints:show': case 'ci:lints:show':
new gl.CILintEditor(); new gl.CILintEditor();
break; break;
case 'users:show':
new UserCallout();
break;
} }
switch (path.first()) { switch (path.first()) {
case 'sessions': case 'sessions':
...@@ -313,6 +317,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -313,6 +317,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'dashboard': case 'dashboard':
case 'root': case 'root':
shortcut_handler = new ShortcutsDashboardNavigation(); shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout();
break; break;
case 'profiles': case 'profiles':
new NotificationsForm(); new NotificationsForm();
......
// require everything else in this directory require('./gl_crop');
function requireAll(context) { return context.keys().map(context); } require('./profile');
requireAll(require.context('.', false, /^\.\/(?!profile_bundle).*\.(js|es6)$/));
// require everything else in this directory require('./protected_branch_access_dropdown');
function requireAll(context) { return context.keys().map(context); } require('./protected_branch_create');
requireAll(require.context('.', false, /^\.\/(?!protected_branches_bundle).*\.(js|es6)$/)); require('./protected_branch_dropdown');
require('./protected_branch_edit');
require('./protected_branch_edit_list');
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */
/* global ace */ /* global ace */
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!snippet_bundle).*\.(js|es6)$/));
(function() { (function() {
$(function() { $(function() {
var editor = ace.edit("editor"); var editor = ace.edit("editor");
......
/* global Cookies */
const userCalloutElementName = '.user-callout';
const closeButton = '.close-user-callout';
const userCalloutBtn = '.user-callout-btn';
const userCalloutSvgAttrName = 'callout-svg';
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
const USER_CALLOUT_TEMPLATE = `
<div class="bordered-box landing content-block">
<button class="btn btn-default close close-user-callout" type="button">
<i class="fa fa-times dismiss-icon"></i>
</button>
<div class="row">
<div class="col-sm-3 col-xs-12 svg-container">
</div>
<div class="col-sm-8 col-xs-12 inner-content">
<h4>
Customize your experience
</h4>
<p>
Change syntax themes, default project pages, and more in preferences.
</p>
<a class="btn user-callout-btn" href="/profile/preferences">Check it out</a>
</div>
</div>
</div>`;
class UserCallout {
constructor() {
this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE);
this.userCalloutBody = $(userCalloutElementName);
this.userCalloutSvg = $(userCalloutElementName).attr(userCalloutSvgAttrName);
$(userCalloutElementName).removeAttr(userCalloutSvgAttrName);
this.init();
}
init() {
const $template = $(USER_CALLOUT_TEMPLATE);
if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') {
$template.find('.svg-container').append(this.userCalloutSvg);
this.userCalloutBody.append($template);
$template.find(closeButton).on('click', e => this.dismissCallout(e));
$template.find(userCalloutBtn).on('click', e => this.dismissCallout(e));
}
}
dismissCallout(e) {
Cookies.set(USER_CALLOUT_COOKIE, 'true');
const $currentTarget = $(e.currentTarget);
if ($currentTarget.hasClass('close-user-callout')) {
this.userCalloutBody.empty();
}
}
}
module.exports = UserCallout;
// require everything else in this directory require('./calendar');
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!users_bundle).*\.(js|es6)$/));
/* global Vue, Flash, gl */ /* global Vue, Flash, gl */
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign, no-alert */
const playIconSvg = require('../../../views/shared/icons/_icon_play.svg'); const playIconSvg = require('../../../views/shared/icons/_icon_play.svg');
((gl) => { ((gl) => {
gl.VuePipelineActions = Vue.extend({ gl.VuePipelineActions = Vue.extend({
props: ['pipeline'], props: ['pipeline'],
...@@ -17,6 +18,20 @@ const playIconSvg = require('../../../views/shared/icons/_icon_play.svg'); ...@@ -17,6 +18,20 @@ const playIconSvg = require('../../../views/shared/icons/_icon_play.svg');
download(name) { download(name) {
return `Download ${name} artifacts`; return `Download ${name} artifacts`;
}, },
/**
* Shows a dialog when the user clicks in the cancel button.
* We need to prevent the default behavior and stop propagation because the
* link relies on UJS.
*
* @param {Event} event
*/
confirmAction(event) {
if (!confirm('Are you sure you want to cancel this pipeline?')) {
event.preventDefault();
event.stopPropagation();
}
},
}, },
data() { data() {
...@@ -93,6 +108,7 @@ const playIconSvg = require('../../../views/shared/icons/_icon_play.svg'); ...@@ -93,6 +108,7 @@ const playIconSvg = require('../../../views/shared/icons/_icon_play.svg');
</a> </a>
<a <a
v-if='pipeline.flags.cancelable' v-if='pipeline.flags.cancelable'
@click="confirmAction"
class="btn btn-remove has-tooltip" class="btn btn-remove has-tooltip"
title="Cancel" title="Cancel"
rel="nofollow" rel="nofollow"
......
...@@ -277,3 +277,41 @@ table.u2f-registrations { ...@@ -277,3 +277,41 @@ table.u2f-registrations {
padding-left: 18px; padding-left: 18px;
} }
} }
.user-callout {
margin: 24px auto 0;
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.landing {
margin-bottom: $gl-padding;
.close {
margin-right: 20px;
}
.dismiss-icon {
float: right;
cursor: pointer;
color: $cycle-analytics-dismiss-icon-color;
}
.svg-container {
text-align: center;
svg {
width: 136px;
height: 136px;
}
}
}
@media(max-width: $screen-xs-max) {
.inner-content {
padding-left: 30px;
}
}
}
...@@ -746,136 +746,63 @@ class Repository ...@@ -746,136 +746,63 @@ class Repository
@tags ||= raw_repository.tags @tags ||= raw_repository.tags
end end
# rubocop:disable Metrics/ParameterLists def create_dir(user, path, **options)
def commit_dir( options[:user] = user
user, path, options[:actions] = [{ action: :create_dir, file_path: path }]
message:, branch_name:,
author_email: nil, author_name: nil,
start_branch_name: nil, start_project: project)
check_tree_entry_for_dir(branch_name, path)
if start_branch_name
start_project.repository.
check_tree_entry_for_dir(start_branch_name, path)
end
commit_file( multi_action(**options)
user,
"#{path}/.gitkeep",
'',
message: message,
branch_name: branch_name,
update: false,
author_email: author_email,
author_name: author_name,
start_branch_name: start_branch_name,
start_project: start_project)
end end
# rubocop:enable Metrics/ParameterLists
# rubocop:disable Metrics/ParameterLists def create_file(user, path, content, **options)
def commit_file( options[:user] = user
user, path, content, options[:actions] = [{ action: :create, file_path: path, content: content }]
message:, branch_name:, update: true,
author_email: nil, author_name: nil,
start_branch_name: nil, start_project: project)
unless update
error_message = "Filename already exists; update not allowed"
if tree_entry_at(branch_name, path) multi_action(**options)
raise Gitlab::Git::Repository::InvalidBlobName.new(error_message) end
end
if start_branch_name && def update_file(user, path, content, **options)
start_project.repository.tree_entry_at(start_branch_name, path) previous_path = options.delete(:previous_path)
raise Gitlab::Git::Repository::InvalidBlobName.new(error_message) action = previous_path && previous_path != path ? :move : :update
end
end
multi_action( options[:user] = user
user: user, options[:actions] = [{ action: action, file_path: path, previous_path: previous_path, content: content }]
message: message,
branch_name: branch_name,
author_email: author_email,
author_name: author_name,
start_branch_name: start_branch_name,
start_project: start_project,
actions: [{ action: :create,
file_path: path,
content: content }])
end
# rubocop:enable Metrics/ParameterLists
# rubocop:disable Metrics/ParameterLists multi_action(**options)
def update_file(
user, path, content,
message:, branch_name:, previous_path:,
author_email: nil, author_name: nil,
start_branch_name: nil, start_project: project)
action = if previous_path && previous_path != path
:move
else
:update
end
multi_action(
user: user,
message: message,
branch_name: branch_name,
author_email: author_email,
author_name: author_name,
start_branch_name: start_branch_name,
start_project: start_project,
actions: [{ action: action,
file_path: path,
content: content,
previous_path: previous_path }])
end end
# rubocop:enable Metrics/ParameterLists
# rubocop:disable Metrics/ParameterLists def delete_file(user, path, **options)
def remove_file( options[:user] = user
user, path, options[:actions] = [{ action: :delete, file_path: path }]
message:, branch_name:,
author_email: nil, author_name: nil, multi_action(**options)
start_branch_name: nil, start_project: project)
multi_action(
user: user,
message: message,
branch_name: branch_name,
author_email: author_email,
author_name: author_name,
start_branch_name: start_branch_name,
start_project: start_project,
actions: [{ action: :delete,
file_path: path }])
end end
# rubocop:enable Metrics/ParameterLists
# rubocop:disable Metrics/ParameterLists # rubocop:disable Metrics/ParameterLists
def multi_action( def multi_action(
user:, branch_name:, message:, actions:, user:, branch_name:, message:, actions:,
author_email: nil, author_name: nil, author_email: nil, author_name: nil,
start_branch_name: nil, start_project: project) start_branch_name: nil, start_project: project)
GitOperationService.new(user, self).with_branch( GitOperationService.new(user, self).with_branch(
branch_name, branch_name,
start_branch_name: start_branch_name, start_branch_name: start_branch_name,
start_project: start_project) do |start_commit| start_project: start_project) do |start_commit|
index = rugged.index
parents = if start_commit index = Gitlab::Git::Index.new(raw_repository)
index.read_tree(start_commit.raw_commit.tree)
[start_commit.sha]
else
[]
end
actions.each do |act| if start_commit
git_action(index, act) index.read_tree(start_commit.raw_commit.tree)
parents = [start_commit.sha]
else
parents = []
end
actions.each do |options|
index.public_send(options.delete(:action), options)
end end
options = { options = {
tree: index.write_tree(rugged), tree: index.write_tree,
message: message, message: message,
parents: parents parents: parents
} }
...@@ -1166,30 +1093,6 @@ class Repository ...@@ -1166,30 +1093,6 @@ class Repository
blob_data_at(sha, '.gitlab-ci.yml') blob_data_at(sha, '.gitlab-ci.yml')
end end
protected
def tree_entry_at(branch_name, path)
branch_exists?(branch_name) &&
# tree_entry is private
raw_repository.send(:tree_entry, commit(branch_name), path)
end
def check_tree_entry_for_dir(branch_name, path)
return unless branch_exists?(branch_name)
entry = tree_entry_at(branch_name, path)
return unless entry
if entry[:type] == :blob
raise Gitlab::Git::Repository::InvalidBlobName.new(
"Directory already exists as a file")
else
raise Gitlab::Git::Repository::InvalidBlobName.new(
"Directory already exists")
end
end
private private
def blob_data_at(sha, path) def blob_data_at(sha, path)
...@@ -1200,58 +1103,6 @@ class Repository ...@@ -1200,58 +1103,6 @@ class Repository
blob.data blob.data
end end
def git_action(index, action)
path = normalize_path(action[:file_path])
if action[:action] == :move
previous_path = normalize_path(action[:previous_path])
end
case action[:action]
when :create, :update, :move
mode =
case action[:action]
when :update
index.get(path)[:mode]
when :move
index.get(previous_path)[:mode]
end
mode ||= 0o100644
index.remove(previous_path) if action[:action] == :move
content = if action[:encoding] == 'base64'
Base64.decode64(action[:content])
else
action[:content]
end
detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
unless detect && detect[:type] == :binary
# When writing to the repo directly as we are doing here,
# the `core.autocrlf` config isn't taken into account.
content.gsub!("\r\n", "\n") if self.autocrlf
end
oid = rugged.write(content, :blob)
index.add(path: path, oid: oid, mode: mode)
when :delete
index.remove(path)
end
end
def normalize_path(path)
pathname = Gitlab::Git::PathHelper.normalize_path(path)
if pathname.each_filename.include?('..')
raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
end
pathname.to_s
end
def refs_directory_exists? def refs_directory_exists?
return false unless path_with_namespace return false unless path_with_namespace
......
module Files module Files
class CreateDirService < Files::BaseService class CreateDirService < Files::BaseService
def commit def commit
repository.commit_dir( repository.create_dir(
current_user, current_user,
@file_path, @file_path,
message: @commit_message, message: @commit_message,
......
module Files module Files
class CreateService < Files::BaseService class CreateService < Files::BaseService
def commit def commit
repository.commit_file( repository.create_file(
current_user, current_user,
@file_path, @file_path,
@file_content, @file_content,
message: @commit_message, message: @commit_message,
branch_name: @target_branch, branch_name: @target_branch,
update: false,
author_email: @author_email, author_email: @author_email,
author_name: @author_name, author_name: @author_name,
start_project: @start_project, start_project: @start_project,
...@@ -17,6 +16,10 @@ module Files ...@@ -17,6 +16,10 @@ module Files
def validate def validate
super super
if @file_content.nil?
raise_error("You must provide content.")
end
if @file_path =~ Gitlab::Regex.directory_traversal_regex if @file_path =~ Gitlab::Regex.directory_traversal_regex
raise_error( raise_error(
'Your changes could not be committed, because the file name ' + 'Your changes could not be committed, because the file name ' +
......
module Files module Files
class DestroyService < Files::BaseService class DestroyService < Files::BaseService
def commit def commit
repository.remove_file( repository.delete_file(
current_user, current_user,
@file_path, @file_path,
message: @commit_message, message: @commit_message,
......
...@@ -2,6 +2,8 @@ module Files ...@@ -2,6 +2,8 @@ module Files
class MultiService < Files::BaseService class MultiService < Files::BaseService
class FileChangedError < StandardError; end class FileChangedError < StandardError; end
ACTIONS = %w[create update delete move].freeze
def commit def commit
repository.multi_action( repository.multi_action(
user: current_user, user: current_user,
...@@ -21,10 +23,19 @@ module Files ...@@ -21,10 +23,19 @@ module Files
super super
params[:actions].each_with_index do |action, index| params[:actions].each_with_index do |action, index|
if ACTIONS.include?(action[:action].to_s)
action[:action] = action[:action].to_sym
else
raise_error("Unknown action type `#{action[:action]}`.")
end
unless action[:file_path].present? unless action[:file_path].present?
raise_error("You must specify a file_path.") raise_error("You must specify a file_path.")
end end
action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
regex_check(action[:file_path]) regex_check(action[:file_path])
regex_check(action[:previous_path]) if action[:previous_path] regex_check(action[:previous_path]) if action[:previous_path]
...@@ -43,8 +54,6 @@ module Files ...@@ -43,8 +54,6 @@ module Files
validate_delete(action) validate_delete(action)
when :move when :move
validate_move(action, index) validate_move(action, index)
else
raise_error("Unknown action type `#{action[:action]}`.")
end end
end end
end end
...@@ -92,6 +101,20 @@ module Files ...@@ -92,6 +101,20 @@ module Files
if repository.blob_at_branch(params[:branch], action[:file_path]) if repository.blob_at_branch(params[:branch], action[:file_path])
raise_error("Your changes could not be committed because a file with the name `#{action[:file_path]}` already exists.") raise_error("Your changes could not be committed because a file with the name `#{action[:file_path]}` already exists.")
end end
if action[:content].nil?
raise_error("You must provide content.")
end
end
def validate_update(action)
if action[:content].nil?
raise_error("You must provide content.")
end
if file_has_changed?
raise FileChangedError.new("You are attempting to update a file `#{action[:file_path]}` that has changed since you started editing it.")
end
end end
def validate_delete(action) def validate_delete(action)
...@@ -114,11 +137,5 @@ module Files ...@@ -114,11 +137,5 @@ module Files
params[:actions][index][:content] = blob.data params[:actions][index][:content] = blob.data
end end
end end
def validate_update(action)
if file_has_changed?
raise FileChangedError.new("You are attempting to update a file `#{action[:file_path]}` that has changed since you started editing it.")
end
end
end end
end end
...@@ -18,6 +18,10 @@ module Files ...@@ -18,6 +18,10 @@ module Files
def validate def validate
super super
if @file_content.nil?
raise_error("You must provide content.")
end
if file_has_changed? if file_has_changed?
raise FileChangedError.new("You are attempting to update a file that has changed since you started editing it.") raise FileChangedError.new("You are attempting to update a file that has changed since you started editing it.")
end end
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
- page_title "Projects" - page_title "Projects"
- header_title "Projects", dashboard_projects_path - header_title "Projects", dashboard_projects_path
.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
- if @projects.any? || params[:filter_projects] - if @projects.any? || params[:filter_projects]
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112 90" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="none" fill-rule="evenodd"><rect width="112" height="90" fill="#fff" rx="6"/><path fill="#eee" fill-rule="nonzero" d="m4 6.01v77.98c0 1.11.899 2.01 2 2.01h100c1.105 0 2-.898 2-2.01v-77.98c0-1.11-.899-2.01-2-2.01h-100c-1.105 0-2 .898-2 2.01m-4 0c0-3.319 2.686-6.01 6-6.01h100c3.315 0 6 2.694 6 6.01v77.98c0 3.319-2.686 6.01-6 6.01h-100c-3.315 0-6-2.694-6-6.01v-77.98"/><g transform="translate(26 35)"><rect width="4" height="39" x="5" fill="#eee" rx="2" id="0"/><rect width="4" height="21" x="5" y="18" fill="#fef0ea" rx="2"/><circle cx="7" cy="13" r="5" fill="#fff"/><path fill="#fb722e" fill-rule="nonzero" d="m7 20c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g transform="translate(49 35)"><use xlink:href="#0"/><rect width="4" height="21" x="5" y="18" fill="#b5a7dd" rx="2"/><circle cx="7" cy="25" r="5" fill="#fff"/><path fill="#6b4fbb" fill-rule="nonzero" d="m7 32c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g transform="translate(72 33)"><rect width="4" height="39" x="5" y="2" fill="#eee" rx="2"/><rect width="4" height="34" x="5" y="7" fill="#fef0ea" rx="2"/><circle cx="7" cy="7" r="5" fill="#fff"/><path fill="#fb722e" fill-rule="nonzero" d="m7 14c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g fill="#6b4fbb"><circle cx="13.5" cy="11.5" r="2.5"/><circle cx="23.5" cy="11.5" r="2.5" opacity=".5"/><circle cx="33.5" cy="11.5" r="2.5" opacity=".5"/></g><path fill="#eee" d="m0 19h111v4h-111z"/></g></svg>
...@@ -98,6 +98,7 @@ ...@@ -98,6 +98,7 @@
Snippets Snippets
%div{ class: container_class } %div{ class: container_class }
.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
.tab-content .tab-content
#activity.tab-pane #activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs .row-content-block.calender-block.white.second-block.hidden-xs
......
---
title: Removes label when moving issue to another list that it is currently in
merge_request:
author:
---
title: Added user callouts to the projects dashboard and user profile
merge_request:
author:
...@@ -155,17 +155,9 @@ class Gitlab::Seeder::CycleAnalytics ...@@ -155,17 +155,9 @@ class Gitlab::Seeder::CycleAnalytics
issue.project.repository.add_branch(@user, branch_name, 'master') issue.project.repository.add_branch(@user, branch_name, 'master')
options = { commit_sha = issue.project.repository.create_file(@user, filename, "content", options, message: "Commit for ##{issue.iid}", branch_name: branch_name)
committer: issue.project.repository.user_to_committer(@user),
author: issue.project.repository.user_to_committer(@user),
commit: { message: "Commit for ##{issue.iid}", branch: branch_name, update_ref: true },
file: { content: "content", path: filename, update: false }
}
commit_sha = Gitlab::Git::Blob.commit(issue.project.repository, options)
issue.project.repository.commit(commit_sha) issue.project.repository.commit(commit_sha)
GitPushService.new(issue.project, GitPushService.new(issue.project,
@user, @user,
oldrev: issue.project.repository.commit("master").sha, oldrev: issue.project.repository.commit("master").sha,
......
This diff is collapsed.
...@@ -52,13 +52,6 @@ module API ...@@ -52,13 +52,6 @@ module API
attrs = declared_params.merge(start_branch: declared_params[:branch], target_branch: declared_params[:branch]) attrs = declared_params.merge(start_branch: declared_params[:branch], target_branch: declared_params[:branch])
attrs[:actions].map! do |action|
action[:action] = action[:action].to_sym
action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
action
end
result = ::Files::MultiService.new(user_project, current_user, attrs).execute result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success if result[:status] == :success
......
...@@ -55,13 +55,6 @@ module API ...@@ -55,13 +55,6 @@ module API
branch = attrs.delete(:branch_name) branch = attrs.delete(:branch_name)
attrs.merge!(branch: branch, start_branch: branch, target_branch: branch) attrs.merge!(branch: branch, start_branch: branch, target_branch: branch)
attrs[:actions].map! do |action|
action[:action] = action[:action].to_sym
action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
action
end
result = ::Files::MultiService.new(user_project, current_user, attrs).execute result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success if result[:status] == :success
......
...@@ -93,163 +93,6 @@ module Gitlab ...@@ -93,163 +93,6 @@ module Gitlab
commit_id: sha, commit_id: sha,
) )
end end
# Commit file in repository and return commit sha
#
# options should contain next structure:
# file: {
# content: 'Lorem ipsum...',
# path: 'documents/story.txt',
# update: true
# },
# author: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# committer: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# commit: {
# message: 'Wow such commit',
# branch: 'master',
# update_ref: false
# }
#
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def commit(repository, options, action = :add)
file = options[:file]
update = file[:update].nil? ? true : file[:update]
author = options[:author]
committer = options[:committer]
commit = options[:commit]
repo = repository.rugged
ref = commit[:branch]
update_ref = commit[:update_ref].nil? ? true : commit[:update_ref]
parents = []
mode = 0o100644
unless ref.start_with?('refs/')
ref = 'refs/heads/' + ref
end
path_name = Gitlab::Git::PathHelper.normalize_path(file[:path])
# Abort if any invalid characters remain (e.g. ../foo)
raise Gitlab::Git::Repository::InvalidBlobName.new("Invalid path") if path_name.each_filename.to_a.include?('..')
filename = path_name.to_s
index = repo.index
unless repo.empty?
rugged_ref = repo.references[ref]
raise Gitlab::Git::Repository::InvalidRef.new("Invalid branch name") unless rugged_ref
last_commit = rugged_ref.target
index.read_tree(last_commit.tree)
parents = [last_commit]
end
if action == :remove
index.remove(filename)
else
file_entry = index.get(filename)
if action == :rename
old_path_name = Gitlab::Git::PathHelper.normalize_path(file[:previous_path])
old_filename = old_path_name.to_s
file_entry = index.get(old_filename)
index.remove(old_filename) unless file_entry.blank?
end
if file_entry
raise Gitlab::Git::Repository::InvalidBlobName.new("Filename already exists; update not allowed") unless update
# Preserve the current file mode if one is available
mode = file_entry[:mode] if file_entry[:mode]
end
content = file[:content]
detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
unless detect && detect[:type] == :binary
# When writing to the repo directly as we are doing here,
# the `core.autocrlf` config isn't taken into account.
content.gsub!("\r\n", "\n") if repository.autocrlf
end
oid = repo.write(content, :blob)
index.add(path: filename, oid: oid, mode: mode)
end
opts = {}
opts[:tree] = index.write_tree(repo)
opts[:author] = author
opts[:committer] = committer
opts[:message] = commit[:message]
opts[:parents] = parents
opts[:update_ref] = ref if update_ref
Rugged::Commit.create(repo, opts)
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# Remove file from repository and return commit sha
#
# options should contain next structure:
# file: {
# path: 'documents/story.txt'
# },
# author: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# committer: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# commit: {
# message: 'Remove FILENAME',
# branch: 'master'
# }
#
def remove(repository, options)
commit(repository, options, :remove)
end
# Rename file from repository and return commit sha
#
# options should contain next structure:
# file: {
# previous_path: 'documents/old_story.txt'
# path: 'documents/story.txt'
# content: 'Lorem ipsum...',
# update: true
# },
# author: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# committer: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# commit: {
# message: 'Rename FILENAME',
# branch: 'master'
# }
#
def rename(repository, options)
commit(repository, options, :rename)
end
end end
def initialize(options) def initialize(options)
......
module Gitlab
module Git
class Index
DEFAULT_MODE = 0o100644
attr_reader :repository, :raw_index
def initialize(repository)
@repository = repository
@raw_index = repository.rugged.index
end
delegate :read_tree, :get, to: :raw_index
def write_tree
raw_index.write_tree(repository.rugged)
end
def dir_exists?(path)
raw_index.find { |entry| entry[:path].start_with?("#{path}/") }
end
def create(options)
options = normalize_options(options)
file_entry = get(options[:file_path])
if file_entry
raise Gitlab::Git::Repository::InvalidBlobName.new("Filename already exists")
end
add_blob(options)
end
def create_dir(options)
options = normalize_options(options)
file_entry = get(options[:file_path])
if file_entry
raise Gitlab::Git::Repository::InvalidBlobName.new("Directory already exists as a file")
end
if dir_exists?(options[:file_path])
raise Gitlab::Git::Repository::InvalidBlobName.new("Directory already exists")
end
options = options.dup
options[:file_path] += '/.gitkeep'
options[:content] = ''
add_blob(options)
end
def update(options)
options = normalize_options(options)
file_entry = get(options[:file_path])
unless file_entry
raise Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
end
add_blob(options, mode: file_entry[:mode])
end
def move(options)
options = normalize_options(options)
file_entry = get(options[:previous_path])
unless file_entry
raise Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
end
raw_index.remove(options[:previous_path])
add_blob(options, mode: file_entry[:mode])
end
def delete(options)
options = normalize_options(options)
file_entry = get(options[:file_path])
unless file_entry
raise Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
end
raw_index.remove(options[:file_path])
end
private
def normalize_options(options)
options = options.dup
options[:file_path] = normalize_path(options[:file_path]) if options[:file_path]
options[:previous_path] = normalize_path(options[:previous_path]) if options[:previous_path]
options
end
def normalize_path(path)
pathname = Gitlab::Git::PathHelper.normalize_path(path.dup)
if pathname.each_filename.include?('..')
raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
end
pathname.to_s
end
def add_blob(options, mode: nil)
content = options[:content]
content = Base64.decode64(content) if options[:encoding] == 'base64'
detect = CharlockHolmes::EncodingDetector.new.detect(content)
unless detect && detect[:type] == :binary
# When writing to the repo directly as we are doing here,
# the `core.autocrlf` config isn't taken into account.
content.gsub!("\r\n", "\n") if repository.autocrlf
end
oid = repository.rugged.write(content, :blob)
raw_index.add(path: options[:file_path], oid: oid, mode: mode || DEFAULT_MODE)
rescue Rugged::IndexError => e
raise Gitlab::Git::Repository::InvalidBlobName.new(e.message)
end
end
end
end
...@@ -837,57 +837,6 @@ module Gitlab ...@@ -837,57 +837,6 @@ module Gitlab
rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value] rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value]
end end
# Create a new directory with a .gitkeep file. Creates
# all required nested directories (i.e. mkdir -p behavior)
#
# options should contain next structure:
# author: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# committer: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# commit: {
# message: 'Wow such commit',
# branch: 'master',
# update_ref: false
# }
def mkdir(path, options = {})
# Check if this directory exists; if it does, then don't bother
# adding .gitkeep file.
ref = options[:commit][:branch]
path = Gitlab::Git::PathHelper.normalize_path(path).to_s
rugged_ref = rugged.ref(ref)
raise InvalidRef.new("Invalid ref") if rugged_ref.nil?
target_commit = rugged_ref.target
raise InvalidRef.new("Invalid target commit") if target_commit.nil?
entry = tree_entry(target_commit, path)
if entry
if entry[:type] == :blob
raise InvalidBlobName.new("Directory already exists as a file")
else
raise InvalidBlobName.new("Directory already exists")
end
end
options[:file] = {
content: '',
path: "#{path}/.gitkeep",
update: true
}
Gitlab::Git::Blob.commit(self, options)
end
# Returns result like "git ls-files" , recursive and full file path # Returns result like "git ls-files" , recursive and full file path
# #
# Ex. # Ex.
......
...@@ -14,8 +14,8 @@ describe Projects::TemplatesController do ...@@ -14,8 +14,8 @@ describe Projects::TemplatesController do
before do before do
project.add_user(user, Gitlab::Access::MASTER) project.add_user(user, Gitlab::Access::MASTER)
project.repository.commit_file(user, file_path_1, 'something valid', project.repository.create_file(user, file_path_1, 'something valid',
message: 'test 3', branch_name: 'master', update: false) message: 'test 3', branch_name: 'master')
end end
describe '#show' do describe '#show' do
......
...@@ -138,27 +138,24 @@ FactoryGirl.define do ...@@ -138,27 +138,24 @@ FactoryGirl.define do
project.add_user(args[:user], args[:access]) project.add_user(args[:user], args[:access])
project.repository.commit_file( project.repository.create_file(
args[:user], args[:user],
".gitlab/#{args[:path]}/bug.md", ".gitlab/#{args[:path]}/bug.md",
'something valid', 'something valid',
message: 'test 3', message: 'test 3',
branch_name: 'master', branch_name: 'master')
update: false) project.repository.create_file(
project.repository.commit_file(
args[:user], args[:user],
".gitlab/#{args[:path]}/template_test.md", ".gitlab/#{args[:path]}/template_test.md",
'template_test', 'template_test',
message: 'test 1', message: 'test 1',
branch_name: 'master', branch_name: 'master')
update: false) project.repository.create_file(
project.repository.commit_file(
args[:user], args[:user],
".gitlab/#{args[:path]}/feature_proposal.md", ".gitlab/#{args[:path]}/feature_proposal.md",
'feature_proposal', 'feature_proposal',
message: 'test 2', message: 'test 2',
branch_name: 'master', branch_name: 'master')
update: false)
end end
end end
end end
......
...@@ -6,7 +6,7 @@ feature 'project owner creates a license file', feature: true, js: true do ...@@ -6,7 +6,7 @@ feature 'project owner creates a license file', feature: true, js: true do
let(:project_master) { create(:user) } let(:project_master) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
background do background do
project.repository.remove_file(project_master, 'LICENSE', project.repository.delete_file(project_master, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master') message: 'Remove LICENSE', branch_name: 'master')
project.team << [project_master, :master] project.team << [project_master, :master]
login_as(project_master) login_as(project_master)
......
...@@ -18,20 +18,18 @@ feature 'issuable templates', feature: true, js: true do ...@@ -18,20 +18,18 @@ feature 'issuable templates', feature: true, js: true do
let(:description_addition) { ' appending to description' } let(:description_addition) { ' appending to description' }
background do background do
project.repository.commit_file( project.repository.create_file(
user, user,
'.gitlab/issue_templates/bug.md', '.gitlab/issue_templates/bug.md',
template_content, template_content,
message: 'added issue template', message: 'added issue template',
branch_name: 'master', branch_name: 'master')
update: false) project.repository.create_file(
project.repository.commit_file(
user, user,
'.gitlab/issue_templates/test.md', '.gitlab/issue_templates/test.md',
longtemplate_content, longtemplate_content,
message: 'added issue template', message: 'added issue template',
branch_name: 'master', branch_name: 'master')
update: false)
visit edit_namespace_project_issue_path project.namespace, project, issue visit edit_namespace_project_issue_path project.namespace, project, issue
fill_in :'issue[title]', with: 'test issue title' fill_in :'issue[title]', with: 'test issue title'
end end
...@@ -79,13 +77,12 @@ feature 'issuable templates', feature: true, js: true do ...@@ -79,13 +77,12 @@ feature 'issuable templates', feature: true, js: true do
let(:issue) { create(:issue, author: user, assignee: user, project: project) } let(:issue) { create(:issue, author: user, assignee: user, project: project) }
background do background do
project.repository.commit_file( project.repository.create_file(
user, user,
'.gitlab/issue_templates/bug.md', '.gitlab/issue_templates/bug.md',
template_content, template_content,
message: 'added issue template', message: 'added issue template',
branch_name: 'master', branch_name: 'master')
update: false)
visit edit_namespace_project_issue_path project.namespace, project, issue visit edit_namespace_project_issue_path project.namespace, project, issue
fill_in :'issue[title]', with: 'test issue title' fill_in :'issue[title]', with: 'test issue title'
fill_in :'issue[description]', with: prior_description fill_in :'issue[description]', with: prior_description
...@@ -104,13 +101,12 @@ feature 'issuable templates', feature: true, js: true do ...@@ -104,13 +101,12 @@ feature 'issuable templates', feature: true, js: true do
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) } let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) }
background do background do
project.repository.commit_file( project.repository.create_file(
user, user,
'.gitlab/merge_request_templates/feature-proposal.md', '.gitlab/merge_request_templates/feature-proposal.md',
template_content, template_content,
message: 'added merge request template', message: 'added merge request template',
branch_name: 'master', branch_name: 'master')
update: false)
visit edit_namespace_project_merge_request_path project.namespace, project, merge_request visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title' fill_in :'merge_request[title]', with: 'test merge request title'
end end
...@@ -135,13 +131,12 @@ feature 'issuable templates', feature: true, js: true do ...@@ -135,13 +131,12 @@ feature 'issuable templates', feature: true, js: true do
fork_project.team << [fork_user, :master] fork_project.team << [fork_user, :master]
create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project) create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
login_as fork_user login_as fork_user
project.repository.commit_file( project.repository.create_file(
fork_user, fork_user,
'.gitlab/merge_request_templates/feature-proposal.md', '.gitlab/merge_request_templates/feature-proposal.md',
template_content, template_content,
message: 'added merge request template', message: 'added merge request template',
branch_name: 'master', branch_name: 'master')
update: false)
visit edit_namespace_project_merge_request_path project.namespace, project, merge_request visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title' fill_in :'merge_request[title]', with: 'test merge request title'
end end
......
require 'spec_helper'
describe 'User Callouts', js: true do
let(:user) { create(:user) }
let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') }
before do
login_as(user)
project.team << [user, :master]
end
it 'takes you to the profile preferences when the link is clicked' do
visit dashboard_projects_path
click_link 'Check it out'
expect(current_path).to eq profile_preferences_path
end
describe 'user callout should appear in two routes' do
it 'shows up on the user profile' do
visit user_path(user)
expect(find('.user-callout')).to have_content 'Customize your experience'
end
it 'shows up on the dashboard projects' do
visit dashboard_projects_path
expect(find('.user-callout')).to have_content 'Customize your experience'
end
end
it 'hides the user callout when click on the dismiss icon' do
visit user_path(user)
within('.user-callout') do
find('.close-user-callout').click
end
expect(page).not_to have_selector('#user-callout')
end
end
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
/* global boardsMockInterceptor */ /* global boardsMockInterceptor */
/* global BoardService */ /* global BoardService */
/* global List */ /* global List */
/* global ListIssue */
/* global listObj */ /* global listObj */
/* global listObjDuplicate */
require('~/lib/utils/url_utility'); require('~/lib/utils/url_utility');
require('~/boards/models/issue'); require('~/boards/models/issue');
...@@ -84,4 +86,23 @@ describe('List model', () => { ...@@ -84,4 +86,23 @@ describe('List model', () => {
done(); done();
}, 0); }, 0);
}); });
it('sends service request to update issue label', () => {
const listDup = new List(listObjDuplicate);
const issue = new ListIssue({
title: 'Testing',
iid: 1,
confidential: false,
labels: [list.label, listDup.label]
});
list.issues.push(issue);
listDup.issues.push(issue);
spyOn(gl.boardService, 'moveIssue').and.callThrough();
listDup.updateIssueLabel(list, issue);
expect(gl.boardService.moveIssue).toHaveBeenCalledWith(issue.id, list.id, listDup.id);
});
}); });
.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
const UserCallout = require('~/user_callout');
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
const Cookie = window.Cookies;
describe('UserCallout', () => {
const fixtureName = 'static/user_callout.html.raw';
preloadFixtures(fixtureName);
beforeEach(function () {
loadFixtures(fixtureName);
this.userCallout = new UserCallout();
this.closeButton = $('.close-user-callout');
this.userCalloutBtn = $('.user-callout-btn');
this.userCalloutContainer = $('.user-callout');
Cookie.set(USER_CALLOUT_COOKIE, 'false');
});
afterEach(function () {
Cookie.set(USER_CALLOUT_COOKIE, 'false');
});
it('shows when cookie is set to false', function () {
expect(Cookie.get(USER_CALLOUT_COOKIE)).toBeDefined();
expect(this.userCalloutContainer.is(':visible')).toBe(true);
});
it('hides when user clicks on the dismiss-icon', function () {
this.closeButton.click();
expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true');
});
it('hides when user clicks on the "check it out" button', function () {
this.userCalloutBtn.click();
expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true');
});
});
...@@ -222,191 +222,6 @@ describe Gitlab::Git::Blob, seed_helper: true do ...@@ -222,191 +222,6 @@ describe Gitlab::Git::Blob, seed_helper: true do
end end
end end
describe :commit do
let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
let(:commit_options) do
{
file: {
content: 'Lorem ipsum...',
path: 'documents/story.txt'
},
author: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
committer: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
commit: {
message: 'Wow such commit',
branch: 'fix-mode'
}
}
end
let(:commit_sha) { Gitlab::Git::Blob.commit(repository, commit_options) }
let(:commit) { repository.lookup(commit_sha) }
it 'should add file with commit' do
# Commit message valid
expect(commit.message).to eq('Wow such commit')
tree = commit.tree.to_a.find { |tree| tree[:name] == 'documents' }
# Directory was created
expect(tree[:type]).to eq(:tree)
# File was created
expect(repository.lookup(tree[:oid]).first[:name]).to eq('story.txt')
end
describe "ref updating" do
it 'creates a commit but does not udate a ref' do
commit_opts = commit_options.tap{ |opts| opts[:commit][:update_ref] = false}
commit_sha = Gitlab::Git::Blob.commit(repository, commit_opts)
commit = repository.lookup(commit_sha)
# Commit message valid
expect(commit.message).to eq('Wow such commit')
# Does not update any related ref
expect(repository.lookup("fix-mode").oid).not_to eq(commit.oid)
expect(repository.lookup("HEAD").oid).not_to eq(commit.oid)
end
end
describe 'reject updates' do
it 'should reject updates' do
commit_options[:file][:update] = false
commit_options[:file][:path] = 'files/executables/ls'
expect{ commit_sha }.to raise_error('Filename already exists; update not allowed')
end
end
describe 'file modes' do
it 'should preserve file modes with commit' do
commit_options[:file][:path] = 'files/executables/ls'
entry = Gitlab::Git::Blob.find_entry_by_path(repository, commit.tree.oid, commit_options[:file][:path])
expect(entry[:filemode]).to eq(0100755)
end
end
end
describe :rename do
let(:repository) { Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) }
let(:rename_options) do
{
file: {
path: 'NEWCONTRIBUTING.md',
previous_path: 'CONTRIBUTING.md',
content: 'Lorem ipsum...',
update: true
},
author: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
committer: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
commit: {
message: 'Rename readme',
branch: 'master'
}
}
end
let(:rename_options2) do
{
file: {
content: 'Lorem ipsum...',
path: 'bin/new_executable',
previous_path: 'bin/executable',
},
author: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
committer: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
commit: {
message: 'Updates toberenamed.txt',
branch: 'master',
update_ref: false
}
}
end
it 'maintains file permissions when renaming' do
mode = 0o100755
Gitlab::Git::Blob.rename(repository, rename_options2)
expect(repository.rugged.index.get(rename_options2[:file][:path])[:mode]).to eq(mode)
end
it 'renames the file with commit and not change file permissions' do
ref = rename_options[:commit][:branch]
expect(repository.rugged.index.get('CONTRIBUTING.md')).not_to be_nil
expect { Gitlab::Git::Blob.rename(repository, rename_options) }.to change { repository.commit_count(ref) }.by(1)
expect(repository.rugged.index.get('CONTRIBUTING.md')).to be_nil
expect(repository.rugged.index.get('NEWCONTRIBUTING.md')).not_to be_nil
end
end
describe :remove do
let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
let(:commit_options) do
{
file: {
path: 'README.md'
},
author: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
committer: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
commit: {
message: 'Remove readme',
branch: 'feature'
}
}
end
let(:commit_sha) { Gitlab::Git::Blob.remove(repository, commit_options) }
let(:commit) { repository.lookup(commit_sha) }
let(:blob) { Gitlab::Git::Blob.find(repository, commit_sha, "README.md") }
it 'should remove file with commit' do
# Commit message valid
expect(commit.message).to eq('Remove readme')
# File was removed
expect(blob).to be_nil
end
end
describe :lfs_pointers do describe :lfs_pointers do
context 'file a valid lfs pointer' do context 'file a valid lfs pointer' do
let(:blob) do let(:blob) do
......
require 'spec_helper'
describe Gitlab::Git::Index, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
let(:index) { described_class.new(repository) }
before do
index.read_tree(repository.lookup('master').tree)
end
describe '#create' do
let(:options) do
{
content: 'Lorem ipsum...',
file_path: 'documents/story.txt'
}
end
context 'when no file at that path exists' do
it 'creates the file in the index' do
index.create(options)
entry = index.get(options[:file_path])
expect(entry).not_to be_nil
expect(repository.lookup(entry[:oid]).content).to eq(options[:content])
end
end
context 'when a file at that path exists' do
before do
options[:file_path] = 'files/executables/ls'
end
it 'raises an error' do
expect { index.create(options) }.to raise_error('Filename already exists')
end
end
context 'when content is in base64' do
before do
options[:content] = Base64.encode64(options[:content])
options[:encoding] = 'base64'
end
it 'decodes base64' do
index.create(options)
entry = index.get(options[:file_path])
expect(repository.lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content]))
end
end
context 'when content contains CRLF' do
before do
repository.autocrlf = :input
options[:content] = "Hello,\r\nWorld"
end
it 'converts to LF' do
index.create(options)
entry = index.get(options[:file_path])
expect(repository.lookup(entry[:oid]).content).to eq("Hello,\nWorld")
end
end
end
describe '#create_dir' do
let(:options) do
{
file_path: 'newdir'
}
end
context 'when no file or dir at that path exists' do
it 'creates the dir in the index' do
index.create_dir(options)
entry = index.get(options[:file_path] + '/.gitkeep')
expect(entry).not_to be_nil
end
end
context 'when a file at that path exists' do
before do
options[:file_path] = 'files/executables/ls'
end
it 'raises an error' do
expect { index.create_dir(options) }.to raise_error('Directory already exists as a file')
end
end
context 'when a directory at that path exists' do
before do
options[:file_path] = 'files/executables'
end
it 'raises an error' do
expect { index.create_dir(options) }.to raise_error('Directory already exists')
end
end
end
describe '#update' do
let(:options) do
{
content: 'Lorem ipsum...',
file_path: 'README.md'
}
end
context 'when no file at that path exists' do
before do
options[:file_path] = 'documents/story.txt'
end
it 'raises an error' do
expect { index.update(options) }.to raise_error("File doesn't exist")
end
end
context 'when a file at that path exists' do
it 'updates the file in the index' do
index.update(options)
entry = index.get(options[:file_path])
expect(repository.lookup(entry[:oid]).content).to eq(options[:content])
end
it 'preserves file mode' do
options[:file_path] = 'files/executables/ls'
index.update(options)
entry = index.get(options[:file_path])
expect(entry[:mode]).to eq(0100755)
end
end
end
describe '#move' do
let(:options) do
{
content: 'Lorem ipsum...',
previous_path: 'README.md',
file_path: 'NEWREADME.md'
}
end
context 'when no file at that path exists' do
it 'raises an error' do
options[:previous_path] = 'documents/story.txt'
expect { index.move(options) }.to raise_error("File doesn't exist")
end
end
context 'when a file at that path exists' do
it 'removes the old file in the index' do
index.move(options)
entry = index.get(options[:previous_path])
expect(entry).to be_nil
end
it 'creates the new file in the index' do
index.move(options)
entry = index.get(options[:file_path])
expect(entry).not_to be_nil
expect(repository.lookup(entry[:oid]).content).to eq(options[:content])
end
it 'preserves file mode' do
options[:previous_path] = 'files/executables/ls'
index.move(options)
entry = index.get(options[:file_path])
expect(entry[:mode]).to eq(0100755)
end
end
end
describe '#delete' do
let(:options) do
{
file_path: 'README.md'
}
end
context 'when no file at that path exists' do
before do
options[:file_path] = 'documents/story.txt'
end
it 'raises an error' do
expect { index.delete(options) }.to raise_error("File doesn't exist")
end
end
context 'when a file at that path exists' do
it 'removes the file in the index' do
index.delete(options)
entry = index.get(options[:file_path])
expect(entry).to be_nil
end
end
end
end
...@@ -844,81 +844,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -844,81 +844,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
describe '#mkdir' do
let(:commit_options) do
{
author: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
committer: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
commit: {
message: 'Test message',
branch: 'refs/heads/fix',
}
}
end
def generate_diff_for_path(path)
"diff --git a/#{path}/.gitkeep b/#{path}/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/#{path}/.gitkeep\n"
end
shared_examples 'mkdir diff check' do |path, expected_path|
it 'creates a directory' do
result = repository.mkdir(path, commit_options)
expect(result).not_to eq(nil)
# Verify another mkdir doesn't create a directory that already exists
expect{ repository.mkdir(path, commit_options) }.to raise_error('Directory already exists')
end
end
describe 'creates a directory in root directory' do
it_should_behave_like 'mkdir diff check', 'new_dir', 'new_dir'
end
describe 'creates a directory in subdirectory' do
it_should_behave_like 'mkdir diff check', 'files/ruby/test', 'files/ruby/test'
end
describe 'creates a directory in subdirectory with a slash' do
it_should_behave_like 'mkdir diff check', '/files/ruby/test2', 'files/ruby/test2'
end
describe 'creates a directory in subdirectory with multiple slashes' do
it_should_behave_like 'mkdir diff check', '//files/ruby/test3', 'files/ruby/test3'
end
describe 'handles relative paths' do
it_should_behave_like 'mkdir diff check', 'files/ruby/../test_relative', 'files/test_relative'
end
describe 'creates nested directories' do
it_should_behave_like 'mkdir diff check', 'files/missing/test', 'files/missing/test'
end
it 'does not attempt to create a directory with invalid relative path' do
expect{ repository.mkdir('../files/missing/test', commit_options) }.to raise_error('Invalid path')
end
it 'does not attempt to overwrite a file' do
expect{ repository.mkdir('README.md', commit_options) }.to raise_error('Directory already exists as a file')
end
it 'does not attempt to overwrite a directory' do
expect{ repository.mkdir('files', commit_options) }.to raise_error('Directory already exists')
end
end
describe "#ls_files" do describe "#ls_files" do
let(:master_file_paths) { repository.ls_files("master") } let(:master_file_paths) { repository.ls_files("master") }
let(:not_existed_branch) { repository.ls_files("not_existed_branch") } let(:not_existed_branch) { repository.ls_files("not_existed_branch") }
......
...@@ -209,13 +209,12 @@ describe Gitlab::GitAccess, lib: true do ...@@ -209,13 +209,12 @@ describe Gitlab::GitAccess, lib: true do
stub_git_hooks stub_git_hooks
project.repository.add_branch(user, unprotected_branch, 'feature') project.repository.add_branch(user, unprotected_branch, 'feature')
target_branch = project.repository.lookup('feature') target_branch = project.repository.lookup('feature')
source_branch = project.repository.commit_file( source_branch = project.repository.create_file(
user, user,
FFaker::InternetSE.login_user_name, FFaker::InternetSE.login_user_name,
FFaker::HipsterIpsum.paragraph, FFaker::HipsterIpsum.paragraph,
message: FFaker::HipsterIpsum.sentence, message: FFaker::HipsterIpsum.sentence,
branch_name: unprotected_branch, branch_name: unprotected_branch)
update: false)
rugged = project.repository.rugged rugged = project.repository.rugged
author = { email: "email@example.com", time: Time.now, name: "Example Git User" } author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
......
...@@ -21,13 +21,12 @@ describe 'CycleAnalytics#production', feature: true do ...@@ -21,13 +21,12 @@ describe 'CycleAnalytics#production', feature: true do
["production deploy happens after merge request is merged (along with other changes)", ["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data| lambda do |context, data|
# Make other changes on master # Make other changes on master
sha = context.project.repository.commit_file( sha = context.project.repository.create_file(
context.user, context.user,
context.random_git_name, context.random_git_name,
'content', 'content',
message: 'commit message', message: 'commit message',
branch_name: 'master', branch_name: 'master')
update: false)
context.project.repository.commit(sha) context.project.repository.commit(sha)
context.deploy_master context.deploy_master
......
...@@ -26,13 +26,12 @@ describe 'CycleAnalytics#staging', feature: true do ...@@ -26,13 +26,12 @@ describe 'CycleAnalytics#staging', feature: true do
["production deploy happens after merge request is merged (along with other changes)", ["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data| lambda do |context, data|
# Make other changes on master # Make other changes on master
sha = context.project.repository.commit_file( sha = context.project.repository.create_file(
context.user, context.user,
context.random_git_name, context.random_git_name,
'content', 'content',
message: 'commit message', message: 'commit message',
branch_name: 'master', branch_name: 'master')
update: false)
context.project.repository.commit(sha) context.project.repository.commit(sha)
context.deploy_master context.deploy_master
......
...@@ -1765,7 +1765,7 @@ describe Project, models: true do ...@@ -1765,7 +1765,7 @@ describe Project, models: true do
end end
before do before do
project.repository.commit_file(User.last, '.gitlab/route-map.yml', route_map, message: 'Add .gitlab/route-map.yml', branch_name: 'master', update: false) project.repository.create_file(User.last, '.gitlab/route-map.yml', route_map, message: 'Add .gitlab/route-map.yml', branch_name: 'master')
end end
context 'when there is a .gitlab/route-map.yml at the commit' do context 'when there is a .gitlab/route-map.yml at the commit' do
......
This diff is collapsed.
...@@ -127,7 +127,7 @@ describe API::Files, api: true do ...@@ -127,7 +127,7 @@ describe API::Files, api: true do
end end
it "returns a 400 if editor fails to create file" do it "returns a 400 if editor fails to create file" do
allow_any_instance_of(Repository).to receive(:commit_file). allow_any_instance_of(Repository).to receive(:create_file).
and_return(false) and_return(false)
post api("/projects/#{project.id}/repository/files", user), valid_params post api("/projects/#{project.id}/repository/files", user), valid_params
...@@ -215,7 +215,7 @@ describe API::Files, api: true do ...@@ -215,7 +215,7 @@ describe API::Files, api: true do
end end
it "returns a 400 if fails to create file" do it "returns a 400 if fails to create file" do
allow_any_instance_of(Repository).to receive(:remove_file).and_return(false) allow_any_instance_of(Repository).to receive(:delete_file).and_return(false)
delete api("/projects/#{project.id}/repository/files", user), valid_params delete api("/projects/#{project.id}/repository/files", user), valid_params
......
...@@ -127,7 +127,7 @@ describe API::V3::Files, api: true do ...@@ -127,7 +127,7 @@ describe API::V3::Files, api: true do
end end
it "returns a 400 if editor fails to create file" do it "returns a 400 if editor fails to create file" do
allow_any_instance_of(Repository).to receive(:commit_file). allow_any_instance_of(Repository).to receive(:create_file).
and_return(false) and_return(false)
post v3_api("/projects/#{project.id}/repository/files", user), valid_params post v3_api("/projects/#{project.id}/repository/files", user), valid_params
...@@ -215,7 +215,7 @@ describe API::V3::Files, api: true do ...@@ -215,7 +215,7 @@ describe API::V3::Files, api: true do
end end
it "returns a 400 if fails to create file" do it "returns a 400 if fails to create file" do
allow_any_instance_of(Repository).to receive(:remove_file).and_return(false) allow_any_instance_of(Repository).to receive(:delete_file).and_return(false)
delete v3_api("/projects/#{project.id}/repository/files", user), valid_params delete v3_api("/projects/#{project.id}/repository/files", user), valid_params
......
...@@ -66,13 +66,12 @@ describe MergeRequests::ResolveService do ...@@ -66,13 +66,12 @@ describe MergeRequests::ResolveService do
context 'when the source project is a fork and does not contain the HEAD of the target branch' do context 'when the source project is a fork and does not contain the HEAD of the target branch' do
let!(:target_head) do let!(:target_head) do
project.repository.commit_file( project.repository.create_file(
user, user,
'new-file-in-target', 'new-file-in-target',
'', '',
message: 'Add new file in target', message: 'Add new file in target',
branch_name: 'conflict-start', branch_name: 'conflict-start')
update: false)
end end
before do before do
......
...@@ -9,14 +9,7 @@ module CycleAnalyticsHelpers ...@@ -9,14 +9,7 @@ module CycleAnalyticsHelpers
commit_shas = Array.new(count) do |index| commit_shas = Array.new(count) do |index|
filename = random_git_name filename = random_git_name
options = { commit_sha = project.repository.create_file(user, filename, "content", message: message, branch_name: branch_name)
committer: project.repository.user_to_committer(user),
author: project.repository.user_to_committer(user),
commit: { message: message, branch: branch_name, update_ref: true },
file: { content: "content", path: filename, update: false }
}
commit_sha = Gitlab::Git::Blob.commit(project.repository, options)
project.repository.commit(commit_sha) project.repository.commit(commit_sha)
commit_sha commit_sha
...@@ -35,13 +28,12 @@ module CycleAnalyticsHelpers ...@@ -35,13 +28,12 @@ module CycleAnalyticsHelpers
project.repository.add_branch(user, source_branch, 'master') project.repository.add_branch(user, source_branch, 'master')
end end
sha = project.repository.commit_file( sha = project.repository.create_file(
user, user,
random_git_name, random_git_name,
'content', 'content',
message: 'commit message', message: 'commit message',
branch_name: source_branch, branch_name: source_branch)
update: false)
project.repository.commit(sha) project.repository.commit(sha)
opts = { opts = {
......
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