Merge branch '24501-new-file-existing-branch' into 'master'
New file from interface on existing branch Closes #24501 See merge request !8427
This commit is contained in:
commit
1585608bdc
|
@ -36,7 +36,7 @@
|
||||||
this.removeFile(file);
|
this.removeFile(file);
|
||||||
});
|
});
|
||||||
return this.on('sending', function(file, xhr, formData) {
|
return this.on('sending', function(file, xhr, formData) {
|
||||||
formData.append('target_branch', form.find('.js-target-branch').val());
|
formData.append('target_branch', form.find('input[name="target_branch"]').val());
|
||||||
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
|
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
|
||||||
formData.append('commit_message', form.find('.js-commit-message').val());
|
formData.append('commit_message', form.find('.js-commit-message').val());
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
class CreateBranchDropdown {
|
||||||
|
constructor(el, targetBranchDropdown) {
|
||||||
|
this.targetBranchDropdown = targetBranchDropdown;
|
||||||
|
this.el = el;
|
||||||
|
this.dropdownBack = this.el.closest('.dropdown').querySelector('.dropdown-menu-back');
|
||||||
|
this.cancelButton = this.el.querySelector('.js-cancel-branch-btn');
|
||||||
|
this.newBranchField = this.el.querySelector('#new_branch_name');
|
||||||
|
this.newBranchCreateButton = this.el.querySelector('.js-new-branch-btn');
|
||||||
|
|
||||||
|
this.newBranchCreateButton.setAttribute('disabled', '');
|
||||||
|
|
||||||
|
this.addBindings();
|
||||||
|
this.cleanupWrapper = this.cleanup.bind(this);
|
||||||
|
document.addEventListener('beforeunload', this.cleanupWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
this.cleanBindings();
|
||||||
|
document.removeEventListener('beforeunload', this.cleanupWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanBindings() {
|
||||||
|
this.newBranchField.removeEventListener('keyup', this.enableBranchCreateButtonWrapper);
|
||||||
|
this.newBranchField.removeEventListener('change', this.enableBranchCreateButtonWrapper);
|
||||||
|
this.newBranchField.removeEventListener('keydown', this.handleNewBranchKeydownWrapper);
|
||||||
|
this.dropdownBack.removeEventListener('click', this.resetFormWrapper);
|
||||||
|
this.cancelButton.removeEventListener('click', this.handleCancelClickWrapper);
|
||||||
|
this.newBranchCreateButton.removeEventListener('click', this.createBranchWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
addBindings() {
|
||||||
|
this.enableBranchCreateButtonWrapper = this.enableBranchCreateButton.bind(this);
|
||||||
|
this.handleNewBranchKeydownWrapper = this.handleNewBranchKeydown.bind(this);
|
||||||
|
this.resetFormWrapper = this.resetForm.bind(this);
|
||||||
|
this.handleCancelClickWrapper = this.handleCancelClick.bind(this);
|
||||||
|
this.createBranchWrapper = this.createBranch.bind(this);
|
||||||
|
|
||||||
|
this.newBranchField.addEventListener('keyup', this.enableBranchCreateButtonWrapper);
|
||||||
|
this.newBranchField.addEventListener('change', this.enableBranchCreateButtonWrapper);
|
||||||
|
this.newBranchField.addEventListener('keydown', this.handleNewBranchKeydownWrapper);
|
||||||
|
this.dropdownBack.addEventListener('click', this.resetFormWrapper);
|
||||||
|
this.cancelButton.addEventListener('click', this.handleCancelClickWrapper);
|
||||||
|
this.newBranchCreateButton.addEventListener('click', this.createBranchWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancelClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
this.resetForm();
|
||||||
|
this.dropdownBack.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNewBranchKeydown(e) {
|
||||||
|
const keyCode = e.which;
|
||||||
|
const ENTER_KEYCODE = 13;
|
||||||
|
if (keyCode === ENTER_KEYCODE) {
|
||||||
|
this.createBranch(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enableBranchCreateButton() {
|
||||||
|
if (this.newBranchField.value !== '') {
|
||||||
|
this.newBranchCreateButton.removeAttribute('disabled');
|
||||||
|
} else {
|
||||||
|
this.newBranchCreateButton.setAttribute('disabled', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm() {
|
||||||
|
this.newBranchField.value = '';
|
||||||
|
this.enableBranchCreateButtonWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
createBranch(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (this.newBranchCreateButton.getAttribute('disabled') === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newBranchName = this.newBranchField.value;
|
||||||
|
this.targetBranchDropdown.setNewBranch(newBranchName);
|
||||||
|
this.resetForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
gl.CreateBranchDropdown = CreateBranchDropdown;
|
|
@ -0,0 +1,152 @@
|
||||||
|
/* eslint-disable class-methods-use-this */
|
||||||
|
const SELECT_ITEM_MSG = 'Select';
|
||||||
|
|
||||||
|
class TargetBranchDropDown {
|
||||||
|
constructor(dropdown) {
|
||||||
|
this.dropdown = dropdown;
|
||||||
|
this.$dropdown = $(dropdown);
|
||||||
|
this.fieldName = this.dropdown.getAttribute('data-field-name');
|
||||||
|
this.form = this.dropdown.closest('form');
|
||||||
|
this.createDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bootstrap() {
|
||||||
|
const dropdowns = document.querySelectorAll('.js-project-branches-dropdown');
|
||||||
|
[].forEach.call(dropdowns, dropdown => new TargetBranchDropDown(dropdown));
|
||||||
|
}
|
||||||
|
|
||||||
|
createDropdown() {
|
||||||
|
const self = this;
|
||||||
|
this.$dropdown.glDropdown({
|
||||||
|
selectable: true,
|
||||||
|
filterable: true,
|
||||||
|
search: {
|
||||||
|
fields: ['title'],
|
||||||
|
},
|
||||||
|
data: (term, callback) => $.ajax({
|
||||||
|
url: self.dropdown.getAttribute('data-refs-url'),
|
||||||
|
data: {
|
||||||
|
ref: self.dropdown.getAttribute('data-ref'),
|
||||||
|
show_all: true,
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
}).done(refs => callback(self.dropdownData(refs))),
|
||||||
|
toggleLabel(item, el) {
|
||||||
|
if (el.is('.is-active')) {
|
||||||
|
return item.text;
|
||||||
|
}
|
||||||
|
return SELECT_ITEM_MSG;
|
||||||
|
},
|
||||||
|
clicked(item, el, e) {
|
||||||
|
e.preventDefault();
|
||||||
|
self.onClick.call(self);
|
||||||
|
},
|
||||||
|
fieldName: self.fieldName,
|
||||||
|
});
|
||||||
|
return new gl.CreateBranchDropdown(this.form.querySelector('.dropdown-new-branch'), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
this.enableSubmit();
|
||||||
|
this.$dropdown.trigger('change.branch');
|
||||||
|
}
|
||||||
|
|
||||||
|
enableSubmit() {
|
||||||
|
const submitBtn = this.form.querySelector('[type="submit"]');
|
||||||
|
if (this.branchInput && this.branchInput.value) {
|
||||||
|
submitBtn.removeAttribute('disabled');
|
||||||
|
} else {
|
||||||
|
submitBtn.setAttribute('disabled', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdownData(refs) {
|
||||||
|
const branchList = this.dropdownItems(refs);
|
||||||
|
this.cachedRefs = refs;
|
||||||
|
this.addDefaultBranch(branchList);
|
||||||
|
this.addNewBranch(branchList);
|
||||||
|
return { Branches: branchList };
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdownItems(refs) {
|
||||||
|
return refs.map(this.dropdownItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdownItem(ref) {
|
||||||
|
return { id: ref, text: ref, title: ref };
|
||||||
|
}
|
||||||
|
|
||||||
|
addDefaultBranch(branchList) {
|
||||||
|
// when no branch is selected do nothing
|
||||||
|
if (!this.branchInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const branchInputVal = this.branchInput.value;
|
||||||
|
const currentBranchIndex = this.searchBranch(branchList, branchInputVal);
|
||||||
|
|
||||||
|
if (currentBranchIndex === -1) {
|
||||||
|
this.unshiftBranch(branchList, this.dropdownItem(branchInputVal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addNewBranch(branchList) {
|
||||||
|
if (this.newBranch) {
|
||||||
|
this.unshiftBranch(branchList, this.newBranch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchBranch(branchList, branchName) {
|
||||||
|
return _.findIndex(branchList, el => branchName === el.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
unshiftBranch(branchList, branch) {
|
||||||
|
const branchIndex = this.searchBranch(branchList, branch.id);
|
||||||
|
|
||||||
|
if (branchIndex === -1) {
|
||||||
|
branchList.unshift(branch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setNewBranch(newBranchName) {
|
||||||
|
this.newBranch = this.dropdownItem(newBranchName);
|
||||||
|
this.refreshData();
|
||||||
|
this.selectBranch(this.searchBranch(this.glDropdown.fullData.Branches, newBranchName));
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshData() {
|
||||||
|
this.glDropdown.fullData = this.dropdownData(this.cachedRefs);
|
||||||
|
this.clearFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFilter() {
|
||||||
|
// apply an empty filter in order to refresh the data
|
||||||
|
this.glDropdown.filter.filter('');
|
||||||
|
this.dropdown.closest('.dropdown').querySelector('.dropdown-page-one .dropdown-input-field').value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
selectBranch(index) {
|
||||||
|
const branch = this.dropdown.closest('.dropdown').querySelectorAll('li a')[index];
|
||||||
|
|
||||||
|
if (!branch.classList.contains('is-active')) {
|
||||||
|
branch.click();
|
||||||
|
} else {
|
||||||
|
this.closeDropdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDropdown() {
|
||||||
|
this.dropdown.closest('.dropdown').querySelector('.dropdown-menu-close').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
get branchInput() {
|
||||||
|
return this.form.querySelector(`input[name="${this.fieldName}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
get glDropdown() {
|
||||||
|
return this.$dropdown.data('glDropdown');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
gl.TargetBranchDropDown = TargetBranchDropDown;
|
|
@ -59,7 +59,7 @@ const UserCallout = require('./user_callout');
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispatcher.prototype.initPageScripts = function() {
|
Dispatcher.prototype.initPageScripts = function() {
|
||||||
var page, path, shortcut_handler;
|
var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
|
||||||
page = $('body').attr('data-page');
|
page = $('body').attr('data-page');
|
||||||
if (!page) {
|
if (!page) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -245,16 +245,36 @@ const UserCallout = require('./user_callout');
|
||||||
case 'projects:tree:show':
|
case 'projects:tree:show':
|
||||||
shortcut_handler = new ShortcutsNavigation();
|
shortcut_handler = new ShortcutsNavigation();
|
||||||
new TreeView();
|
new TreeView();
|
||||||
|
gl.TargetBranchDropDown.bootstrap();
|
||||||
break;
|
break;
|
||||||
case 'projects:find_file:show':
|
case 'projects:find_file:show':
|
||||||
shortcut_handler = true;
|
shortcut_handler = true;
|
||||||
break;
|
break;
|
||||||
|
case 'projects:blob:new':
|
||||||
|
gl.TargetBranchDropDown.bootstrap();
|
||||||
|
break;
|
||||||
|
case 'projects:blob:create':
|
||||||
|
gl.TargetBranchDropDown.bootstrap();
|
||||||
|
break;
|
||||||
case 'projects:blob:show':
|
case 'projects:blob:show':
|
||||||
|
gl.TargetBranchDropDown.bootstrap();
|
||||||
|
new LineHighlighter();
|
||||||
|
shortcut_handler = new ShortcutsNavigation();
|
||||||
|
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
|
||||||
|
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
|
||||||
|
new ShortcutsBlob({
|
||||||
|
skipResetBindings: true,
|
||||||
|
fileBlobPermalinkUrl,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'projects:blob:edit':
|
||||||
|
gl.TargetBranchDropDown.bootstrap();
|
||||||
|
break;
|
||||||
case 'projects:blame:show':
|
case 'projects:blame:show':
|
||||||
new LineHighlighter();
|
new LineHighlighter();
|
||||||
shortcut_handler = new ShortcutsNavigation();
|
shortcut_handler = new ShortcutsNavigation();
|
||||||
const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
|
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
|
||||||
const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
|
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
|
||||||
new ShortcutsBlob({
|
new ShortcutsBlob({
|
||||||
skipResetBindings: true,
|
skipResetBindings: true,
|
||||||
fileBlobPermalinkUrl,
|
fileBlobPermalinkUrl,
|
||||||
|
|
|
@ -66,6 +66,8 @@ import './blob/blob_gitignore_selectors';
|
||||||
import './blob/blob_license_selector';
|
import './blob/blob_license_selector';
|
||||||
import './blob/blob_license_selectors';
|
import './blob/blob_license_selectors';
|
||||||
import './blob/template_selector';
|
import './blob/template_selector';
|
||||||
|
import './blob/create_branch_dropdown';
|
||||||
|
import './blob/target_branch_dropdown';
|
||||||
|
|
||||||
// templates
|
// templates
|
||||||
import './templates/issuable_template_selector';
|
import './templates/issuable_template_selector';
|
||||||
|
|
|
@ -3,19 +3,23 @@
|
||||||
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
|
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
|
||||||
|
|
||||||
this.NewCommitForm = (function() {
|
this.NewCommitForm = (function() {
|
||||||
function NewCommitForm(form) {
|
function NewCommitForm(form, targetBranchName = 'target_branch') {
|
||||||
|
this.form = form;
|
||||||
|
this.targetBranchName = targetBranchName;
|
||||||
this.renderDestination = bind(this.renderDestination, this);
|
this.renderDestination = bind(this.renderDestination, this);
|
||||||
this.newBranch = form.find('.js-target-branch');
|
this.targetBranchDropdown = form.find('button.js-target-branch');
|
||||||
this.originalBranch = form.find('.js-original-branch');
|
this.originalBranch = form.find('.js-original-branch');
|
||||||
this.createMergeRequest = form.find('.js-create-merge-request');
|
this.createMergeRequest = form.find('.js-create-merge-request');
|
||||||
this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
|
this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
|
||||||
|
this.targetBranchDropdown.on('change.branch', this.renderDestination);
|
||||||
this.renderDestination();
|
this.renderDestination();
|
||||||
this.newBranch.keyup(this.renderDestination);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NewCommitForm.prototype.renderDestination = function() {
|
NewCommitForm.prototype.renderDestination = function() {
|
||||||
var different;
|
var different;
|
||||||
different = this.newBranch.val() !== this.originalBranch.val();
|
var targetBranch = this.form.find(`input[name="${this.targetBranchName}"]`);
|
||||||
|
|
||||||
|
different = targetBranch.val() !== this.originalBranch.val();
|
||||||
if (different) {
|
if (different) {
|
||||||
this.createMergeRequestContainer.show();
|
this.createMergeRequestContainer.show();
|
||||||
if (!this.wasDifferent) {
|
if (!this.wasDifferent) {
|
||||||
|
|
|
@ -795,7 +795,8 @@ pre.light-well {
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-refs-form .dropdown-menu,
|
.project-refs-form .dropdown-menu,
|
||||||
.dropdown-menu-projects {
|
.dropdown-menu-projects,
|
||||||
|
.dropdown-menu-branches {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
|
||||||
@media (min-width: $screen-sm-min) {
|
@media (min-width: $screen-sm-min) {
|
||||||
|
|
|
@ -23,6 +23,8 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
update_ref
|
||||||
|
|
||||||
create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
|
create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
|
||||||
success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) },
|
success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) },
|
||||||
failure_view: :new,
|
failure_view: :new,
|
||||||
|
@ -87,6 +89,11 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def update_ref
|
||||||
|
branch_exists = @repository.find_branch(@target_branch)
|
||||||
|
@ref = @target_branch if branch_exists
|
||||||
|
end
|
||||||
|
|
||||||
def blob
|
def blob
|
||||||
@blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path))
|
@blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path))
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,16 @@ class Projects::BranchesController < Projects::ApplicationController
|
||||||
def index
|
def index
|
||||||
@sort = params[:sort].presence || sort_value_name
|
@sort = params[:sort].presence || sort_value_name
|
||||||
@branches = BranchesFinder.new(@repository, params).execute
|
@branches = BranchesFinder.new(@repository, params).execute
|
||||||
@branches = Kaminari.paginate_array(@branches).page(params[:page])
|
|
||||||
|
|
||||||
@max_commits = @branches.reduce(0) do |memo, branch|
|
@branches = Kaminari.paginate_array(@branches).page(params[:page]) unless params[:show_all].present?
|
||||||
diverging_commit_counts = repository.diverging_commit_counts(branch)
|
|
||||||
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
|
|
||||||
end
|
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html do
|
||||||
|
@max_commits = @branches.reduce(0) do |memo, branch|
|
||||||
|
diverging_commit_counts = repository.diverging_commit_counts(branch)
|
||||||
|
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
|
||||||
|
end
|
||||||
|
end
|
||||||
format.json do
|
format.json do
|
||||||
render json: @branches.map(&:name)
|
render json: @branches.map(&:name)
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,4 +37,4 @@
|
||||||
= commit_in_fork_help
|
= commit_in_fork_help
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
new NewCommitForm($('.js-#{type}-form'))
|
new NewCommitForm($('.js-#{type}-form'), 'start_branch')
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
- dropdown_toggle_text = @target_branch || tree_edit_branch
|
||||||
|
= hidden_field_tag 'target_branch', dropdown_toggle_text
|
||||||
|
|
||||||
|
.dropdown
|
||||||
|
= dropdown_toggle dropdown_toggle_text, { toggle: 'dropdown', selected: dropdown_toggle_text, field_name: 'target_branch', form_id: '.js-edit-blob-form', refs_url: namespace_project_branches_path(@project.namespace, @project) }, { toggle_class: 'js-project-branches-dropdown js-target-branch' }
|
||||||
|
.dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging.dropdown-menu-branches
|
||||||
|
= render partial: 'shared/projects/blob/branch_page_default'
|
||||||
|
= render partial: 'shared/projects/blob/branch_page_create'
|
|
@ -7,7 +7,7 @@
|
||||||
.form-group.branch
|
.form-group.branch
|
||||||
= label_tag 'target_branch', 'Target branch', class: 'control-label'
|
= label_tag 'target_branch', 'Target branch', class: 'control-label'
|
||||||
.col-sm-10
|
.col-sm-10
|
||||||
= text_field_tag 'target_branch', @target_branch || tree_edit_branch, required: true, class: "form-control js-target-branch"
|
= render 'shared/branch_switcher'
|
||||||
|
|
||||||
.js-create-merge-request-container
|
.js-create-merge-request-container
|
||||||
.checkbox
|
.checkbox
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
.dropdown-page-two.dropdown-new-branch
|
||||||
|
= dropdown_title('Create new branch', back: true)
|
||||||
|
= dropdown_content do
|
||||||
|
%input#new_branch_name.default-dropdown-input.append-bottom-10{ type: "text", placeholder: "Name new branch" }
|
||||||
|
%button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" }
|
||||||
|
Create
|
||||||
|
%button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" }
|
||||||
|
Cancel
|
|
@ -0,0 +1,10 @@
|
||||||
|
.dropdown-page-one
|
||||||
|
= dropdown_title "Select branch"
|
||||||
|
= dropdown_filter "Search branches"
|
||||||
|
= dropdown_content
|
||||||
|
= dropdown_loading
|
||||||
|
= dropdown_footer do
|
||||||
|
%ul.dropdown-footer-list
|
||||||
|
%li
|
||||||
|
%a.create-new-branch.dropdown-toggle-page{ href: "#" }
|
||||||
|
Create new branch
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: New file from interface on existing branch
|
||||||
|
merge_request: 8427
|
||||||
|
author: Jacopo Beschi @jacopo-beschi
|
|
@ -82,7 +82,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I fill the new branch name' do
|
step 'I fill the new branch name' do
|
||||||
fill_in :target_branch, with: 'new_branch_name', visible: true
|
first('button.js-target-branch', visible: true).click
|
||||||
|
first('.create-new-branch', visible: true).click
|
||||||
|
first('#new_branch_name', visible: true).set('new_branch_name')
|
||||||
|
first('.js-new-branch-btn', visible: true).click
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I fill the new file name with an illegal name' do
|
step 'I fill the new file name with an illegal name' do
|
||||||
|
|
|
@ -266,5 +266,19 @@ describe Projects::BranchesController do
|
||||||
expect(parsed_response.first).to eq 'master'
|
expect(parsed_response.first).to eq 'master'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'show_all = true' do
|
||||||
|
it 'returns all the branches name' do
|
||||||
|
get :index,
|
||||||
|
namespace_id: project.namespace,
|
||||||
|
project_id: project,
|
||||||
|
format: :json,
|
||||||
|
show_all: true
|
||||||
|
|
||||||
|
parsed_response = JSON.parse(response.body)
|
||||||
|
|
||||||
|
expect(parsed_response.length).to eq(project.repository.branches.count)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'New blob creation', feature: true, js: true do
|
||||||
|
include WaitForAjax
|
||||||
|
|
||||||
|
given(:user) { create(:user) }
|
||||||
|
given(:role) { :developer }
|
||||||
|
given(:project) { create(:project) }
|
||||||
|
given(:content) { 'class NextFeature\nend\n' }
|
||||||
|
|
||||||
|
background do
|
||||||
|
login_as(user)
|
||||||
|
project.team << [user, role]
|
||||||
|
visit namespace_project_new_blob_path(project.namespace, project, 'master')
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit_file
|
||||||
|
wait_for_ajax
|
||||||
|
fill_in 'file_name', with: 'feature.rb'
|
||||||
|
execute_script("ace.edit('editor').setValue('#{content}')")
|
||||||
|
end
|
||||||
|
|
||||||
|
def select_branch_index(index)
|
||||||
|
first('button.js-target-branch').click
|
||||||
|
wait_for_ajax
|
||||||
|
all('a[data-group="Branches"]')[index].click
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_new_branch(name)
|
||||||
|
first('button.js-target-branch').click
|
||||||
|
click_link 'Create new branch'
|
||||||
|
fill_in 'new_branch_name', with: name
|
||||||
|
click_button 'Create'
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_file
|
||||||
|
click_button 'Commit Changes'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with default target branch' do
|
||||||
|
background do
|
||||||
|
edit_file
|
||||||
|
commit_file
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'creates the blob in the default branch' do
|
||||||
|
expect(page).to have_content 'master'
|
||||||
|
expect(page).to have_content 'successfully created'
|
||||||
|
expect(page).to have_content 'NextFeature'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with different target branch' do
|
||||||
|
background do
|
||||||
|
edit_file
|
||||||
|
select_branch_index(0)
|
||||||
|
commit_file
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'creates the blob in the different branch' do
|
||||||
|
expect(page).to have_content 'test'
|
||||||
|
expect(page).to have_content 'successfully created'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a new target branch' do
|
||||||
|
given(:new_branch_name) { 'new-feature' }
|
||||||
|
|
||||||
|
background do
|
||||||
|
edit_file
|
||||||
|
create_new_branch(new_branch_name)
|
||||||
|
commit_file
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'creates the blob in the new branch' do
|
||||||
|
expect(page).to have_content new_branch_name
|
||||||
|
expect(page).to have_content 'successfully created'
|
||||||
|
end
|
||||||
|
scenario 'returns you to the mr' do
|
||||||
|
expect(page).to have_content 'New Merge Request'
|
||||||
|
expect(page).to have_content "From #{new_branch_name} into master"
|
||||||
|
expect(page).to have_content 'Add new file'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the file already exist in the source branch' do
|
||||||
|
background do
|
||||||
|
Files::CreateService.new(
|
||||||
|
project,
|
||||||
|
user,
|
||||||
|
start_branch: 'master',
|
||||||
|
target_branch: 'master',
|
||||||
|
commit_message: 'Create file',
|
||||||
|
file_path: 'feature.rb',
|
||||||
|
file_content: content
|
||||||
|
).execute
|
||||||
|
edit_file
|
||||||
|
commit_file
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'shows error message' do
|
||||||
|
expect(page).to have_content('Your changes could not be committed because a file with the same name already exists')
|
||||||
|
expect(page).to have_content('New File')
|
||||||
|
expect(page).to have_content('NextFeature')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,109 @@
|
||||||
|
require('jquery');
|
||||||
|
require('~/extensions/jquery.js');
|
||||||
|
require('~/gl_dropdown');
|
||||||
|
require('~/lib/utils/type_utility');
|
||||||
|
require('~/blob/create_branch_dropdown');
|
||||||
|
require('~/blob/target_branch_dropdown');
|
||||||
|
|
||||||
|
describe('CreateBranchDropdown', () => {
|
||||||
|
const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
|
||||||
|
// selectors
|
||||||
|
const createBranchSel = '.js-new-branch-btn';
|
||||||
|
const backBtnSel = '.dropdown-menu-back';
|
||||||
|
const cancelBtnSel = '.js-cancel-branch-btn';
|
||||||
|
const branchNameSel = '#new_branch_name';
|
||||||
|
const branchName = 'new_name';
|
||||||
|
let dropdown;
|
||||||
|
|
||||||
|
function createDropdown() {
|
||||||
|
const dropdownEl = document.querySelector('.js-project-branches-dropdown');
|
||||||
|
const projectBranches = getJSONFixture('project_branches.json');
|
||||||
|
dropdown = new gl.TargetBranchDropDown(dropdownEl);
|
||||||
|
dropdown.cachedRefs = projectBranches;
|
||||||
|
return dropdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBranchBtn() {
|
||||||
|
return document.querySelector(createBranchSel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function backBtn() {
|
||||||
|
return document.querySelector(backBtnSel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelBtn() {
|
||||||
|
return document.querySelector(cancelBtnSel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function branchNameEl() {
|
||||||
|
return document.querySelector(branchNameSel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeBranchName(text) {
|
||||||
|
branchNameEl().value = text;
|
||||||
|
branchNameEl().dispatchEvent(new Event('change'));
|
||||||
|
}
|
||||||
|
|
||||||
|
preloadFixtures(fixtureTemplate);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
loadFixtures(fixtureTemplate);
|
||||||
|
createDropdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disable submit when branch name is empty', () => {
|
||||||
|
expect(createBranchBtn()).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enable submit when branch name is present', () => {
|
||||||
|
changeBranchName(branchName);
|
||||||
|
|
||||||
|
expect(createBranchBtn()).not.toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets the form when cancel btn is clicked and triggers dropdownback', () => {
|
||||||
|
const spyBackEvent = spyOnEvent(backBtnSel, 'click');
|
||||||
|
changeBranchName(branchName);
|
||||||
|
|
||||||
|
cancelBtn().click();
|
||||||
|
|
||||||
|
expect(branchNameEl()).toHaveValue('');
|
||||||
|
expect(spyBackEvent).toHaveBeenTriggered();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets the form when back btn is clicked', () => {
|
||||||
|
changeBranchName(branchName);
|
||||||
|
|
||||||
|
backBtn().click();
|
||||||
|
|
||||||
|
expect(branchNameEl()).toHaveValue('');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('new branch creation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
changeBranchName(branchName);
|
||||||
|
});
|
||||||
|
it('sets the new branch name and updates the dropdown', () => {
|
||||||
|
spyOn(dropdown, 'setNewBranch');
|
||||||
|
|
||||||
|
createBranchBtn().click();
|
||||||
|
|
||||||
|
expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets the form', () => {
|
||||||
|
createBranchBtn().click();
|
||||||
|
|
||||||
|
expect(branchNameEl()).toHaveValue('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is triggered with enter keypress', () => {
|
||||||
|
spyOn(dropdown, 'setNewBranch');
|
||||||
|
const enterEvent = new Event('keydown');
|
||||||
|
enterEvent.which = 13;
|
||||||
|
branchNameEl().dispatchEvent(enterEvent);
|
||||||
|
|
||||||
|
expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,121 @@
|
||||||
|
require('jquery');
|
||||||
|
require('~/extensions/jquery.js');
|
||||||
|
require('~/gl_dropdown');
|
||||||
|
require('~/lib/utils/type_utility');
|
||||||
|
require('~/blob/create_branch_dropdown');
|
||||||
|
require('~/blob/target_branch_dropdown');
|
||||||
|
|
||||||
|
describe('TargetBranchDropdown', () => {
|
||||||
|
const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
|
||||||
|
let dropdown;
|
||||||
|
|
||||||
|
function createDropdown() {
|
||||||
|
const projectBranches = getJSONFixture('project_branches.json');
|
||||||
|
const dropdownEl = document.querySelector('.js-project-branches-dropdown');
|
||||||
|
dropdown = new gl.TargetBranchDropDown(dropdownEl);
|
||||||
|
dropdown.cachedRefs = projectBranches;
|
||||||
|
dropdown.refreshData();
|
||||||
|
return dropdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitBtn() {
|
||||||
|
return document.querySelector('button[type="submit"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchField() {
|
||||||
|
return document.querySelector('.dropdown-page-one .dropdown-input-field');
|
||||||
|
}
|
||||||
|
|
||||||
|
function element() {
|
||||||
|
return document.querySelectorAll('div.dropdown-content li a');
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementAtIndex(index) {
|
||||||
|
return element()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickElementAtIndex(index) {
|
||||||
|
elementAtIndex(index).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
preloadFixtures(fixtureTemplate);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
loadFixtures(fixtureTemplate);
|
||||||
|
createDropdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disable submit when branch is not selected', () => {
|
||||||
|
document.querySelector('input[name="target_branch"]').value = null;
|
||||||
|
clickElementAtIndex(1);
|
||||||
|
|
||||||
|
expect(submitBtn().getAttribute('disabled')).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enable submit when a branch is selected', () => {
|
||||||
|
clickElementAtIndex(1);
|
||||||
|
|
||||||
|
expect(submitBtn().getAttribute('disabled')).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('triggers change.branch event on a branch click', () => {
|
||||||
|
spyOnEvent(dropdown.$dropdown, 'change.branch');
|
||||||
|
clickElementAtIndex(0);
|
||||||
|
|
||||||
|
expect('change.branch').toHaveBeenTriggeredOn(dropdown.$dropdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#dropdownData', () => {
|
||||||
|
it('cache the refs', () => {
|
||||||
|
const refs = dropdown.cachedRefs;
|
||||||
|
dropdown.cachedRefs = null;
|
||||||
|
|
||||||
|
dropdown.dropdownData(refs);
|
||||||
|
|
||||||
|
expect(dropdown.cachedRefs).toEqual(refs);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the Branches with the newBranch and defaultBranch', () => {
|
||||||
|
const refs = dropdown.cachedRefs;
|
||||||
|
dropdown.branchInput.value = 'master';
|
||||||
|
dropdown.newBranch = { id: 'new_branch', text: 'new_branch', title: 'new_branch' };
|
||||||
|
|
||||||
|
const branches = dropdown.dropdownData(refs).Branches;
|
||||||
|
|
||||||
|
expect(branches.length).toEqual(4);
|
||||||
|
expect(branches[0]).toEqual(dropdown.newBranch);
|
||||||
|
expect(branches[1]).toEqual({ id: 'master', text: 'master', title: 'master' });
|
||||||
|
expect(branches[2]).toEqual({ id: 'development', text: 'development', title: 'development' });
|
||||||
|
expect(branches[3]).toEqual({ id: 'staging', text: 'staging', title: 'staging' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#setNewBranch', () => {
|
||||||
|
it('adds the new branch and select it', () => {
|
||||||
|
const branchName = 'new_branch';
|
||||||
|
|
||||||
|
dropdown.setNewBranch(branchName);
|
||||||
|
|
||||||
|
expect(elementAtIndex(0)).toHaveClass('is-active');
|
||||||
|
expect(elementAtIndex(0)).toContainHtml(branchName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't add a new branch if already exists in the list", () => {
|
||||||
|
const branchName = elementAtIndex(0).text;
|
||||||
|
const initialLength = element().length;
|
||||||
|
|
||||||
|
dropdown.setNewBranch(branchName);
|
||||||
|
|
||||||
|
expect(element().length).toEqual(initialLength);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears the search filter', () => {
|
||||||
|
const branchName = elementAtIndex(0).text;
|
||||||
|
searchField().value = 'searching';
|
||||||
|
|
||||||
|
dropdown.setNewBranch(branchName);
|
||||||
|
|
||||||
|
expect(searchField().value).toEqual('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
[
|
||||||
|
"master",
|
||||||
|
"development",
|
||||||
|
"staging"
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
%form.js-edit-blob-form
|
||||||
|
%input{type: 'hidden', name: 'target_branch', value: 'master'}
|
||||||
|
%div
|
||||||
|
.dropdown
|
||||||
|
%button.dropdown-menu-toggle.js-project-branches-dropdown.js-target-branch{type: 'button', data: {toggle: 'dropdown', selected: 'master', field_name: 'target_branch', form_id: '.js-edit-blob-form'}}
|
||||||
|
.dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging
|
||||||
|
.dropdown-page-one
|
||||||
|
.dropdown-title 'Select branch'
|
||||||
|
.dropdown-input
|
||||||
|
%input.dropdown-input-field{type: 'search', value: ''}
|
||||||
|
%i.fa.fa-search.dropdown-input-search
|
||||||
|
%i.fa.fa-times-dropdown-input-clear.js-dropdown-input-clear{role: 'button'}
|
||||||
|
.dropdown-content
|
||||||
|
.dropdown-footer
|
||||||
|
%ul.dropdown-footer-list
|
||||||
|
%li
|
||||||
|
%a.create-new-branch.dropdown-toggle-page{href: "#"}
|
||||||
|
Create new branch
|
||||||
|
.dropdown-page-two.dropdown-new-branch
|
||||||
|
%button.dropdown-title-button.dropdown-menu-back{type: 'button'}
|
||||||
|
.dropdown_title 'Create new branch'
|
||||||
|
.dropdown_content
|
||||||
|
%input#new_branch_name.default-dropdown-input{ type: "text", placeholder: "Name new branch" }
|
||||||
|
%button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" }
|
||||||
|
Create
|
||||||
|
%button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" }
|
||||||
|
Cancel
|
||||||
|
%button{type: 'submit'}
|
Loading…
Reference in New Issue