Merge branch 'master' into sh-headless-chrome-support
* master: (96 commits) Fetch the merged branches at once Merging EE doc into CE Avoid using Rugged in Gitlab::Git::Wiki#preview_slug Cache commits on the repository model Remove groups_select from global namespace & simplifies the code Change default disabled merge request widget message to "Merge is not allowed yet" Semi-linear history merge is now available in CE. Remove repetitive karma spec Improve spec to check hidden component Rename to shouldShowUsername Add KubernetesService#default_namespace tests Revert "Merge branch '36670-remove-edit-form' into 'master'" Fix bitbucket login Remove duped tests Add path attribute to WikiFile class Make local_branches OPT_OUT Clarify the language around External Group membership with SAML SSO to clarify that this will NOT add users to GitLab Groups. Added ssh fingerprint, gitlab ci and pages information in an instance configuration page Fix the incorrect value being used to set GL_USERNAME on hooks Resolve "Remove overzealous tooltips in projects page tabs" ...
This commit is contained in:
commit
6a92dff2bc
|
@ -100,7 +100,7 @@ entry.
|
|||
- [CHANGED] Added defaults for protected branches dropdowns on the repository settings. !14278
|
||||
- [CHANGED] Show confirmation modal before deleting account. !14360
|
||||
- [CHANGED] Allow creating merge requests across a fork network. !14422
|
||||
- [CHANGED] Re-arrange <script> tags before <template> tags in .vue files. !14671
|
||||
- [CHANGED] Re-arrange script HTML tags before template HTML tags in .vue files. !14671
|
||||
- [CHANGED] Create idea of read-only database. !14688
|
||||
- [CHANGED] Add active states to nav bar counters.
|
||||
- [CHANGED] Add view replaced file link for image diffs.
|
||||
|
|
|
@ -21,10 +21,10 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
|
|||
- [Workflow labels](#workflow-labels)
|
||||
- [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc)
|
||||
- [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc)
|
||||
- [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-ci-discussion-edge-platform-etc)
|
||||
- [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-cicd-discussion-edge-platform-etc)
|
||||
- [Priority labels (~Deliverable and ~Stretch)](#priority-labels-deliverable-and-stretch)
|
||||
- [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
|
||||
- [Implement design & UI elements](#implement-design--ui-elements)
|
||||
- [Implement design & UI elements](#implement-design-ui-elements)
|
||||
- [Issue tracker](#issue-tracker)
|
||||
- [Issue triaging](#issue-triaging)
|
||||
- [Feature proposals](#feature-proposals)
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -398,7 +398,7 @@ group :ed25519 do
|
|||
end
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.45.0', require: 'gitaly'
|
||||
gem 'gitaly-proto', '~> 0.48.0', require: 'gitaly'
|
||||
|
||||
gem 'toml-rb', '~> 0.3.15', require: false
|
||||
|
||||
|
|
|
@ -274,7 +274,7 @@ GEM
|
|||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
gitaly-proto (0.45.0)
|
||||
gitaly-proto (0.48.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.0)
|
||||
github-linguist (4.7.6)
|
||||
|
@ -1026,7 +1026,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.2.0)
|
||||
gitaly-proto (~> 0.45.0)
|
||||
gitaly-proto (~> 0.48.0)
|
||||
github-linguist (~> 4.7.0)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-markup (~> 1.6.2)
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
viewType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'child',
|
||||
},
|
||||
},
|
||||
mixins: [
|
||||
pipelinesMixin,
|
||||
|
@ -110,6 +115,7 @@
|
|||
:pipelines="state.pipelines"
|
||||
:update-graph-dropdown="updateGraphDropdown"
|
||||
:auto-devops-help-path="autoDevopsHelpPath"
|
||||
:view-type="viewType"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@ import Cookies from 'js-cookie';
|
|||
import _ from 'underscore';
|
||||
import bp from './breakpoints';
|
||||
|
||||
export default class NewNavSidebar {
|
||||
export default class ContextualSidebar {
|
||||
constructor() {
|
||||
this.initDomElements();
|
||||
this.render();
|
||||
|
@ -55,7 +55,7 @@ export default class NewNavSidebar {
|
|||
this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
|
||||
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
|
||||
}
|
||||
NewNavSidebar.setCollapsedCookie(collapsed);
|
||||
ContextualSidebar.setCollapsedCookie(collapsed);
|
||||
|
||||
this.toggleSidebarOverflow();
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
import './lib/utils/url_utility';
|
||||
import FilesCommentButton from './files_comment_button';
|
||||
import SingleFileDiff from './single_file_diff';
|
||||
|
@ -8,7 +6,7 @@ import imageDiffHelper from './image_diff/helpers/index';
|
|||
const UNFOLD_COUNT = 20;
|
||||
let isBound = false;
|
||||
|
||||
class Diff {
|
||||
export default class Diff {
|
||||
constructor() {
|
||||
const $diffFile = $('.files .diff-file');
|
||||
|
||||
|
@ -104,7 +102,7 @@ class Diff {
|
|||
}
|
||||
this.highlightSelectedLine();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
handleParallelLineDown(e) {
|
||||
const line = $(e.currentTarget);
|
||||
const table = line.closest('table');
|
||||
|
@ -116,11 +114,11 @@ class Diff {
|
|||
table.addClass(`${lineClass}-selected`);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
diffViewType() {
|
||||
return $('.inline-parallel-buttons a.active').data('view-type');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
lineNumbers(line) {
|
||||
const children = line.find('.diff-line-num').toArray();
|
||||
if (children.length !== 2) {
|
||||
|
@ -128,7 +126,7 @@ class Diff {
|
|||
}
|
||||
return children.map(elm => parseInt($(elm).data('linenumber'), 10) || 0);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
highlightSelectedLine() {
|
||||
const hash = gl.utils.getLocationHash();
|
||||
const $diffFiles = $('.diff-file');
|
||||
|
@ -141,6 +139,3 @@ class Diff {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.Diff = Diff;
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
/* global NewBranchForm */
|
||||
/* global NotificationsForm */
|
||||
/* global NotificationsDropdown */
|
||||
/* global GroupAvatar */
|
||||
import groupAvatar from './group_avatar';
|
||||
import GroupLabelSubscription from './group_label_subscription';
|
||||
/* global LineHighlighter */
|
||||
import BuildArtifacts from './build_artifacts';
|
||||
import CILintEditor from './ci_lint_editor';
|
||||
/* global GroupsSelect */
|
||||
import groupsSelect from './groups_select';
|
||||
/* global Search */
|
||||
/* global Admin */
|
||||
/* global NamespaceSelects */
|
||||
|
@ -87,6 +88,7 @@ import U2FAuthenticate from './u2f/authenticate';
|
|||
import Members from './members';
|
||||
import memberExpirationDate from './member_expiration_date';
|
||||
import DueDateSelectors from './due_date_select';
|
||||
import Diff from './diff';
|
||||
|
||||
(function() {
|
||||
var Dispatcher;
|
||||
|
@ -237,8 +239,9 @@ import DueDateSelectors from './due_date_select';
|
|||
new GLForm($('.milestone-form'), true);
|
||||
break;
|
||||
case 'projects:compare:show':
|
||||
new gl.Diff();
|
||||
initChangesDropdown();
|
||||
new Diff();
|
||||
const paddingTop = 16;
|
||||
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
|
||||
break;
|
||||
case 'projects:branches:new':
|
||||
case 'projects:branches:create':
|
||||
|
@ -273,7 +276,7 @@ import DueDateSelectors from './due_date_select';
|
|||
}
|
||||
case 'projects:merge_requests:creations:diffs':
|
||||
case 'projects:merge_requests:edit':
|
||||
new gl.Diff();
|
||||
new Diff();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
new GLForm($('.merge-request-form'), true);
|
||||
new IssuableForm($('.merge-request-form'));
|
||||
|
@ -307,7 +310,7 @@ import DueDateSelectors from './due_date_select';
|
|||
new GLForm($('.release-form'), true);
|
||||
break;
|
||||
case 'projects:merge_requests:show':
|
||||
new gl.Diff();
|
||||
new Diff();
|
||||
shortcut_handler = new ShortcutsIssuable(true);
|
||||
new ZenMode();
|
||||
|
||||
|
@ -323,7 +326,7 @@ import DueDateSelectors from './due_date_select';
|
|||
new gl.Activities();
|
||||
break;
|
||||
case 'projects:commit:show':
|
||||
new gl.Diff();
|
||||
new Diff();
|
||||
new ZenMode();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
new MiniPipelineGraph({
|
||||
|
@ -411,7 +414,7 @@ import DueDateSelectors from './due_date_select';
|
|||
break;
|
||||
case 'projects:project_members:index':
|
||||
memberExpirationDate('.js-access-expiration-date-groups');
|
||||
new GroupsSelect();
|
||||
groupsSelect();
|
||||
memberExpirationDate();
|
||||
new Members();
|
||||
new UsersSelect();
|
||||
|
@ -422,11 +425,11 @@ import DueDateSelectors from './due_date_select';
|
|||
case 'admin:groups:create':
|
||||
BindInOut.initAll();
|
||||
new Group();
|
||||
new GroupAvatar();
|
||||
groupAvatar();
|
||||
break;
|
||||
case 'groups:edit':
|
||||
case 'admin:groups:edit':
|
||||
new GroupAvatar();
|
||||
groupAvatar();
|
||||
break;
|
||||
case 'projects:tree:show':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
|
@ -473,7 +476,7 @@ import DueDateSelectors from './due_date_select';
|
|||
const $el = $(el);
|
||||
|
||||
if ($el.find('.dropdown-group-label').length) {
|
||||
new gl.GroupLabelSubscription($el);
|
||||
new GroupLabelSubscription($el);
|
||||
} else {
|
||||
new gl.ProjectLabelSubscription($el);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
|
||||
/* global notes */
|
||||
|
||||
/* Developer beware! Do not add logic to showButton or hideButton
|
||||
* that will force a reflow. Doing so will create a signficant performance
|
||||
* bottleneck for pages with large diffs. For a comprehensive list of what
|
||||
|
@ -20,8 +17,10 @@ const DIFF_EXPANDED_CLASS = 'diff-expanded';
|
|||
|
||||
export default {
|
||||
init($diffFile) {
|
||||
/* Caching is used only when the following members are *true*. This is because there are likely to be
|
||||
* differently configured versions of diffs in the same session. However if these values are true, they
|
||||
/* Caching is used only when the following members are *true*.
|
||||
* This is because there are likely to be
|
||||
* differently configured versions of diffs in the same session.
|
||||
* However if these values are true, they
|
||||
* will be true in all cases */
|
||||
|
||||
if (!this.userCanCreateNote) {
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */
|
||||
|
||||
window.GroupAvatar = (function() {
|
||||
function GroupAvatar() {
|
||||
$('.js-choose-group-avatar-button').on("click", function() {
|
||||
var form;
|
||||
form = $(this).closest("form");
|
||||
return form.find(".js-group-avatar-input").click();
|
||||
export default function groupAvatar() {
|
||||
$('.js-choose-group-avatar-button').on('click', function onClickGroupAvatar() {
|
||||
const form = $(this).closest('form');
|
||||
return form.find('.js-group-avatar-input').click();
|
||||
});
|
||||
$('.js-group-avatar-input').on("change", function() {
|
||||
var filename, form;
|
||||
form = $(this).closest("form");
|
||||
filename = $(this).val().replace(/^.*[\\\/]/, '');
|
||||
return form.find(".js-avatar-filename").text(filename);
|
||||
$('.js-group-avatar-input').on('change', function onChangeAvatarInput() {
|
||||
const form = $(this).closest('form');
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const filename = $(this).val().replace(/^.*[\\\/]/, '');
|
||||
return form.find('.js-avatar-filename').text(filename);
|
||||
});
|
||||
}
|
||||
|
||||
return GroupAvatar;
|
||||
})();
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, max-len */
|
||||
|
||||
class GroupLabelSubscription {
|
||||
export default class GroupLabelSubscription {
|
||||
constructor(container) {
|
||||
const $container = $(container);
|
||||
this.$dropdown = $container.find('.dropdown');
|
||||
|
@ -18,7 +16,7 @@ class GroupLabelSubscription {
|
|||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url
|
||||
url,
|
||||
}).done(() => {
|
||||
this.toggleSubscriptionButtons();
|
||||
this.$unsubscribeButtons.removeAttr('data-url');
|
||||
|
@ -35,7 +33,7 @@ class GroupLabelSubscription {
|
|||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url
|
||||
url,
|
||||
}).done(() => {
|
||||
this.toggleSubscriptionButtons();
|
||||
});
|
||||
|
@ -47,6 +45,3 @@ class GroupLabelSubscription {
|
|||
this.$unsubscribeButtons.toggleClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.GroupLabelSubscription = GroupLabelSubscription;
|
||||
|
|
|
@ -1,34 +1,24 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var,
|
||||
camelcase, one-var-declaration-per-line, quotes, object-shorthand,
|
||||
prefer-arrow-callback, comma-dangle, consistent-return, yoda,
|
||||
prefer-rest-params, prefer-spread, no-unused-vars, prefer-template,
|
||||
promise/catch-or-return */
|
||||
import Api from './api';
|
||||
import { normalizeCRLFHeaders } from './lib/utils/common_utils';
|
||||
|
||||
var slice = [].slice;
|
||||
|
||||
window.GroupsSelect = (function() {
|
||||
function GroupsSelect() {
|
||||
$('.ajax-groups-select').each((function(_this) {
|
||||
const self = _this;
|
||||
|
||||
return function(i, select) {
|
||||
var all_available, skip_groups;
|
||||
const $select = $(select);
|
||||
all_available = $select.data('all-available');
|
||||
skip_groups = $select.data('skip-groups') || [];
|
||||
|
||||
export default function groupsSelect() {
|
||||
// Needs to be accessible in rspec
|
||||
window.GROUP_SELECT_PER_PAGE = 20;
|
||||
$('.ajax-groups-select').each(function setAjaxGroupsSelect2() {
|
||||
const $select = $(this);
|
||||
const allAvailable = $select.data('all-available');
|
||||
const skipGroups = $select.data('skip-groups') || [];
|
||||
$select.select2({
|
||||
placeholder: "Search for a group",
|
||||
placeholder: 'Search for a group',
|
||||
multiple: $select.hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
url: Api.buildUrl(Api.groupsPath),
|
||||
dataType: 'json',
|
||||
quietMillis: 250,
|
||||
transport: function (params) {
|
||||
$.ajax(params).then((data, status, xhr) => {
|
||||
transport(params) {
|
||||
return $.ajax(params)
|
||||
.then((data, status, xhr) => {
|
||||
const results = data || [];
|
||||
|
||||
const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders());
|
||||
|
@ -42,22 +32,24 @@ window.GroupsSelect = (function() {
|
|||
more,
|
||||
},
|
||||
};
|
||||
}).then(params.success).fail(params.error);
|
||||
})
|
||||
.then(params.success)
|
||||
.fail(params.error);
|
||||
},
|
||||
data: function (search, page) {
|
||||
data(search, page) {
|
||||
return {
|
||||
search,
|
||||
page,
|
||||
per_page: GroupsSelect.PER_PAGE,
|
||||
all_available,
|
||||
per_page: window.GROUP_SELECT_PER_PAGE,
|
||||
all_available: allAvailable,
|
||||
};
|
||||
},
|
||||
results: function (data, page) {
|
||||
results(data, page) {
|
||||
if (data.length) return { results: [] };
|
||||
|
||||
const groups = data.length ? data : data.results || [];
|
||||
const more = data.pagination ? data.pagination.more : false;
|
||||
const results = groups.filter(group => skip_groups.indexOf(group.id) === -1);
|
||||
const results = groups.filter(group => skipGroups.indexOf(group.id) === -1);
|
||||
|
||||
return {
|
||||
results,
|
||||
|
@ -66,56 +58,29 @@ window.GroupsSelect = (function() {
|
|||
};
|
||||
},
|
||||
},
|
||||
initSelection: function(element, callback) {
|
||||
var id;
|
||||
id = $(element).val();
|
||||
if (id !== "") {
|
||||
// eslint-disable-next-line consistent-return
|
||||
initSelection(element, callback) {
|
||||
const id = $(element).val();
|
||||
if (id !== '') {
|
||||
return Api.group(id, callback);
|
||||
}
|
||||
},
|
||||
formatResult: function() {
|
||||
var args;
|
||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||
return self.formatResult.apply(self, args);
|
||||
formatResult(object) {
|
||||
return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`;
|
||||
},
|
||||
formatSelection: function() {
|
||||
var args;
|
||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||
return self.formatSelection.apply(self, args);
|
||||
formatSelection(object) {
|
||||
return object.full_name;
|
||||
},
|
||||
dropdownCssClass: "ajax-groups-dropdown select2-infinite",
|
||||
dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
escapeMarkup: function(m) {
|
||||
escapeMarkup(m) {
|
||||
return m;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
self.dropdown = document.querySelector('.select2-infinite .select2-results');
|
||||
|
||||
$select.on('select2-loaded', self.forceOverflow.bind(self));
|
||||
};
|
||||
})(this));
|
||||
$select.on('select2-loaded', () => {
|
||||
const dropdown = document.querySelector('.select2-infinite .select2-results');
|
||||
dropdown.style.height = `${Math.floor(dropdown.scrollHeight)}px`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
GroupsSelect.prototype.formatResult = function(group) {
|
||||
var avatar;
|
||||
if (group.avatar_url) {
|
||||
avatar = group.avatar_url;
|
||||
} else {
|
||||
avatar = gon.default_avatar_url;
|
||||
}
|
||||
return "<div class='group-result'> <div class='group-name'>" + group.full_name + "</div> <div class='group-path'>" + group.full_path + "</div> </div>";
|
||||
};
|
||||
|
||||
GroupsSelect.prototype.formatSelection = function(group) {
|
||||
return group.full_name;
|
||||
};
|
||||
|
||||
GroupsSelect.prototype.forceOverflow = function (e) {
|
||||
this.dropdown.style.height = `${Math.floor(this.dropdown.scrollHeight)}px`;
|
||||
};
|
||||
|
||||
GroupsSelect.PER_PAGE = 20;
|
||||
|
||||
return GroupsSelect;
|
||||
})();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import stickyMonitor from './lib/utils/sticky';
|
||||
|
||||
export default () => {
|
||||
stickyMonitor(document.querySelector('.js-diff-files-changed'));
|
||||
export default (stickyTop) => {
|
||||
stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop);
|
||||
|
||||
$('.js-diff-stats-dropdown').glDropdown({
|
||||
filterable: true,
|
||||
|
|
|
@ -51,20 +51,19 @@ const PARTICIPANTS_ROW_COUNT = 7;
|
|||
}
|
||||
|
||||
IssuableContext.prototype.initParticipants = function() {
|
||||
$(document).on("click", ".js-participants-more", this.toggleHiddenParticipants);
|
||||
return $(".js-participants-author").each(function(i) {
|
||||
$(document).on('click', '.js-participants-more', this.toggleHiddenParticipants);
|
||||
return $('.js-participants-author').each(function(i) {
|
||||
if (i >= PARTICIPANTS_ROW_COUNT) {
|
||||
return $(this).addClass("js-participants-hidden").hide();
|
||||
return $(this).addClass('js-participants-hidden').hide();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
IssuableContext.prototype.toggleHiddenParticipants = function(e) {
|
||||
var currentText, lessText, originalText;
|
||||
e.preventDefault();
|
||||
currentText = $(this).text().trim();
|
||||
lessText = $(this).data("less-text");
|
||||
originalText = $(this).data("original-text");
|
||||
IssuableContext.prototype.toggleHiddenParticipants = function() {
|
||||
const currentText = $(this).text().trim();
|
||||
const lessText = $(this).data('less-text');
|
||||
const originalText = $(this).data('original-text');
|
||||
|
||||
if (currentText === originalText) {
|
||||
$(this).text(lessText);
|
||||
|
||||
|
@ -73,7 +72,7 @@ const PARTICIPANTS_ROW_COUNT = 7;
|
|||
$(this).text(originalText);
|
||||
}
|
||||
|
||||
$(".js-participants-hidden").toggle();
|
||||
$('.js-participants-hidden').toggle();
|
||||
};
|
||||
|
||||
return IssuableContext;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */
|
||||
import _ from 'underscore';
|
||||
import Cookies from 'js-cookie';
|
||||
import NewNavSidebar from './new_sidebar';
|
||||
import ContextualSidebar from './contextual_sidebar';
|
||||
import initFlyOutNav from './fly_out_nav';
|
||||
|
||||
(function() {
|
||||
|
@ -51,8 +51,8 @@ import initFlyOutNav from './fly_out_nav';
|
|||
});
|
||||
|
||||
$(() => {
|
||||
const newNavSidebar = new NewNavSidebar();
|
||||
newNavSidebar.bindEvents();
|
||||
const contextualSidebar = new ContextualSidebar();
|
||||
contextualSidebar.bindEvents();
|
||||
|
||||
initFlyOutNav();
|
||||
});
|
||||
|
|
|
@ -28,14 +28,10 @@ export const isSticky = (el, scrollY, stickyTop, insertPlaceholder) => {
|
|||
}
|
||||
};
|
||||
|
||||
export default (el, insertPlaceholder = true) => {
|
||||
export default (el, stickyTop, insertPlaceholder = true) => {
|
||||
if (!el) return;
|
||||
|
||||
const computedStyle = window.getComputedStyle(el);
|
||||
|
||||
if (!/sticky/.test(computedStyle.position)) return;
|
||||
|
||||
const stickyTop = parseInt(computedStyle.top, 10);
|
||||
if (typeof CSS === 'undefined' || !(CSS.supports('(position: -webkit-sticky) or (position: sticky)'))) return;
|
||||
|
||||
document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop, insertPlaceholder), {
|
||||
passive: true,
|
||||
|
|
|
@ -50,16 +50,11 @@ import './compare_autocomplete';
|
|||
import './confirm_danger_modal';
|
||||
import './copy_as_gfm';
|
||||
import './copy_to_clipboard';
|
||||
import './diff';
|
||||
import './files_comment_button';
|
||||
import Flash, { removeFlashClickListener } from './flash';
|
||||
import './gl_dropdown';
|
||||
import './gl_field_error';
|
||||
import './gl_field_errors';
|
||||
import './gl_form';
|
||||
import './group_avatar';
|
||||
import './group_label_subscription';
|
||||
import './groups_select';
|
||||
import './header';
|
||||
import './importer_status';
|
||||
import './issuable_index';
|
||||
|
|
|
@ -11,8 +11,8 @@ import {
|
|||
handleLocationHash,
|
||||
isMetaClick,
|
||||
} from './lib/utils/common_utils';
|
||||
|
||||
import initDiscussionTab from './image_diff/init_discussion_tab';
|
||||
import Diff from './diff';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
// MergeRequestTabs
|
||||
|
@ -67,6 +67,10 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
|
|||
class MergeRequestTabs {
|
||||
|
||||
constructor({ action, setUrl, stubLocation } = {}) {
|
||||
const mergeRequestTabs = document.querySelector('.js-tabs-affix');
|
||||
const navbar = document.querySelector('.navbar-gitlab');
|
||||
const paddingTop = 16;
|
||||
|
||||
this.diffsLoaded = false;
|
||||
this.pipelinesLoaded = false;
|
||||
this.commitsLoaded = false;
|
||||
|
@ -76,6 +80,11 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
|
|||
this.setCurrentAction = this.setCurrentAction.bind(this);
|
||||
this.tabShown = this.tabShown.bind(this);
|
||||
this.showTab = this.showTab.bind(this);
|
||||
this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
|
||||
|
||||
if (mergeRequestTabs) {
|
||||
this.stickyTop += mergeRequestTabs.offsetHeight;
|
||||
}
|
||||
|
||||
if (stubLocation) {
|
||||
location = stubLocation;
|
||||
|
@ -278,7 +287,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
|
|||
const $container = $('#diffs');
|
||||
$container.html(data.html);
|
||||
|
||||
initChangesDropdown();
|
||||
initChangesDropdown(this.stickyTop);
|
||||
|
||||
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
|
||||
gl.diffNotesCompileComponents();
|
||||
|
@ -292,7 +301,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
|
|||
}
|
||||
this.diffsLoaded = true;
|
||||
|
||||
new gl.Diff();
|
||||
new Diff();
|
||||
this.scrollToElement('#diffs');
|
||||
|
||||
$('.diff-file').each((i, el) => {
|
||||
|
|
|
@ -1280,10 +1280,12 @@ export default class Notes {
|
|||
* Get data from Form attributes to use for saving/submitting comment.
|
||||
*/
|
||||
getFormData($form) {
|
||||
const content = $form.find('.js-note-text').val();
|
||||
return {
|
||||
formData: $form.serialize(),
|
||||
formContent: _.escape($form.find('.js-note-text').val()),
|
||||
formContent: _.escape(content),
|
||||
formAction: $form.attr('action'),
|
||||
formContentOriginal: content,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1415,7 +1417,7 @@ export default class Notes {
|
|||
const isMainForm = $form.hasClass('js-main-target-form');
|
||||
const isDiscussionForm = $form.hasClass('js-discussion-note-form');
|
||||
const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
|
||||
const { formData, formContent, formAction } = this.getFormData($form);
|
||||
const { formData, formContent, formAction, formContentOriginal } = this.getFormData($form);
|
||||
let noteUniqueId;
|
||||
let systemNoteUniqueId;
|
||||
let hasQuickActions = false;
|
||||
|
@ -1574,7 +1576,7 @@ export default class Notes {
|
|||
$form = $notesContainer.parent().find('form');
|
||||
}
|
||||
|
||||
$form.find('.js-note-text').val(formContent);
|
||||
$form.find('.js-note-text').val(formContentOriginal);
|
||||
this.reenableTargetFormSubmitButton(e);
|
||||
this.addNoteError($form);
|
||||
});
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
|
||||
import issueNoteEditedText from './issue_note_edited_text.vue';
|
||||
import issueNoteForm from './issue_note_form.vue';
|
||||
import placeholderNote from './issue_placeholder_note.vue';
|
||||
import placeholderSystemNote from './issue_placeholder_system_note.vue';
|
||||
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
|
||||
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
|
||||
import autosave from '../mixins/autosave';
|
||||
|
||||
export default {
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
import * as constants from '../constants';
|
||||
import issueNote from './issue_note.vue';
|
||||
import issueDiscussion from './issue_discussion.vue';
|
||||
import issueSystemNote from './issue_system_note.vue';
|
||||
import systemNote from '../../vue_shared/components/notes/system_note.vue';
|
||||
import issueCommentForm from './issue_comment_form.vue';
|
||||
import placeholderNote from './issue_placeholder_note.vue';
|
||||
import placeholderSystemNote from './issue_placeholder_system_note.vue';
|
||||
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
|
||||
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
|
||||
export default {
|
||||
|
@ -37,7 +37,7 @@
|
|||
components: {
|
||||
issueNote,
|
||||
issueDiscussion,
|
||||
issueSystemNote,
|
||||
systemNote,
|
||||
issueCommentForm,
|
||||
loadingIcon,
|
||||
placeholderNote,
|
||||
|
@ -68,7 +68,7 @@
|
|||
}
|
||||
return placeholderNote;
|
||||
} else if (note.individual_note) {
|
||||
return note.notes[0].system ? issueSystemNote : issueNote;
|
||||
return note.notes[0].system ? systemNote : issueNote;
|
||||
}
|
||||
|
||||
return issueDiscussion;
|
||||
|
|
|
@ -12,6 +12,15 @@
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
// Can be rendered in 3 different places, with some visual differences
|
||||
// Accepts root | child
|
||||
// `root` -> main view
|
||||
// `child` -> rendered inside MR or Commit View
|
||||
viewType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'root',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
tablePagination,
|
||||
|
@ -206,6 +215,7 @@
|
|||
:pipelines="state.pipelines"
|
||||
:update-graph-dropdown="updateGraphDropdown"
|
||||
:auto-devops-help-path="autoDevopsPath"
|
||||
:view-type="viewType"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -21,6 +21,10 @@
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
viewType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
pipelinesTableRowComponent,
|
||||
|
@ -59,6 +63,7 @@
|
|||
:pipeline="model"
|
||||
:update-graph-dropdown="updateGraphDropdown"
|
||||
:auto-devops-help-path="autoDevopsHelpPath"
|
||||
:view-type="viewType"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -29,6 +29,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
viewType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
asyncButtonComponent,
|
||||
|
@ -207,6 +211,10 @@ export default {
|
|||
this.pipeline.details.manual_actions.length ||
|
||||
this.pipeline.details.artifacts.length;
|
||||
},
|
||||
|
||||
isChildView() {
|
||||
return this.viewType === 'child';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -218,7 +226,10 @@ export default {
|
|||
Status
|
||||
</div>
|
||||
<div class="table-mobile-content">
|
||||
<ci-badge :status="pipelineStatus"/>
|
||||
<ci-badge
|
||||
:status="pipelineStatus"
|
||||
:show-text="!isChildView"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -240,7 +251,9 @@ export default {
|
|||
:commit-url="commitUrl"
|
||||
:short-sha="commitShortSha"
|
||||
:title="commitTitle"
|
||||
:author="commitAuthor"/>
|
||||
:author="commitAuthor"
|
||||
:show-branch="!isChildView"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
<script>
|
||||
import flash, { hideFlash } from '../../flash';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
props: {
|
||||
currentBranch: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
branchName: '',
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
btnDisabled() {
|
||||
return this.loading || this.branchName === '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleDropdown() {
|
||||
this.$dropdown.dropdown('toggle');
|
||||
},
|
||||
submitNewBranch() {
|
||||
// need to query as the element is appended outside of Vue
|
||||
const flashEl = this.$refs.flashContainer.querySelector('.flash-alert');
|
||||
|
||||
this.loading = true;
|
||||
|
||||
if (flashEl) {
|
||||
hideFlash(flashEl, false);
|
||||
}
|
||||
|
||||
eventHub.$emit('createNewBranch', this.branchName);
|
||||
},
|
||||
showErrorMessage(message) {
|
||||
this.loading = false;
|
||||
flash(message, 'alert', this.$el);
|
||||
},
|
||||
createdNewBranch(newBranchName) {
|
||||
this.loading = false;
|
||||
this.branchName = '';
|
||||
|
||||
if (this.dropdownText) {
|
||||
this.dropdownText.textContent = newBranchName;
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// Dropdown is outside of Vue instance & is controlled by Bootstrap
|
||||
this.$dropdown = $('.git-revision-dropdown');
|
||||
|
||||
// text element is outside Vue app
|
||||
this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
|
||||
|
||||
eventHub.$on('createNewBranchSuccess', this.createdNewBranch);
|
||||
eventHub.$on('createNewBranchError', this.showErrorMessage);
|
||||
eventHub.$on('toggleNewBranchDropdown', this.toggleDropdown);
|
||||
},
|
||||
destroyed() {
|
||||
eventHub.$off('createNewBranchSuccess', this.createdNewBranch);
|
||||
eventHub.$off('toggleNewBranchDropdown', this.toggleDropdown);
|
||||
eventHub.$off('createNewBranchError', this.showErrorMessage);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="flash-container"
|
||||
ref="flashContainer"
|
||||
>
|
||||
</div>
|
||||
<p>
|
||||
Create from:
|
||||
<code>{{ currentBranch }}</code>
|
||||
</p>
|
||||
<input
|
||||
class="form-control js-new-branch-name"
|
||||
type="text"
|
||||
placeholder="Name new branch"
|
||||
v-model="branchName"
|
||||
@keyup.enter.stop.prevent="submitNewBranch"
|
||||
/>
|
||||
<div class="prepend-top-default clearfix">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary pull-left"
|
||||
:disabled="btnDisabled"
|
||||
@click.stop.prevent="submitNewBranch"
|
||||
>
|
||||
<loading-icon
|
||||
v-if="loading"
|
||||
:inline="true"
|
||||
/>
|
||||
<span>Create</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default pull-right"
|
||||
@click.stop.prevent="toggleDropdown"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,86 @@
|
|||
<script>
|
||||
import RepoStore from '../../stores/repo_store';
|
||||
import RepoHelper from '../../helpers/repo_helper';
|
||||
import eventHub from '../../event_hub';
|
||||
import newModal from './modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
newModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openModal: false,
|
||||
modalType: '',
|
||||
currentPath: RepoStore.path,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
createNewItem(type) {
|
||||
this.modalType = type;
|
||||
this.toggleModalOpen();
|
||||
},
|
||||
toggleModalOpen() {
|
||||
this.openModal = !this.openModal;
|
||||
},
|
||||
createNewEntryInStore(name, type) {
|
||||
RepoHelper.createNewEntry(name, type);
|
||||
|
||||
this.toggleModalOpen();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
eventHub.$on('createNewEntry', this.createNewEntryInStore);
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('createNewEntry', this.createNewEntryInStore);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ul class="breadcrumb repo-breadcrumb">
|
||||
<li class="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default dropdown-toggle add-to-tree"
|
||||
data-toggle="dropdown"
|
||||
aria-label="Create new file or directory"
|
||||
>
|
||||
<i
|
||||
class="fa fa-plus"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="createNewItem('blob')"
|
||||
>
|
||||
{{ __('New file') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="createNewItem('tree')"
|
||||
>
|
||||
{{ __('New directory') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<new-modal
|
||||
v-if="openModal"
|
||||
:type="modalType"
|
||||
:current-path="currentPath"
|
||||
@toggle="toggleModalOpen"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,90 @@
|
|||
<script>
|
||||
import { __ } from '../../../locale';
|
||||
import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
|
||||
import eventHub from '../../event_hub';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
currentPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
entryName: this.currentPath !== '' ? `${this.currentPath}/` : '',
|
||||
};
|
||||
},
|
||||
components: {
|
||||
popupDialog,
|
||||
},
|
||||
methods: {
|
||||
createEntryInStore() {
|
||||
eventHub.$emit('createNewEntry', this.entryName, this.type);
|
||||
},
|
||||
toggleModalOpen() {
|
||||
this.$emit('toggle');
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
modalTitle() {
|
||||
if (this.type === 'tree') {
|
||||
return __('Create new directory');
|
||||
}
|
||||
|
||||
return __('Create new file');
|
||||
},
|
||||
buttonLabel() {
|
||||
if (this.type === 'tree') {
|
||||
return __('Create directory');
|
||||
}
|
||||
|
||||
return __('Create file');
|
||||
},
|
||||
formLabelName() {
|
||||
if (this.type === 'tree') {
|
||||
return __('Directory name');
|
||||
}
|
||||
|
||||
return __('File name');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.fieldName.focus();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<popup-dialog
|
||||
:title="modalTitle"
|
||||
:primary-button-label="buttonLabel"
|
||||
kind="success"
|
||||
@toggle="toggleModalOpen"
|
||||
@submit="createEntryInStore"
|
||||
>
|
||||
<form
|
||||
class="form-horizontal"
|
||||
slot="body"
|
||||
@submit.prevent="createEntryInStore"
|
||||
>
|
||||
<fieldset class="form-group append-bottom-0">
|
||||
<label class="label-light col-sm-3">
|
||||
{{ formLabelName }}
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
v-model="entryName"
|
||||
ref="fieldName"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</popup-dialog>
|
||||
</template>
|
|
@ -8,7 +8,9 @@ import RepoMixin from '../mixins/repo_mixin';
|
|||
import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
|
||||
import Store from '../stores/repo_store';
|
||||
import Helper from '../helpers/repo_helper';
|
||||
import Service from '../services/repo_service';
|
||||
import MonacoLoaderHelper from '../helpers/monaco_loader_helper';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -24,12 +26,19 @@ export default {
|
|||
PopupDialog,
|
||||
RepoPreview,
|
||||
},
|
||||
|
||||
created() {
|
||||
eventHub.$on('createNewBranch', this.createNewBranch);
|
||||
},
|
||||
mounted() {
|
||||
Helper.getContent().catch(Helper.loadingError);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
eventHub.$off('createNewBranch', this.createNewBranch);
|
||||
},
|
||||
methods: {
|
||||
getCurrentLocation() {
|
||||
return location.href;
|
||||
},
|
||||
toggleDialogOpen(toggle) {
|
||||
this.dialog.open = toggle;
|
||||
},
|
||||
|
@ -37,9 +46,30 @@ export default {
|
|||
dialogSubmitted(status) {
|
||||
this.toggleDialogOpen(false);
|
||||
this.dialog.status = status;
|
||||
},
|
||||
|
||||
// remove tmp files
|
||||
Helper.removeAllTmpFiles('openedFiles');
|
||||
Helper.removeAllTmpFiles('files');
|
||||
},
|
||||
toggleBlobView: Store.toggleBlobView,
|
||||
createNewBranch(branch) {
|
||||
Service.createBranch({
|
||||
branch,
|
||||
ref: Store.currentBranch,
|
||||
}).then((res) => {
|
||||
const newBranchName = res.data.name;
|
||||
const newUrl = this.getCurrentLocation().replace(Store.currentBranch, newBranchName);
|
||||
|
||||
Store.currentBranch = newBranchName;
|
||||
|
||||
history.pushState({ key: Helper.key }, '', newUrl);
|
||||
|
||||
eventHub.$emit('createNewBranchSuccess', newBranchName);
|
||||
eventHub.$emit('toggleNewBranchDropdown');
|
||||
}).catch((err) => {
|
||||
eventHub.$emit('createNewBranchError', err.response.data.message);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -49,7 +49,7 @@ export default {
|
|||
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
|
||||
const commitMessage = this.commitMessage;
|
||||
const actions = this.changedFiles.map(f => ({
|
||||
action: 'update',
|
||||
action: f.tempFile ? 'create' : 'update',
|
||||
file_path: f.path,
|
||||
content: f.newContent,
|
||||
}));
|
||||
|
@ -62,7 +62,6 @@ export default {
|
|||
if (newBranch) {
|
||||
payload.start_branch = this.currentBranch;
|
||||
}
|
||||
this.submitCommitsLoading = true;
|
||||
Service.commitFiles(payload)
|
||||
.then(() => {
|
||||
this.resetCommitState();
|
||||
|
@ -78,6 +77,8 @@ export default {
|
|||
},
|
||||
|
||||
tryCommit(e, skipBranchCheck = false, newBranch = false) {
|
||||
this.submitCommitsLoading = true;
|
||||
|
||||
if (skipBranchCheck) {
|
||||
this.makeCommit(newBranch);
|
||||
} else {
|
||||
|
@ -90,6 +91,7 @@ export default {
|
|||
this.makeCommit(newBranch);
|
||||
})
|
||||
.catch(() => {
|
||||
this.submitCommitsLoading = false;
|
||||
Flash('An error occurred while committing your changes');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ const RepoEditor = {
|
|||
},
|
||||
|
||||
mounted() {
|
||||
Service.getRaw(this.activeFile.raw_path)
|
||||
Service.getRaw(this.activeFile)
|
||||
.then((rawResponse) => {
|
||||
Store.blobRaw = rawResponse.data;
|
||||
Store.activeFile.plain = rawResponse.data;
|
||||
|
|
|
@ -11,7 +11,12 @@ const RepoFileButtons = {
|
|||
mixins: [RepoMixin],
|
||||
|
||||
computed: {
|
||||
|
||||
showButtons() {
|
||||
return this.activeFile.raw_path ||
|
||||
this.activeFile.blame_path ||
|
||||
this.activeFile.commits_path ||
|
||||
this.activeFile.permalink;
|
||||
},
|
||||
rawDownloadButtonLabel() {
|
||||
return this.binary ? 'Download' : 'Raw';
|
||||
},
|
||||
|
@ -30,7 +35,10 @@ export default RepoFileButtons;
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div id="repo-file-buttons">
|
||||
<div
|
||||
v-if="showButtons"
|
||||
class="repo-file-buttons"
|
||||
>
|
||||
<a
|
||||
:href="activeFile.raw_path"
|
||||
target="_blank"
|
||||
|
|
|
@ -18,8 +18,8 @@ const RepoTab = {
|
|||
},
|
||||
changedClass() {
|
||||
const tabChangedObj = {
|
||||
'fa-times close-icon': !this.tab.changed,
|
||||
'fa-circle unsaved-icon': this.tab.changed,
|
||||
'fa-times close-icon': !this.tab.changed && !this.tab.tempFile,
|
||||
'fa-circle unsaved-icon': this.tab.changed || this.tab.tempFile,
|
||||
};
|
||||
return tabChangedObj;
|
||||
},
|
||||
|
@ -30,7 +30,7 @@ const RepoTab = {
|
|||
Store.setActiveFiles(file);
|
||||
},
|
||||
closeTab(file) {
|
||||
if (file.changed) return;
|
||||
if (file.changed || file.tempFile) return;
|
||||
|
||||
Store.removeFromOpenedFiles(file);
|
||||
},
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
|
||||
import Service from '../services/repo_service';
|
||||
import Store from '../stores/repo_store';
|
||||
import Flash from '../../flash';
|
||||
|
@ -8,6 +7,7 @@ const RepoHelper = {
|
|||
|
||||
getDefaultActiveFile() {
|
||||
return {
|
||||
id: '',
|
||||
active: true,
|
||||
binary: false,
|
||||
extension: '',
|
||||
|
@ -62,6 +62,7 @@ const RepoHelper = {
|
|||
});
|
||||
|
||||
RepoHelper.updateHistoryEntry(tree.url, title);
|
||||
Store.path = tree.path;
|
||||
},
|
||||
|
||||
setDirectoryToClosed(entry) {
|
||||
|
@ -96,8 +97,8 @@ const RepoHelper = {
|
|||
.then((response) => {
|
||||
const data = response.data;
|
||||
if (response.headers && response.headers['page-title']) data.pageTitle = decodeURI(response.headers['page-title']);
|
||||
if (response.headers && response.headers['is-root'] && !Store.isInitialRoot) {
|
||||
Store.isRoot = convertPermissionToBoolean(response.headers['is-root']);
|
||||
if (data.path && !Store.isInitialRoot) {
|
||||
Store.isRoot = data.path === '/';
|
||||
Store.isInitialRoot = Store.isRoot;
|
||||
}
|
||||
|
||||
|
@ -110,7 +111,7 @@ const RepoHelper = {
|
|||
RepoHelper.setBinaryDataAsBase64(data);
|
||||
Store.setViewToPreview();
|
||||
} else if (!Store.isPreviewView() && !data.render_error) {
|
||||
Service.getRaw(data.raw_path)
|
||||
Service.getRaw(data)
|
||||
.then((rawResponse) => {
|
||||
Store.blobRaw = rawResponse.data;
|
||||
data.plain = rawResponse.data;
|
||||
|
@ -138,6 +139,10 @@ const RepoHelper = {
|
|||
|
||||
addToDirectory(file, data) {
|
||||
const tree = file || Store;
|
||||
|
||||
// TODO: Figure out why `popstate` is being trigger in the specs
|
||||
if (!tree.files) return;
|
||||
|
||||
const files = tree.files.concat(this.dataToListOfFiles(data, file ? file.level + 1 : 0));
|
||||
|
||||
tree.files = files;
|
||||
|
@ -157,7 +162,18 @@ const RepoHelper = {
|
|||
},
|
||||
|
||||
serializeRepoEntity(type, entity, level = 0) {
|
||||
const { id, url, name, icon, last_commit, tree_url } = entity;
|
||||
const {
|
||||
id,
|
||||
url,
|
||||
name,
|
||||
icon,
|
||||
last_commit,
|
||||
tree_url,
|
||||
path,
|
||||
tempFile,
|
||||
active,
|
||||
opened,
|
||||
} = entity;
|
||||
|
||||
return {
|
||||
id,
|
||||
|
@ -165,11 +181,14 @@ const RepoHelper = {
|
|||
name,
|
||||
url,
|
||||
tree_url,
|
||||
path,
|
||||
level,
|
||||
tempFile,
|
||||
icon: `fa-${icon}`,
|
||||
files: [],
|
||||
loading: false,
|
||||
opened: false,
|
||||
opened,
|
||||
active,
|
||||
// eslint-disable-next-line camelcase
|
||||
lastCommit: last_commit ? {
|
||||
url: `${Store.projectUrl}/commit/${last_commit.id}`,
|
||||
|
@ -213,7 +232,7 @@ const RepoHelper = {
|
|||
},
|
||||
|
||||
findOpenedFileFromActive() {
|
||||
return Store.openedFiles.find(openedFile => Store.activeFile.url === openedFile.url);
|
||||
return Store.openedFiles.find(openedFile => Store.activeFile.id === openedFile.id);
|
||||
},
|
||||
|
||||
getFileFromPath(path) {
|
||||
|
@ -223,6 +242,76 @@ const RepoHelper = {
|
|||
loadingError() {
|
||||
Flash('Unable to load this content at this time.');
|
||||
},
|
||||
openEditMode() {
|
||||
Store.editMode = true;
|
||||
Store.currentBlobView = 'repo-editor';
|
||||
},
|
||||
updateStorePath(path) {
|
||||
Store.path = path;
|
||||
},
|
||||
findOrCreateEntry(type, tree, name) {
|
||||
let exists = true;
|
||||
let foundEntry = tree.files.find(dir => dir.type === type && dir.name === name);
|
||||
|
||||
if (!foundEntry) {
|
||||
foundEntry = RepoHelper.serializeRepoEntity(type, {
|
||||
id: name,
|
||||
name,
|
||||
path: tree.path ? `${tree.path}/${name}` : name,
|
||||
icon: type === 'tree' ? 'folder' : 'file-text-o',
|
||||
tempFile: true,
|
||||
opened: true,
|
||||
active: true,
|
||||
}, tree.level !== undefined ? tree.level + 1 : 0);
|
||||
|
||||
exists = false;
|
||||
tree.files.push(foundEntry);
|
||||
}
|
||||
|
||||
return {
|
||||
entry: foundEntry,
|
||||
exists,
|
||||
};
|
||||
},
|
||||
removeAllTmpFiles(storeFilesKey) {
|
||||
Store[storeFilesKey] = Store[storeFilesKey].filter(f => !f.tempFile);
|
||||
},
|
||||
createNewEntry(name, type) {
|
||||
const originalPath = Store.path;
|
||||
let entryName = name;
|
||||
|
||||
if (entryName.indexOf(`${originalPath}/`) !== 0) {
|
||||
this.updateStorePath('');
|
||||
} else {
|
||||
entryName = entryName.replace(`${originalPath}/`, '');
|
||||
}
|
||||
|
||||
if (entryName === '') return;
|
||||
|
||||
const fileName = type === 'tree' ? '.gitkeep' : entryName;
|
||||
let tree = Store;
|
||||
|
||||
if (type === 'tree') {
|
||||
const dirNames = entryName.split('/');
|
||||
|
||||
dirNames.forEach((dirName) => {
|
||||
if (dirName === '') return;
|
||||
|
||||
tree = this.findOrCreateEntry('tree', tree, dirName).entry;
|
||||
});
|
||||
}
|
||||
|
||||
if ((type === 'tree' && tree.tempFile) || type === 'blob') {
|
||||
const file = this.findOrCreateEntry('blob', tree, fileName);
|
||||
|
||||
if (!file.exists) {
|
||||
this.setFile(file.entry, file.entry);
|
||||
this.openEditMode();
|
||||
}
|
||||
}
|
||||
|
||||
this.updateStorePath(originalPath);
|
||||
},
|
||||
};
|
||||
|
||||
export default RepoHelper;
|
||||
|
|
|
@ -5,6 +5,8 @@ import Service from './services/repo_service';
|
|||
import Store from './stores/repo_store';
|
||||
import Repo from './components/repo.vue';
|
||||
import RepoEditButton from './components/repo_edit_button.vue';
|
||||
import newBranchForm from './components/new_branch_form.vue';
|
||||
import newDropdown from './components/new_dropdown/index.vue';
|
||||
import Translate from '../vue_shared/translate';
|
||||
|
||||
function initDropdowns() {
|
||||
|
@ -27,6 +29,7 @@ function setInitialStore(data) {
|
|||
Store.service = Service;
|
||||
Store.service.url = data.url;
|
||||
Store.service.refsUrl = data.refsUrl;
|
||||
Store.path = data.currentPath;
|
||||
Store.projectId = data.projectId;
|
||||
Store.projectName = data.projectName;
|
||||
Store.projectUrl = data.projectUrl;
|
||||
|
@ -62,9 +65,42 @@ function initRepoEditButton(el) {
|
|||
});
|
||||
}
|
||||
|
||||
function initNewDropdown(el) {
|
||||
return new Vue({
|
||||
el,
|
||||
components: {
|
||||
newDropdown,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('new-dropdown');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function initNewBranchForm() {
|
||||
const el = document.querySelector('.js-new-branch-dropdown');
|
||||
|
||||
if (!el) return null;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
components: {
|
||||
newBranchForm,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('new-branch-form', {
|
||||
props: {
|
||||
currentBranch: Store.currentBranch,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function initRepoBundle() {
|
||||
const repo = document.getElementById('repo');
|
||||
const editButton = document.querySelector('.editable-mode');
|
||||
const newDropdownHolder = document.querySelector('.js-new-dropdown');
|
||||
setInitialStore(repo.dataset);
|
||||
addEventsForNonVueEls();
|
||||
initDropdowns();
|
||||
|
@ -73,6 +109,8 @@ function initRepoBundle() {
|
|||
|
||||
initRepo(repo);
|
||||
initRepoEditButton(editButton);
|
||||
initNewBranchForm();
|
||||
initNewDropdown(newDropdownHolder);
|
||||
}
|
||||
|
||||
$(initRepoBundle);
|
||||
|
|
|
@ -8,7 +8,7 @@ const RepoMixin = {
|
|||
|
||||
changedFiles() {
|
||||
const changedFileList = this.openedFiles
|
||||
.filter(file => file.changed);
|
||||
.filter(file => file.changed || file.tempFile);
|
||||
return changedFileList;
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import axios from 'axios';
|
||||
import csrf from '../../lib/utils/csrf';
|
||||
import Store from '../stores/repo_store';
|
||||
import Api from '../../api';
|
||||
import Helper from '../helpers/repo_helper';
|
||||
|
||||
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
|
||||
|
||||
const RepoService = {
|
||||
url: '',
|
||||
options: {
|
||||
|
@ -10,10 +13,17 @@ const RepoService = {
|
|||
format: 'json',
|
||||
},
|
||||
},
|
||||
createBranchPath: '/api/:version/projects/:id/repository/branches',
|
||||
richExtensionRegExp: /md/,
|
||||
|
||||
getRaw(url) {
|
||||
return axios.get(url, {
|
||||
getRaw(file) {
|
||||
if (file.tempFile) {
|
||||
return Promise.resolve({
|
||||
data: '',
|
||||
});
|
||||
}
|
||||
|
||||
return axios.get(file.raw_path, {
|
||||
// Stop Axios from parsing a JSON file into a JS object
|
||||
transformResponse: [res => res],
|
||||
});
|
||||
|
@ -73,6 +83,12 @@ const RepoService = {
|
|||
.then(this.commitFlash);
|
||||
},
|
||||
|
||||
createBranch(payload) {
|
||||
const url = Api.buildUrl(this.createBranchPath)
|
||||
.replace(':id', Store.projectId);
|
||||
return axios.post(url, payload);
|
||||
},
|
||||
|
||||
commitFlash(data) {
|
||||
if (data.short_id && data.stats) {
|
||||
window.Flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
|
||||
|
|
|
@ -13,6 +13,7 @@ const RepoStore = {
|
|||
projectId: '',
|
||||
projectName: '',
|
||||
projectUrl: '',
|
||||
branchUrl: '',
|
||||
blobRaw: '',
|
||||
currentBlobView: 'repo-preview',
|
||||
openedFiles: [],
|
||||
|
@ -38,6 +39,7 @@ const RepoStore = {
|
|||
newMrTemplateUrl: '',
|
||||
branchChanged: false,
|
||||
commitMessage: '',
|
||||
path: '',
|
||||
loading: {
|
||||
tree: false,
|
||||
blob: false,
|
||||
|
@ -76,21 +78,23 @@ const RepoStore = {
|
|||
} else if (file.newContent || file.plain) {
|
||||
RepoStore.blobRaw = file.newContent || file.plain;
|
||||
} else {
|
||||
Service.getRaw(file.raw_path)
|
||||
Service.getRaw(file)
|
||||
.then((rawResponse) => {
|
||||
RepoStore.blobRaw = rawResponse.data;
|
||||
Helper.findOpenedFileFromActive().plain = rawResponse.data;
|
||||
}).catch(Helper.loadingError);
|
||||
}
|
||||
|
||||
if (!file.loading) Helper.updateHistoryEntry(file.url, file.pageTitle || file.name);
|
||||
if (!file.loading && !file.tempFile) {
|
||||
Helper.updateHistoryEntry(file.url, file.pageTitle || file.name);
|
||||
}
|
||||
RepoStore.binary = file.binary;
|
||||
RepoStore.setActiveLine(-1);
|
||||
},
|
||||
|
||||
setFileActivity(file, openedFile, i) {
|
||||
const activeFile = openedFile;
|
||||
activeFile.active = file.url === activeFile.url;
|
||||
activeFile.active = file.id === activeFile.id;
|
||||
|
||||
if (activeFile.active) RepoStore.setActiveFile(activeFile, i);
|
||||
|
||||
|
@ -98,7 +102,7 @@ const RepoStore = {
|
|||
},
|
||||
|
||||
setActiveFile(activeFile, i) {
|
||||
RepoStore.activeFile = Object.assign({}, RepoStore.activeFile, activeFile);
|
||||
RepoStore.activeFile = Object.assign({}, Helper.getDefaultActiveFile(), activeFile);
|
||||
RepoStore.activeFileIndex = i;
|
||||
},
|
||||
|
||||
|
@ -120,6 +124,11 @@ const RepoStore = {
|
|||
return openedFile.path !== file.path;
|
||||
});
|
||||
|
||||
// remove the file from the sidebar if it is a tempFile
|
||||
if (file.tempFile) {
|
||||
RepoStore.files = RepoStore.files.filter(f => !(f.tempFile && f.path === file.path));
|
||||
}
|
||||
|
||||
// now activate the right tab based on what you closed.
|
||||
if (RepoStore.openedFiles.length === 0) {
|
||||
RepoStore.activeFile = {};
|
||||
|
@ -169,7 +178,7 @@ const RepoStore = {
|
|||
// getters
|
||||
|
||||
isActiveFile(file) {
|
||||
return file && file.url === RepoStore.activeFile.url;
|
||||
return file && file.id === RepoStore.activeFile.id;
|
||||
},
|
||||
|
||||
isPreviewView() {
|
||||
|
|
|
@ -286,6 +286,7 @@ export default {
|
|||
<input
|
||||
id="remove-source-branch-input"
|
||||
v-model="removeSourceBranch"
|
||||
class="js-remove-source-branch-checkbox"
|
||||
:disabled="isRemoveSourceBranchButtonDisabled"
|
||||
type="checkbox"/> Remove source branch
|
||||
</label>
|
||||
|
@ -311,8 +312,8 @@ export default {
|
|||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="bold">
|
||||
The pipeline for this merge request has not succeeded yet
|
||||
<span class="bold js-resolve-mr-widget-items-message">
|
||||
You can only merge once the items above are resolved
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import ciIcon from './ci_icon.vue';
|
||||
import tooltip from '../directives/tooltip';
|
||||
/**
|
||||
* Renders CI Badge link with CI icon and status text based on
|
||||
* API response shared between all places where it is used.
|
||||
|
@ -27,17 +28,23 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
showText: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
ciIcon,
|
||||
},
|
||||
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
computed: {
|
||||
cssClass() {
|
||||
const className = this.status.group;
|
||||
|
||||
return className ? `ci-status ci-${this.status.group}` : 'ci-status';
|
||||
return className ? `ci-status ci-${className}` : 'ci-status';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -45,8 +52,13 @@ export default {
|
|||
<template>
|
||||
<a
|
||||
:href="status.details_path"
|
||||
:class="cssClass">
|
||||
:class="cssClass"
|
||||
v-tooltip
|
||||
:title="!showText ? status.text : ''">
|
||||
<ci-icon :status="status" />
|
||||
|
||||
<template v-if="showText">
|
||||
{{status.text}}
|
||||
</template>
|
||||
</a>
|
||||
</template>
|
||||
|
|
|
@ -63,14 +63,17 @@
|
|||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
showBranch: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Used to verify if all the properties needed to render the commit
|
||||
* ref section were provided.
|
||||
*
|
||||
* TODO: Improve this! Use lodash _.has when we have it.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasCommitRef() {
|
||||
|
@ -80,8 +83,6 @@
|
|||
* Used to verify if all the properties needed to render the commit
|
||||
* author section were provided.
|
||||
*
|
||||
* TODO: Improve this! Use lodash _.has when we have it.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasAuthor() {
|
||||
|
@ -114,8 +115,8 @@
|
|||
</script>
|
||||
<template>
|
||||
<div class="branch-commit">
|
||||
<template v-if="hasCommitRef && showBranch">
|
||||
<div
|
||||
v-if="hasCommitRef"
|
||||
class="icon-container hidden-xs">
|
||||
<i
|
||||
v-if="tag"
|
||||
|
@ -130,7 +131,6 @@
|
|||
</div>
|
||||
|
||||
<a
|
||||
v-if="hasCommitRef"
|
||||
class="ref-name hidden-xs"
|
||||
:href="commitRef.ref_url"
|
||||
v-tooltip
|
||||
|
@ -138,7 +138,7 @@
|
|||
:title="commitRef.name">
|
||||
{{commitRef.name}}
|
||||
</a>
|
||||
|
||||
</template>
|
||||
<div
|
||||
v-html="commitIconSvg"
|
||||
class="commit-icon js-commit-icon">
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<script>
|
||||
|
||||
/* This is a re-usable vue component for rendering a button
|
||||
that will probably be sending off ajax requests and need
|
||||
to show the loading status by setting the `loading` option.
|
||||
This can also be used for initial page load when you don't
|
||||
know the action of the button yet by setting
|
||||
`loading: true, label: undefined`.
|
||||
|
||||
Sample configuration:
|
||||
|
||||
<loading-button
|
||||
:loading="true"
|
||||
:label="Hello"
|
||||
@click="..."
|
||||
/>
|
||||
|
||||
*/
|
||||
|
||||
import loadingIcon from './loading_icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
methods: {
|
||||
onClick(e) {
|
||||
this.$emit('click', e);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="btn btn-align-content"
|
||||
@click="onClick"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
>
|
||||
<transition name="fade">
|
||||
<loading-icon
|
||||
v-if="loading"
|
||||
:inline="true"
|
||||
class="js-loading-button-icon"
|
||||
:class="{
|
||||
'append-right-5': label
|
||||
}"
|
||||
/>
|
||||
</transition>
|
||||
<transition name="fade">
|
||||
<span
|
||||
v-if="label"
|
||||
class="js-loading-button-label"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
</transition>
|
||||
</button>
|
||||
</template>
|
|
@ -1,9 +1,26 @@
|
|||
<script>
|
||||
/**
|
||||
* Common component to render a placeholder note and user information.
|
||||
*
|
||||
* This component needs to be used with a vuex store.
|
||||
* That vuex store needs to have a `getUserData` getter that contains
|
||||
* {
|
||||
* path: String,
|
||||
* avatar_url: String,
|
||||
* name: String,
|
||||
* username: String,
|
||||
* }
|
||||
*
|
||||
* @example
|
||||
* <placeholder-note
|
||||
* :note="{body: 'This is a note'}"
|
||||
* />
|
||||
*/
|
||||
import { mapGetters } from 'vuex';
|
||||
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import userAvatarLink from '../user_avatar/user_avatar_link.vue';
|
||||
|
||||
export default {
|
||||
name: 'issuePlaceholderNote',
|
||||
name: 'placeholderNote',
|
||||
props: {
|
||||
note: {
|
||||
type: Object,
|
|
@ -1,4 +1,12 @@
|
|||
<script>
|
||||
/**
|
||||
* Common component to render a placeholder system note.
|
||||
*
|
||||
* @example
|
||||
* <placeholder-system-note
|
||||
* :note="{ body: 'Commands are being applied'}"
|
||||
* />
|
||||
*/
|
||||
export default {
|
||||
name: 'placeholderSystemNote',
|
||||
props: {
|
|
@ -1,6 +1,24 @@
|
|||
<script>
|
||||
/**
|
||||
* Common component to render a system note, icon and user information.
|
||||
*
|
||||
* This component needs to be used with a vuex store.
|
||||
* That vuex store needs to have a `targetNoteHash` getter
|
||||
*
|
||||
* @example
|
||||
* <system-note
|
||||
* :note="{
|
||||
* id: String,
|
||||
* author: Object,
|
||||
* createdAt: String,
|
||||
* note_html: String,
|
||||
* system_note_icon_name: String
|
||||
* }"
|
||||
* />
|
||||
*/
|
||||
import { mapGetters } from 'vuex';
|
||||
import issueNoteHeader from './issue_note_header.vue';
|
||||
import issueNoteHeader from '../../../notes/components/issue_note_header.vue';
|
||||
import { spriteIcon } from '../../../lib/utils/common_utils';
|
||||
|
||||
export default {
|
||||
name: 'systemNote',
|
||||
|
@ -24,7 +42,7 @@
|
|||
return this.targetNoteHash === this.noteAnchorId;
|
||||
},
|
||||
iconHtml() {
|
||||
return gl.utils.spriteIcon(this.note.system_note_icon_name);
|
||||
return spriteIcon(this.note.system_note_icon_name);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -46,7 +64,8 @@
|
|||
:author="note.author"
|
||||
:created-at="note.created_at"
|
||||
:note-id="note.id"
|
||||
:action-text-html="note.note_html" />
|
||||
:action-text-html="note.note_html"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -9,7 +9,7 @@ export default {
|
|||
},
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
kind: {
|
||||
type: String,
|
||||
|
@ -82,10 +82,11 @@ export default {
|
|||
type="button"
|
||||
class="btn"
|
||||
:class="btnCancelKindClass"
|
||||
@click="emitSubmit(false)">
|
||||
@click="close">
|
||||
{{ closeButtonLabel }}
|
||||
</button>
|
||||
<button type="button"
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="btnKindClass"
|
||||
@click="emitSubmit(true)">
|
||||
|
|
|
@ -12,12 +12,14 @@
|
|||
:img-alt="tooltipText"
|
||||
:img-size="20"
|
||||
:tooltip-text="tooltipText"
|
||||
tooltip-placement="top"
|
||||
:tooltip-placement="top"
|
||||
:username="username"
|
||||
/>
|
||||
|
||||
*/
|
||||
|
||||
import userAvatarImage from './user_avatar_image.vue';
|
||||
import tooltip from '../../directives/tooltip';
|
||||
|
||||
export default {
|
||||
name: 'UserAvatarLink',
|
||||
|
@ -60,6 +62,22 @@ export default {
|
|||
required: false,
|
||||
default: 'top',
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
shouldShowUsername() {
|
||||
return this.username.length > 0;
|
||||
},
|
||||
avatarTooltipText() {
|
||||
return this.shouldShowUsername ? '' : this.tooltipText;
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -73,8 +91,13 @@ export default {
|
|||
:img-alt="imgAlt"
|
||||
:css-classes="imgCssClasses"
|
||||
:size="imgSize"
|
||||
:tooltip-text="tooltipText"
|
||||
:tooltip-text="avatarTooltipText"
|
||||
:tooltip-placement="tooltipPlacement"
|
||||
/>
|
||||
/><span
|
||||
v-if="shouldShowUsername"
|
||||
v-tooltip
|
||||
:title="tooltipText"
|
||||
:tooltip-placement="tooltipPlacement"
|
||||
>{{username}}</span>
|
||||
</a>
|
||||
</template>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
@import "framework/layout";
|
||||
|
||||
@import "framework/animations";
|
||||
@import "framework/vue_transitions";
|
||||
@import "framework/avatar";
|
||||
@import "framework/asciidoctor";
|
||||
@import "framework/banner";
|
||||
|
@ -36,7 +37,7 @@
|
|||
@import "framework/secondary-navigation-elements";
|
||||
@import "framework/selects";
|
||||
@import "framework/sidebar";
|
||||
@import "framework/new-sidebar";
|
||||
@import "framework/contextual-sidebar";
|
||||
@import "framework/tables";
|
||||
@import "framework/notes";
|
||||
@import "framework/tabs";
|
||||
|
|
|
@ -292,6 +292,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn-align-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
&.btn-grouped {
|
||||
@include btn-with-margin;
|
||||
|
|
|
@ -1,24 +1,10 @@
|
|||
@import "framework/variables";
|
||||
@import 'framework/tw_bootstrap_variables';
|
||||
@import "bootstrap/variables";
|
||||
|
||||
$active-background: rgba(0, 0, 0, .04);
|
||||
$active-hover-background: $active-background;
|
||||
$active-hover-color: $gl-text-color;
|
||||
$inactive-badge-background: rgba(0, 0, 0, .08);
|
||||
$hover-background: rgba(0, 0, 0, .06);
|
||||
$hover-color: $gl-text-color;
|
||||
$inactive-color: $gl-text-color-secondary;
|
||||
$new-sidebar-width: 220px;
|
||||
$new-sidebar-collapsed-width: 50px;
|
||||
|
||||
.page-with-new-sidebar {
|
||||
.page-with-contextual-sidebar {
|
||||
@media (min-width: $screen-md-min) {
|
||||
padding-left: $new-sidebar-collapsed-width;
|
||||
padding-left: $contextual-sidebar-collapsed-width;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-lg-min) {
|
||||
padding-left: $new-sidebar-width;
|
||||
padding-left: $contextual-sidebar-width;
|
||||
}
|
||||
|
||||
// Override position: absolute
|
||||
|
@ -34,7 +20,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
|
||||
.page-with-icon-sidebar {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
padding-left: $new-sidebar-collapsed-width;
|
||||
padding-left: $contextual-sidebar-collapsed-width;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,12 +38,12 @@ $new-sidebar-collapsed-width: 50px;
|
|||
|
||||
&:hover,
|
||||
a:hover {
|
||||
background-color: $hover-background;
|
||||
color: $hover-color;
|
||||
background-color: $link-hover-background;
|
||||
color: $gl-text-color;
|
||||
|
||||
.settings-avatar {
|
||||
svg {
|
||||
fill: $hover-color;
|
||||
fill: $gl-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +71,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
.nav-sidebar {
|
||||
position: fixed;
|
||||
z-index: 400;
|
||||
width: $new-sidebar-width;
|
||||
width: $contextual-sidebar-width;
|
||||
transition: left $sidebar-transition-duration;
|
||||
top: $header-height;
|
||||
bottom: 0;
|
||||
|
@ -103,7 +89,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
|
||||
&.sidebar-icons-only {
|
||||
width: auto;
|
||||
min-width: $new-sidebar-collapsed-width;
|
||||
min-width: $contextual-sidebar-collapsed-width;
|
||||
|
||||
.nav-sidebar-inner-scroll {
|
||||
overflow-x: hidden;
|
||||
|
@ -149,11 +135,11 @@ $new-sidebar-collapsed-width: 50px;
|
|||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
color: $inactive-color;
|
||||
color: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $inactive-color;
|
||||
fill: $gl-text-color-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +154,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
left: (-$new-sidebar-width);
|
||||
left: (-$contextual-sidebar-width);
|
||||
}
|
||||
|
||||
.nav-icon-container {
|
||||
|
@ -210,8 +196,8 @@ $new-sidebar-collapsed-width: 50px;
|
|||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: $active-hover-background;
|
||||
color: $active-hover-color;
|
||||
background: $link-active-background;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +206,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: $active-background;
|
||||
background: $link-active-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -308,11 +294,11 @@ $new-sidebar-collapsed-width: 50px;
|
|||
|
||||
.badge {
|
||||
background-color: $inactive-badge-background;
|
||||
color: $inactive-color;
|
||||
color: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $active-background;
|
||||
background: $link-active-background;
|
||||
|
||||
> a {
|
||||
margin-left: 4px;
|
||||
|
@ -330,7 +316,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
|
||||
&.active > a:hover,
|
||||
&.is-over > a {
|
||||
background-color: $hover-background;
|
||||
background-color: $link-hover-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -340,7 +326,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
|
||||
.toggle-sidebar-button,
|
||||
.close-nav-button {
|
||||
width: $new-sidebar-width - 2px;
|
||||
width: $contextual-sidebar-width - 2px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding: 16px;
|
||||
|
@ -407,7 +393,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
}
|
||||
|
||||
.toggle-sidebar-button {
|
||||
width: $new-sidebar-collapsed-width - 2px;
|
||||
width: $contextual-sidebar-collapsed-width - 2px;
|
||||
padding: 16px;
|
||||
|
||||
.collapse-text,
|
|
@ -65,7 +65,7 @@
|
|||
display: flex;
|
||||
flex: 1;
|
||||
-webkit-flex: 1;
|
||||
padding-left: 30px;
|
||||
padding-left: 12px;
|
||||
position: relative;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -221,10 +221,6 @@
|
|||
box-shadow: 0 0 4px $search-input-focus-shadow-color;
|
||||
}
|
||||
|
||||
&.focus .fa-filter {
|
||||
color: $common-gray-dark;
|
||||
}
|
||||
|
||||
gl-emoji {
|
||||
display: inline-block;
|
||||
font-family: inherit;
|
||||
|
@ -251,13 +247,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.fa-filter {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
color: $gray-darkest;
|
||||
}
|
||||
|
||||
.fa-times {
|
||||
right: 10px;
|
||||
color: $gray-darkest;
|
||||
|
|
|
@ -9,6 +9,8 @@ $sidebar-transition-duration: .15s;
|
|||
$sidebar-breakpoint: 1024px;
|
||||
$default-transition-duration: .15s;
|
||||
$right-sidebar-transition-duration: .3s;
|
||||
$contextual-sidebar-width: 220px;
|
||||
$contextual-sidebar-collapsed-width: 50px;
|
||||
|
||||
/*
|
||||
* Color schema
|
||||
|
@ -358,6 +360,13 @@ $dropdown-item-hover-bg: $gray-darker;
|
|||
$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09);
|
||||
$dropdown-hover-color: $blue-400;
|
||||
|
||||
/*
|
||||
* Contextual Sidebar
|
||||
*/
|
||||
$link-active-background: rgba(0, 0, 0, .04);
|
||||
$link-hover-background: rgba(0, 0, 0, .06);
|
||||
$inactive-badge-background: rgba(0, 0, 0, .08);
|
||||
|
||||
/*
|
||||
* Buttons
|
||||
*/
|
||||
|
@ -404,7 +413,6 @@ $note-targe3-inside: #ffffd3;
|
|||
$note-line2-border: #ddd;
|
||||
$note-icon-gutter-width: 55px;
|
||||
|
||||
|
||||
/*
|
||||
* Zen
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity $sidebar-transition-duration $general-hover-transition-curve;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
|
@ -414,7 +414,7 @@
|
|||
margin: 5px;
|
||||
}
|
||||
|
||||
.page-with-new-sidebar.page-with-sidebar .issue-boards-sidebar {
|
||||
.page-with-contextual-sidebar.page-with-sidebar .issue-boards-sidebar {
|
||||
.issuable-sidebar-header {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -249,13 +249,12 @@
|
|||
width: 100%;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.discussion-actions {
|
||||
display: table;
|
||||
|
||||
.new-issue-for-discussion path {
|
||||
.btn-default path {
|
||||
fill: $gray-darkest;
|
||||
}
|
||||
|
||||
|
|
|
@ -466,6 +466,10 @@ ul.notes {
|
|||
float: right;
|
||||
margin-left: 10px;
|
||||
color: $gray-darkest;
|
||||
|
||||
.btn-group > .discussion-next-btn {
|
||||
margin-left: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.note-actions {
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity $sidebar-transition-duration;
|
||||
}
|
||||
|
||||
.monaco-loader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -206,7 +201,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
#repo-file-buttons {
|
||||
.repo-file-buttons {
|
||||
background-color: $white-light;
|
||||
padding: 5px 10px;
|
||||
border-top: 1px solid $white-normal;
|
||||
|
|
|
@ -57,6 +57,10 @@ class HelpController < ApplicationController
|
|||
def shortcuts
|
||||
end
|
||||
|
||||
def instance_configuration
|
||||
@instance_configuration = InstanceConfiguration.new
|
||||
end
|
||||
|
||||
def ui
|
||||
@user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
|
||||
end
|
||||
|
|
|
@ -205,6 +205,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
tree_path = path_segments.join('/')
|
||||
|
||||
render json: json.merge(
|
||||
id: @blob.id,
|
||||
path: blob.path,
|
||||
name: blob.name,
|
||||
extension: blob.extension,
|
||||
|
|
|
@ -15,6 +15,8 @@ class Projects::BranchesController < Projects::ApplicationController
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
@refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
|
||||
@merged_branch_names =
|
||||
repository.merged_branch_names(@branches.map(&:name))
|
||||
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37429
|
||||
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||
@max_commits = @branches.reduce(0) do |memo, branch|
|
||||
|
|
|
@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
before_action :authorize_create_issue!, only: [:new, :create]
|
||||
|
||||
# Allow modify issue
|
||||
before_action :authorize_update_issue!, only: [:update, :move]
|
||||
before_action :authorize_update_issue!, only: [:edit, :update, :move]
|
||||
|
||||
# Allow create a new branch and empty WIP merge request from current issue
|
||||
before_action :authorize_create_merge_request!, only: [:create_merge_request]
|
||||
|
@ -63,6 +63,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
respond_with(@issue)
|
||||
end
|
||||
|
||||
def edit
|
||||
respond_with(@issue)
|
||||
end
|
||||
|
||||
def show
|
||||
@noteable = @issue
|
||||
@note = @project.notes.new(noteable: @issue)
|
||||
|
@ -122,6 +126,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
recaptcha_check_with_fallback { render :edit }
|
||||
end
|
||||
|
||||
format.json do
|
||||
render_issue_json
|
||||
end
|
||||
|
|
|
@ -36,7 +36,6 @@ class Projects::TreeController < Projects::ApplicationController
|
|||
|
||||
format.json do
|
||||
page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
|
||||
response.header['is-root'] = @path.empty?
|
||||
|
||||
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
|
||||
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||
|
|
|
@ -23,7 +23,7 @@ class BranchesFinder
|
|||
|
||||
def filter_by_name(branches)
|
||||
if search
|
||||
branches.select { |branch| branch.name.include?(search) }
|
||||
branches.select { |branch| branch.name.upcase.include?(search.upcase) }
|
||||
else
|
||||
branches
|
||||
end
|
||||
|
|
|
@ -120,6 +120,15 @@ module ApplicationSettingsHelper
|
|||
message.html_safe
|
||||
end
|
||||
|
||||
def circuitbreaker_access_retries_help_text
|
||||
_('The number of attempts GitLab will make to access a storage.')
|
||||
end
|
||||
|
||||
def circuitbreaker_backoff_threshold_help_text
|
||||
_("The number of failures after which GitLab will start temporarily "\
|
||||
"disabling access to a storage shard on a host")
|
||||
end
|
||||
|
||||
def circuitbreaker_failure_wait_time_help_text
|
||||
_("When access to a storage fails. GitLab will prevent access to the "\
|
||||
"storage for the time specified here. This allows the filesystem to "\
|
||||
|
@ -144,6 +153,8 @@ module ApplicationSettingsHelper
|
|||
:akismet_api_key,
|
||||
:akismet_enabled,
|
||||
:auto_devops_enabled,
|
||||
:circuitbreaker_access_retries,
|
||||
:circuitbreaker_backoff_threshold,
|
||||
:circuitbreaker_failure_count_threshold,
|
||||
:circuitbreaker_failure_reset_time,
|
||||
:circuitbreaker_failure_wait_time,
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
module InstanceConfigurationHelper
|
||||
def instance_configuration_cell_html(value, &block)
|
||||
return '-' unless value.to_s.presence
|
||||
|
||||
block_given? ? yield(value) : value
|
||||
end
|
||||
|
||||
def instance_configuration_host(host)
|
||||
@instance_configuration_host ||= instance_configuration_cell_html(host).capitalize
|
||||
end
|
||||
|
||||
# Value must be in bytes
|
||||
def instance_configuration_human_size_cell(value)
|
||||
instance_configuration_cell_html(value) do |v|
|
||||
number_to_human_size(v, strip_insignificant_zeros: true, significant: false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
module NavHelper
|
||||
def page_with_sidebar_class
|
||||
class_name = page_gutter_class
|
||||
class_name << 'page-with-new-sidebar' if defined?(@left_sidebar) && @left_sidebar
|
||||
class_name << 'page-with-contextual-sidebar' if defined?(@left_sidebar) && @left_sidebar
|
||||
class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar
|
||||
|
||||
class_name
|
||||
|
|
|
@ -16,17 +16,16 @@ module StorageHealthHelper
|
|||
def message_for_circuit_breaker(circuit_breaker)
|
||||
maximum_failures = circuit_breaker.failure_count_threshold
|
||||
current_failures = circuit_breaker.failure_count
|
||||
permanently_broken = circuit_breaker.circuit_broken? && current_failures >= maximum_failures
|
||||
|
||||
translation_params = { number_of_failures: current_failures,
|
||||
maximum_failures: maximum_failures,
|
||||
number_of_seconds: circuit_breaker.failure_wait_time }
|
||||
|
||||
if permanently_broken
|
||||
if circuit_breaker.circuit_broken?
|
||||
s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
|
||||
"retry automatically. Reset storage information when the problem is "\
|
||||
"resolved.") % translation_params
|
||||
elsif circuit_breaker.circuit_broken?
|
||||
elsif circuit_breaker.backing_off?
|
||||
_("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
|
||||
"block access for %{number_of_seconds} seconds.") % translation_params
|
||||
else
|
||||
|
|
|
@ -153,13 +153,25 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
presence: true,
|
||||
numericality: { greater_than_or_equal_to: 0 }
|
||||
|
||||
validates :circuitbreaker_failure_count_threshold,
|
||||
validates :circuitbreaker_backoff_threshold,
|
||||
:circuitbreaker_failure_count_threshold,
|
||||
:circuitbreaker_failure_wait_time,
|
||||
:circuitbreaker_failure_reset_time,
|
||||
:circuitbreaker_storage_timeout,
|
||||
presence: true,
|
||||
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||
|
||||
validates :circuitbreaker_access_retries,
|
||||
presence: true,
|
||||
numericality: { only_integer: true, greater_than_or_equal_to: 1 }
|
||||
|
||||
validates_each :circuitbreaker_backoff_threshold do |record, attr, value|
|
||||
if value.to_i >= record.circuitbreaker_failure_count_threshold
|
||||
record.errors.add(attr, _("The circuitbreaker backoff threshold should be "\
|
||||
"lower than the failure count threshold"))
|
||||
end
|
||||
end
|
||||
|
||||
SUPPORTED_KEY_TYPES.each do |type|
|
||||
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
|
||||
end
|
||||
|
|
|
@ -249,9 +249,7 @@ module Ci
|
|||
end
|
||||
|
||||
def commit
|
||||
@commit ||= project.commit(sha)
|
||||
rescue
|
||||
nil
|
||||
@commit ||= project.commit_by(oid: sha)
|
||||
end
|
||||
|
||||
def branch?
|
||||
|
|
|
@ -110,7 +110,7 @@ class Environment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def ref_path
|
||||
"refs/#{Repository::REF_ENVIRONMENTS}/#{Shellwords.shellescape(name)}"
|
||||
"refs/#{Repository::REF_ENVIRONMENTS}/#{generate_slug}"
|
||||
end
|
||||
|
||||
def formatted_external_url
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
require 'resolv'
|
||||
|
||||
class InstanceConfiguration
|
||||
SSH_ALGORITHMS = %w(DSA ECDSA ED25519 RSA).freeze
|
||||
SSH_ALGORITHMS_PATH = '/etc/ssh/'.freeze
|
||||
CACHE_KEY = 'instance_configuration'.freeze
|
||||
EXPIRATION_TIME = 24.hours
|
||||
|
||||
def settings
|
||||
@configuration ||= Rails.cache.fetch(CACHE_KEY, expires_in: EXPIRATION_TIME) do
|
||||
{ ssh_algorithms_hashes: ssh_algorithms_hashes,
|
||||
host: host,
|
||||
gitlab_pages: gitlab_pages,
|
||||
gitlab_ci: gitlab_ci }.deep_symbolize_keys
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ssh_algorithms_hashes
|
||||
SSH_ALGORITHMS.map { |algo| ssh_algorithm_hashes(algo) }.compact
|
||||
end
|
||||
|
||||
def host
|
||||
Settings.gitlab.host
|
||||
end
|
||||
|
||||
def gitlab_pages
|
||||
Settings.pages.to_h.merge(ip_address: resolv_dns(Settings.pages.host))
|
||||
end
|
||||
|
||||
def resolv_dns(dns)
|
||||
Resolv.getaddress(dns)
|
||||
rescue Resolv::ResolvError
|
||||
end
|
||||
|
||||
def gitlab_ci
|
||||
Settings.gitlab_ci
|
||||
.to_h
|
||||
.merge(artifacts_max_size: { value: Settings.artifacts.max_size&.megabytes,
|
||||
default: 100.megabytes })
|
||||
end
|
||||
|
||||
def ssh_algorithm_file(algorithm)
|
||||
File.join(SSH_ALGORITHMS_PATH, "ssh_host_#{algorithm.downcase}_key.pub")
|
||||
end
|
||||
|
||||
def ssh_algorithm_hashes(algorithm)
|
||||
content = ssh_algorithm_file_content(algorithm)
|
||||
return unless content.present?
|
||||
|
||||
{ name: algorithm,
|
||||
md5: ssh_algorithm_md5(content),
|
||||
sha256: ssh_algorithm_sha256(content) }
|
||||
end
|
||||
|
||||
def ssh_algorithm_file_content(algorithm)
|
||||
file = ssh_algorithm_file(algorithm)
|
||||
return unless File.exist?(file)
|
||||
|
||||
File.read(file)
|
||||
end
|
||||
|
||||
def ssh_algorithm_md5(ssh_file_content)
|
||||
OpenSSL::Digest::MD5.hexdigest(ssh_file_content).scan(/../).join(':')
|
||||
end
|
||||
|
||||
def ssh_algorithm_sha256(ssh_file_content)
|
||||
OpenSSL::Digest::SHA256.hexdigest(ssh_file_content)
|
||||
end
|
||||
end
|
|
@ -16,9 +16,9 @@ class PagesDomain < ActiveRecord::Base
|
|||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
after_create :update
|
||||
after_save :update
|
||||
after_destroy :update
|
||||
after_create :update_daemon
|
||||
after_save :update_daemon
|
||||
after_destroy :update_daemon
|
||||
|
||||
def to_param
|
||||
domain
|
||||
|
@ -80,7 +80,7 @@ class PagesDomain < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def update
|
||||
def update_daemon
|
||||
::Projects::UpdatePagesConfigurationService.new(project).execute
|
||||
end
|
||||
|
||||
|
|
|
@ -540,6 +540,10 @@ class Project < ActiveRecord::Base
|
|||
repository.commit(ref)
|
||||
end
|
||||
|
||||
def commit_by(oid:)
|
||||
repository.commit_by(oid: oid)
|
||||
end
|
||||
|
||||
# ref can't be HEAD, can only be branch/tag name or SHA
|
||||
def latest_successful_builds_for(ref = default_branch)
|
||||
latest_pipeline = pipelines.latest_successful_for(ref)
|
||||
|
@ -553,7 +557,7 @@ class Project < ActiveRecord::Base
|
|||
|
||||
def merge_base_commit(first_commit_id, second_commit_id)
|
||||
sha = repository.merge_base(first_commit_id, second_commit_id)
|
||||
repository.commit(sha) if sha
|
||||
commit_by(oid: sha) if sha
|
||||
end
|
||||
|
||||
def saved?
|
||||
|
|
|
@ -3,6 +3,8 @@ class JiraService < IssueTrackerService
|
|||
|
||||
validates :url, url: true, presence: true, if: :activated?
|
||||
validates :api_url, url: true, allow_blank: true
|
||||
validates :username, presence: true, if: :activated?
|
||||
validates :password, presence: true, if: :activated?
|
||||
|
||||
prop_accessor :username, :password, :url, :api_url, :jira_issue_transition_id, :title, :description
|
||||
|
||||
|
|
|
@ -153,7 +153,10 @@ class KubernetesService < DeploymentService
|
|||
end
|
||||
|
||||
def default_namespace
|
||||
"#{project.path}-#{project.id}" if project.present?
|
||||
return unless project
|
||||
|
||||
slug = "#{project.path}-#{project.id}".downcase
|
||||
slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
|
||||
end
|
||||
|
||||
def build_kubeclient!(api_path: 'api', api_version: 'v1')
|
||||
|
|
|
@ -76,6 +76,7 @@ class Repository
|
|||
@full_path = full_path
|
||||
@disk_path = disk_path || full_path
|
||||
@project = project
|
||||
@commit_cache = {}
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
|
@ -103,18 +104,17 @@ class Repository
|
|||
|
||||
def commit(ref = 'HEAD')
|
||||
return nil unless exists?
|
||||
return ref if ref.is_a?(::Commit)
|
||||
|
||||
commit =
|
||||
if ref.is_a?(Gitlab::Git::Commit)
|
||||
ref
|
||||
else
|
||||
Gitlab::Git::Commit.find(raw_repository, ref)
|
||||
find_commit(ref)
|
||||
end
|
||||
|
||||
commit = ::Commit.new(commit, @project) if commit
|
||||
commit
|
||||
rescue Rugged::OdbError, Rugged::TreeError
|
||||
nil
|
||||
# Finding a commit by the passed SHA
|
||||
# Also takes care of caching, based on the SHA
|
||||
def commit_by(oid:)
|
||||
return @commit_cache[oid] if @commit_cache.key?(oid)
|
||||
|
||||
@commit_cache[oid] = find_commit(oid)
|
||||
end
|
||||
|
||||
def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
|
||||
|
@ -231,7 +231,7 @@ class Repository
|
|||
# branches or tags, but we want to keep some of these commits around, for
|
||||
# example if they have comments or CI builds.
|
||||
def keep_around(sha)
|
||||
return unless sha && commit(sha)
|
||||
return unless sha && commit_by(oid: sha)
|
||||
|
||||
return if kept_around?(sha)
|
||||
|
||||
|
@ -862,22 +862,12 @@ class Repository
|
|||
end
|
||||
|
||||
def ff_merge(user, source, target_branch, merge_request: nil)
|
||||
our_commit = rugged.branches[target_branch].target
|
||||
their_commit =
|
||||
if source.is_a?(Gitlab::Git::Commit)
|
||||
source.raw_commit
|
||||
else
|
||||
rugged.lookup(source)
|
||||
end
|
||||
their_commit_id = commit(source)&.id
|
||||
raise 'Invalid merge source' if their_commit_id.nil?
|
||||
|
||||
raise 'Invalid merge target' if our_commit.nil?
|
||||
raise 'Invalid merge source' if their_commit.nil?
|
||||
merge_request&.update(in_progress_merge_commit_sha: their_commit_id)
|
||||
|
||||
with_branch(user, target_branch) do |start_commit|
|
||||
merge_request&.update(in_progress_merge_commit_sha: their_commit.oid)
|
||||
|
||||
their_commit.oid
|
||||
end
|
||||
with_cache_hooks { raw.ff_merge(user, their_commit_id, target_branch) }
|
||||
end
|
||||
|
||||
def revert(
|
||||
|
@ -912,18 +902,27 @@ class Repository
|
|||
end
|
||||
end
|
||||
|
||||
def merged_to_root_ref?(branch_name)
|
||||
branch_commit = commit(branch_name)
|
||||
root_ref_commit = commit(root_ref)
|
||||
def merged_to_root_ref?(branch_or_name, pre_loaded_merged_branches = nil)
|
||||
branch = Gitlab::Git::Branch.find(self, branch_or_name)
|
||||
|
||||
if branch_commit
|
||||
same_head = branch_commit.id == root_ref_commit.id
|
||||
!same_head && ancestor?(branch_commit.id, root_ref_commit.id)
|
||||
if branch
|
||||
root_ref_sha = commit(root_ref).sha
|
||||
same_head = branch.target == root_ref_sha
|
||||
merged =
|
||||
if pre_loaded_merged_branches
|
||||
pre_loaded_merged_branches.include?(branch.name)
|
||||
else
|
||||
ancestor?(branch.target, root_ref_sha)
|
||||
end
|
||||
|
||||
!same_head && merged
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
delegate :merged_branch_names, to: :raw_repository
|
||||
|
||||
def merge_base(first_commit_id, second_commit_id)
|
||||
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
|
||||
second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
|
||||
|
@ -1031,6 +1030,10 @@ class Repository
|
|||
if instance_variable_defined?(ivar)
|
||||
instance_variable_get(ivar)
|
||||
else
|
||||
# If the repository doesn't exist and a fallback was specified we return
|
||||
# that value inmediately. This saves us Rugged/gRPC invocations.
|
||||
return fallback unless fallback.nil? || exists?
|
||||
|
||||
begin
|
||||
value =
|
||||
if memoize_only
|
||||
|
@ -1040,8 +1043,9 @@ class Repository
|
|||
end
|
||||
instance_variable_set(ivar, value)
|
||||
rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
|
||||
# if e.g. HEAD or the entire repository doesn't exist we want to
|
||||
# gracefully handle this and not cache anything.
|
||||
# Even if the above `#exists?` check passes these errors might still
|
||||
# occur (for example because of a non-existing HEAD). We want to
|
||||
# gracefully handle this and not cache anything
|
||||
fallback
|
||||
end
|
||||
end
|
||||
|
@ -1069,6 +1073,18 @@ class Repository
|
|||
|
||||
private
|
||||
|
||||
# TODO Generice finder, later split this on finders by Ref or Oid
|
||||
# gitlab-org/gitlab-ce#39239
|
||||
def find_commit(oid_or_ref)
|
||||
commit = if oid_or_ref.is_a?(Gitlab::Git::Commit)
|
||||
oid_or_ref
|
||||
else
|
||||
Gitlab::Git::Commit.find(raw_repository, oid_or_ref)
|
||||
end
|
||||
|
||||
::Commit.new(commit, @project) if commit
|
||||
end
|
||||
|
||||
def blob_data_at(sha, path)
|
||||
blob = blob_at(sha, path)
|
||||
return unless blob
|
||||
|
@ -1107,12 +1123,12 @@ class Repository
|
|||
|
||||
def last_commit_for_path_by_gitaly(sha, path)
|
||||
c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
|
||||
commit(c)
|
||||
commit_by(oid: c)
|
||||
end
|
||||
|
||||
def last_commit_for_path_by_rugged(sha, path)
|
||||
sha = last_commit_id_for_path_by_shelling_out(sha, path)
|
||||
commit(sha)
|
||||
commit_by(oid: sha)
|
||||
end
|
||||
|
||||
def last_commit_id_for_path_by_shelling_out(sha, path)
|
||||
|
|
|
@ -16,8 +16,8 @@ module Users
|
|||
user_cache_key
|
||||
]
|
||||
|
||||
if event.project.forked?
|
||||
keys << project_cache_key(event.project.forked_from_project)
|
||||
if forked_from = event.project.forked_from_project
|
||||
keys << project_cache_key(forked_from)
|
||||
end
|
||||
|
||||
keys.each { |key| set_key(key, event.id) }
|
||||
|
|
|
@ -533,29 +533,41 @@
|
|||
%fieldset
|
||||
%legend Git Storage Circuitbreaker settings
|
||||
.form-group
|
||||
= f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2'
|
||||
= f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
|
||||
= f.number_field :circuitbreaker_access_retries, class: 'form-control'
|
||||
.help-block
|
||||
= circuitbreaker_failure_count_help_text
|
||||
= circuitbreaker_access_retries_help_text
|
||||
.form-group
|
||||
= f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.number_field :circuitbreaker_storage_timeout, class: 'form-control'
|
||||
.help-block
|
||||
= circuitbreaker_storage_timeout_help_text
|
||||
.form-group
|
||||
= f.label :circuitbreaker_backoff_threshold, _('Number of failures before backing off'), class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.number_field :circuitbreaker_backoff_threshold, class: 'form-control'
|
||||
.help-block
|
||||
= circuitbreaker_backoff_threshold_help_text
|
||||
.form-group
|
||||
= f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.number_field :circuitbreaker_failure_wait_time, class: 'form-control'
|
||||
.help-block
|
||||
= circuitbreaker_failure_wait_time_help_text
|
||||
.form-group
|
||||
= f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
|
||||
.help-block
|
||||
= circuitbreaker_failure_count_help_text
|
||||
.form-group
|
||||
= f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.number_field :circuitbreaker_failure_reset_time, class: 'form-control'
|
||||
.help-block
|
||||
= circuitbreaker_failure_reset_time_help_text
|
||||
.form-group
|
||||
= f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.number_field :circuitbreaker_storage_timeout, class: 'form-control'
|
||||
.help-block
|
||||
= circuitbreaker_storage_timeout_help_text
|
||||
|
||||
%fieldset
|
||||
%legend Repository Checks
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
.fade-right= icon('angle-right')
|
||||
%ul.nav-links.scrolling-tabs
|
||||
= nav_link(page: [dashboard_projects_path, root_path]) do
|
||||
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
|
||||
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
|
||||
Your projects
|
||||
= nav_link(page: starred_dashboard_projects_path) do
|
||||
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
|
||||
= link_to starred_dashboard_projects_path, data: {placement: 'right'} do
|
||||
Starred projects
|
||||
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
|
||||
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
|
||||
= link_to explore_root_path, data: {placement: 'right'} do
|
||||
Explore projects
|
||||
|
||||
.nav-controls
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
%span= Gitlab::VERSION
|
||||
%small= link_to Gitlab::REVISION, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab::REVISION)
|
||||
= version_status_badge
|
||||
|
||||
%p.slead
|
||||
GitLab is open source software to collaborate on code.
|
||||
%br
|
||||
|
@ -23,6 +24,7 @@
|
|||
Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises.
|
||||
%br
|
||||
Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank', rel: 'noopener noreferrer'}.
|
||||
%p= link_to 'Check the current instance configuration ', help_instance_configuration_url
|
||||
%hr
|
||||
|
||||
.row.prepend-top-default
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
- page_title 'Instance Configuration'
|
||||
.wiki.documentation
|
||||
%h1 Instance Configuration
|
||||
|
||||
%p
|
||||
In this page you will find information about the settings that are used in your current instance.
|
||||
|
||||
= render 'help/instance_configuration/ssh_info'
|
||||
= render 'help/instance_configuration/gitlab_pages'
|
||||
= render 'help/instance_configuration/gitlab_ci'
|
||||
%p
|
||||
%strong Table of contents
|
||||
|
||||
%ul
|
||||
= content_for :table_content
|
||||
|
||||
= content_for :settings_content
|
|
@ -0,0 +1,24 @@
|
|||
- content_for :table_content do
|
||||
%li= link_to 'GitLab CI', '#gitlab-ci'
|
||||
|
||||
- content_for :settings_content do
|
||||
%h2#gitlab-ci
|
||||
GitLab CI
|
||||
|
||||
%p
|
||||
Below are the current settings regarding
|
||||
= succeed('.') { link_to('GitLab CI', 'https://about.gitlab.com/gitlab-ci', target: '_blank') }
|
||||
|
||||
.table-responsive
|
||||
%table
|
||||
%thead
|
||||
%tr
|
||||
%th Setting
|
||||
%th= instance_configuration_host(@instance_configuration.settings[:host])
|
||||
%th Default
|
||||
%tbody
|
||||
%tr
|
||||
- artifacts_size = @instance_configuration.settings[:gitlab_ci][:artifacts_max_size]
|
||||
%td Artifacts maximum size
|
||||
%td= instance_configuration_human_size_cell(artifacts_size[:value])
|
||||
%td= instance_configuration_human_size_cell(artifacts_size[:default])
|
|
@ -0,0 +1,35 @@
|
|||
- gitlab_pages = @instance_configuration.settings[:gitlab_pages]
|
||||
- content_for :table_content do
|
||||
%li= link_to 'GitLab Pages', '#gitlab-pages'
|
||||
|
||||
- content_for :settings_content do
|
||||
%h2#gitlab-pages
|
||||
GitLab Pages
|
||||
|
||||
%p
|
||||
Below are the settings for
|
||||
= succeed('.') { link_to('Gitlab Pages', gitlab_pages[:url], target: '_blank') }
|
||||
.table-responsive
|
||||
%table
|
||||
%thead
|
||||
%tr
|
||||
%th Setting
|
||||
%th= instance_configuration_host(@instance_configuration.settings[:host])
|
||||
%tbody
|
||||
%tr
|
||||
%td Domain Name
|
||||
%td
|
||||
%code= instance_configuration_cell_html(gitlab_pages[:host])
|
||||
%tr
|
||||
%td IP Address
|
||||
%td
|
||||
%code= instance_configuration_cell_html(gitlab_pages[:ip_address])
|
||||
%tr
|
||||
%td Port
|
||||
%td
|
||||
%code= instance_configuration_cell_html(gitlab_pages[:port])
|
||||
%br
|
||||
|
||||
%p
|
||||
The maximum size of your Pages site is regulated by the artifacts maximum
|
||||
size which is part of #{succeed('.') { link_to('GitLab CI', '#gitlab-ci') }}
|
|
@ -0,0 +1,27 @@
|
|||
- ssh_info = @instance_configuration.settings[:ssh_algorithms_hashes]
|
||||
- if ssh_info.any?
|
||||
- content_for :table_content do
|
||||
%li= link_to 'SSH host keys fingerprints', '#ssh-host-keys-fingerprints'
|
||||
|
||||
- content_for :settings_content do
|
||||
%h2#ssh-host-keys-fingerprints
|
||||
SSH host keys fingerprints
|
||||
|
||||
%p
|
||||
Below are the fingerprints for the current instance SSH host keys.
|
||||
|
||||
.table-responsive
|
||||
%table
|
||||
%thead
|
||||
%tr
|
||||
%th Algorithm
|
||||
%th MD5
|
||||
%th SHA256
|
||||
%tbody
|
||||
- ssh_info.each do |algorithm|
|
||||
%tr
|
||||
%td= algorithm[:name]
|
||||
%td
|
||||
%code= instance_configuration_cell_html(algorithm[:md5])
|
||||
%td
|
||||
%code= instance_configuration_cell_html(algorithm[:sha256])
|
|
@ -0,0 +1,7 @@
|
|||
- local_assigns.fetch(:view)
|
||||
|
||||
%strong
|
||||
%span{ data: { defer_to: "#{view.defer_key}-duration" } } ...
|
||||
\/
|
||||
%span{ data: { defer_to: "#{view.defer_key}-calls" } } ...
|
||||
Gitaly
|
|
@ -1,3 +1,4 @@
|
|||
- merged = local_assigns.fetch(:merged, false)
|
||||
- commit = @repository.commit(branch.dereferenced_target)
|
||||
- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
|
||||
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
|
||||
|
@ -12,7 +13,7 @@
|
|||
|
||||
- if branch.name == @repository.root_ref
|
||||
%span.label.label-primary default
|
||||
- elsif @repository.merged_to_root_ref? branch.name
|
||||
- elsif merged
|
||||
%span.label.label-info.has-tooltip{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
|
||||
= s_('Branches|merged')
|
||||
|
||||
|
@ -47,7 +48,7 @@
|
|||
target: "#modal-delete-branch",
|
||||
delete_path: project_branch_path(@project, branch.name),
|
||||
branch_name: branch.name,
|
||||
is_merged: ("true" if @repository.merged_to_root_ref?(branch.name)) } }
|
||||
is_merged: ("true" if merged) } }
|
||||
= icon("trash-o")
|
||||
- else
|
||||
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
- if @branches.any?
|
||||
%ul.content-list.all-branches
|
||||
- @branches.each do |branch|
|
||||
= render "projects/branches/branch", branch: branch
|
||||
= render "projects/branches/branch", branch: branch, merged: @repository.merged_to_root_ref?(branch, @merged_branch_names)
|
||||
= paginate @branches, theme: 'gitlab'
|
||||
- else
|
||||
.nothing-here-block
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
- if can?(current_user, :update_cluster, @cluster)
|
||||
.form-group
|
||||
= field.submit s_('ClusterIntegration|Save'), class: 'btn btn-success'
|
||||
= field.submit _('Save'), class: 'btn btn-success'
|
||||
|
||||
%section.settings#js-cluster-details
|
||||
.settings-header
|
||||
|
@ -68,7 +68,7 @@
|
|||
|
||||
%section.settings#js-cluster-advanced-settings
|
||||
.settings-header
|
||||
%h4= s_('ClusterIntegration|Advanced settings')
|
||||
%h4= _('Advanced settings')
|
||||
%button.btn.js-settings-toggle
|
||||
= expanded ? 'Collapse' : 'Expand'
|
||||
%p= s_('ClusterIntegration|Manage Cluster integration on your GitLab project')
|
||||
|
|
|
@ -77,5 +77,6 @@
|
|||
#{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) }
|
||||
.mr-widget-pipeline-graph
|
||||
= render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
|
||||
- if last_pipeline.duration
|
||||
in
|
||||
= time_interval_in_words last_pipeline.duration
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
%pre.light-well
|
||||
:preserve
|
||||
cd existing_repo
|
||||
git remote rename origin old-origin
|
||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
|
||||
git push -u origin --all
|
||||
git push -u origin --tags
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
- page_title "Edit", "#{@issue.title} (#{@issue.to_reference})", "Issues"
|
||||
|
||||
%h3.page-title
|
||||
Edit Issue ##{@issue.iid}
|
||||
%hr
|
||||
|
||||
= render "form"
|
|
@ -1,8 +1,10 @@
|
|||
.tree-ref-container
|
||||
.tree-ref-holder
|
||||
= render 'shared/ref_switcher', destination: 'tree', path: @path
|
||||
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
|
||||
|
||||
- unless show_new_repo?
|
||||
- if show_new_repo?
|
||||
.js-new-dropdown
|
||||
- else
|
||||
= render 'projects/tree/old_tree_header'
|
||||
|
||||
.tree-controls
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
- show_new_branch_form = show_new_repo? && show_create && can?(current_user, :push_code, @project)
|
||||
- dropdown_toggle_text = @ref || @project.default_branch
|
||||
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
|
||||
= hidden_field_tag :destination, destination
|
||||
|
@ -7,8 +8,20 @@
|
|||
= hidden_field_tag key, value, id: nil
|
||||
.dropdown
|
||||
= dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown" }
|
||||
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
|
||||
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
|
||||
.dropdown-page-one
|
||||
= dropdown_title _("Switch branch/tag")
|
||||
= dropdown_filter _("Search branches and tags")
|
||||
= dropdown_content
|
||||
= dropdown_loading
|
||||
- if show_new_branch_form
|
||||
= dropdown_footer do
|
||||
%ul.dropdown-footer-list
|
||||
%li
|
||||
%a.dropdown-toggle-page{ href: "#" }
|
||||
Create new branch
|
||||
- if show_new_branch_form
|
||||
.dropdown-page-two
|
||||
= dropdown_title("Create new branch", options: { back: true })
|
||||
= dropdown_content do
|
||||
.js-new-branch-dropdown
|
||||
|
|
|
@ -14,5 +14,5 @@
|
|||
= link_to_member(@project, participant, name: false, size: 24, lazy_load: true)
|
||||
- if participants_extra > 0
|
||||
.hide-collapsed.participants-more
|
||||
%a.js-participants-more{ href: "#", data: { original_text: "+ #{participants_size - 7} more", less_text: "- show less" } }
|
||||
%button.btn-transparent.btn-blank.js-participants-more{ type: 'button', data: { original_text: "+ #{participants_size - 7} more", less_text: "- show less" } }
|
||||
+ #{participants_extra} more
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
%ul.tokens-container.list-unstyled
|
||||
%li.input-token
|
||||
%input.form-control.filtered-search{ search_filter_input_options(type) }
|
||||
= icon('filter')
|
||||
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { action: 'submit' } }
|
||||
|
|
|
@ -7,4 +7,5 @@
|
|||
blob_url: namespace_project_blob_path(project.namespace, project, '{{branch}}'),
|
||||
new_mr_template_url: namespace_project_new_merge_request_path(project.namespace, project, merge_request: { source_branch: '{{source_branch}}' }),
|
||||
can_commit: (!!can_push_branch?(project, @ref)).to_s,
|
||||
on_top_of_branch: (!!on_top_of_branch?(project, @ref)).to_s } }
|
||||
on_top_of_branch: (!!on_top_of_branch?(project, @ref)).to_s,
|
||||
current_path: @path } }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Suggest to rename the remote for existing repository instructions
|
||||
merge_request: 14970
|
||||
author: helmo42
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add API endpoints for Pages Domains
|
||||
merge_request: 13917
|
||||
author: Travis Miller
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove filter icon from search bar
|
||||
merge_request:
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Case insensitive search for branches
|
||||
merge_request: 14995
|
||||
author: George Andrinopoulos
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Refactor have_http_status into have_gitlab_http_status
|
||||
merge_request: 14958
|
||||
author: Jacopo Beschi @jacopo-beschi
|
||||
type: added
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue