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] Added defaults for protected branches dropdowns on the repository settings. !14278
|
||||||
- [CHANGED] Show confirmation modal before deleting account. !14360
|
- [CHANGED] Show confirmation modal before deleting account. !14360
|
||||||
- [CHANGED] Allow creating merge requests across a fork network. !14422
|
- [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] Create idea of read-only database. !14688
|
||||||
- [CHANGED] Add active states to nav bar counters.
|
- [CHANGED] Add active states to nav bar counters.
|
||||||
- [CHANGED] Add view replaced file link for image diffs.
|
- [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)
|
- [Workflow labels](#workflow-labels)
|
||||||
- [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc)
|
- [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)
|
- [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)
|
- [Priority labels (~Deliverable and ~Stretch)](#priority-labels-deliverable-and-stretch)
|
||||||
- [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
|
- [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 tracker](#issue-tracker)
|
||||||
- [Issue triaging](#issue-triaging)
|
- [Issue triaging](#issue-triaging)
|
||||||
- [Feature proposals](#feature-proposals)
|
- [Feature proposals](#feature-proposals)
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -398,7 +398,7 @@ group :ed25519 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Gitaly GRPC client
|
# 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
|
gem 'toml-rb', '~> 0.3.15', require: false
|
||||||
|
|
||||||
|
|
|
@ -274,7 +274,7 @@ GEM
|
||||||
po_to_json (>= 1.0.0)
|
po_to_json (>= 1.0.0)
|
||||||
rails (>= 3.2.0)
|
rails (>= 3.2.0)
|
||||||
gherkin-ruby (0.3.2)
|
gherkin-ruby (0.3.2)
|
||||||
gitaly-proto (0.45.0)
|
gitaly-proto (0.48.0)
|
||||||
google-protobuf (~> 3.1)
|
google-protobuf (~> 3.1)
|
||||||
grpc (~> 1.0)
|
grpc (~> 1.0)
|
||||||
github-linguist (4.7.6)
|
github-linguist (4.7.6)
|
||||||
|
@ -1026,7 +1026,7 @@ DEPENDENCIES
|
||||||
gettext (~> 3.2.2)
|
gettext (~> 3.2.2)
|
||||||
gettext_i18n_rails (~> 1.8.0)
|
gettext_i18n_rails (~> 1.8.0)
|
||||||
gettext_i18n_rails_js (~> 1.2.0)
|
gettext_i18n_rails_js (~> 1.2.0)
|
||||||
gitaly-proto (~> 0.45.0)
|
gitaly-proto (~> 0.48.0)
|
||||||
github-linguist (~> 4.7.0)
|
github-linguist (~> 4.7.0)
|
||||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||||
gitlab-markup (~> 1.6.2)
|
gitlab-markup (~> 1.6.2)
|
||||||
|
|
|
@ -25,6 +25,11 @@
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
viewType: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'child',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mixins: [
|
mixins: [
|
||||||
pipelinesMixin,
|
pipelinesMixin,
|
||||||
|
@ -110,6 +115,7 @@
|
||||||
:pipelines="state.pipelines"
|
:pipelines="state.pipelines"
|
||||||
:update-graph-dropdown="updateGraphDropdown"
|
:update-graph-dropdown="updateGraphDropdown"
|
||||||
:auto-devops-help-path="autoDevopsHelpPath"
|
:auto-devops-help-path="autoDevopsHelpPath"
|
||||||
|
:view-type="viewType"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Cookies from 'js-cookie';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import bp from './breakpoints';
|
import bp from './breakpoints';
|
||||||
|
|
||||||
export default class NewNavSidebar {
|
export default class ContextualSidebar {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initDomElements();
|
this.initDomElements();
|
||||||
this.render();
|
this.render();
|
||||||
|
@ -55,7 +55,7 @@ export default class NewNavSidebar {
|
||||||
this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
|
this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
|
||||||
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
|
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
|
||||||
}
|
}
|
||||||
NewNavSidebar.setCollapsedCookie(collapsed);
|
ContextualSidebar.setCollapsedCookie(collapsed);
|
||||||
|
|
||||||
this.toggleSidebarOverflow();
|
this.toggleSidebarOverflow();
|
||||||
}
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
/* eslint-disable class-methods-use-this */
|
|
||||||
|
|
||||||
import './lib/utils/url_utility';
|
import './lib/utils/url_utility';
|
||||||
import FilesCommentButton from './files_comment_button';
|
import FilesCommentButton from './files_comment_button';
|
||||||
import SingleFileDiff from './single_file_diff';
|
import SingleFileDiff from './single_file_diff';
|
||||||
|
@ -8,7 +6,7 @@ import imageDiffHelper from './image_diff/helpers/index';
|
||||||
const UNFOLD_COUNT = 20;
|
const UNFOLD_COUNT = 20;
|
||||||
let isBound = false;
|
let isBound = false;
|
||||||
|
|
||||||
class Diff {
|
export default class Diff {
|
||||||
constructor() {
|
constructor() {
|
||||||
const $diffFile = $('.files .diff-file');
|
const $diffFile = $('.files .diff-file');
|
||||||
|
|
||||||
|
@ -104,7 +102,7 @@ class Diff {
|
||||||
}
|
}
|
||||||
this.highlightSelectedLine();
|
this.highlightSelectedLine();
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
handleParallelLineDown(e) {
|
handleParallelLineDown(e) {
|
||||||
const line = $(e.currentTarget);
|
const line = $(e.currentTarget);
|
||||||
const table = line.closest('table');
|
const table = line.closest('table');
|
||||||
|
@ -116,11 +114,11 @@ class Diff {
|
||||||
table.addClass(`${lineClass}-selected`);
|
table.addClass(`${lineClass}-selected`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
diffViewType() {
|
diffViewType() {
|
||||||
return $('.inline-parallel-buttons a.active').data('view-type');
|
return $('.inline-parallel-buttons a.active').data('view-type');
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
lineNumbers(line) {
|
lineNumbers(line) {
|
||||||
const children = line.find('.diff-line-num').toArray();
|
const children = line.find('.diff-line-num').toArray();
|
||||||
if (children.length !== 2) {
|
if (children.length !== 2) {
|
||||||
|
@ -128,7 +126,7 @@ class Diff {
|
||||||
}
|
}
|
||||||
return children.map(elm => parseInt($(elm).data('linenumber'), 10) || 0);
|
return children.map(elm => parseInt($(elm).data('linenumber'), 10) || 0);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
highlightSelectedLine() {
|
highlightSelectedLine() {
|
||||||
const hash = gl.utils.getLocationHash();
|
const hash = gl.utils.getLocationHash();
|
||||||
const $diffFiles = $('.diff-file');
|
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 NewBranchForm */
|
||||||
/* global NotificationsForm */
|
/* global NotificationsForm */
|
||||||
/* global NotificationsDropdown */
|
/* global NotificationsDropdown */
|
||||||
/* global GroupAvatar */
|
import groupAvatar from './group_avatar';
|
||||||
|
import GroupLabelSubscription from './group_label_subscription';
|
||||||
/* global LineHighlighter */
|
/* global LineHighlighter */
|
||||||
import BuildArtifacts from './build_artifacts';
|
import BuildArtifacts from './build_artifacts';
|
||||||
import CILintEditor from './ci_lint_editor';
|
import CILintEditor from './ci_lint_editor';
|
||||||
/* global GroupsSelect */
|
import groupsSelect from './groups_select';
|
||||||
/* global Search */
|
/* global Search */
|
||||||
/* global Admin */
|
/* global Admin */
|
||||||
/* global NamespaceSelects */
|
/* global NamespaceSelects */
|
||||||
|
@ -87,6 +88,7 @@ import U2FAuthenticate from './u2f/authenticate';
|
||||||
import Members from './members';
|
import Members from './members';
|
||||||
import memberExpirationDate from './member_expiration_date';
|
import memberExpirationDate from './member_expiration_date';
|
||||||
import DueDateSelectors from './due_date_select';
|
import DueDateSelectors from './due_date_select';
|
||||||
|
import Diff from './diff';
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var Dispatcher;
|
var Dispatcher;
|
||||||
|
@ -237,8 +239,9 @@ import DueDateSelectors from './due_date_select';
|
||||||
new GLForm($('.milestone-form'), true);
|
new GLForm($('.milestone-form'), true);
|
||||||
break;
|
break;
|
||||||
case 'projects:compare:show':
|
case 'projects:compare:show':
|
||||||
new gl.Diff();
|
new Diff();
|
||||||
initChangesDropdown();
|
const paddingTop = 16;
|
||||||
|
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
|
||||||
break;
|
break;
|
||||||
case 'projects:branches:new':
|
case 'projects:branches:new':
|
||||||
case 'projects:branches:create':
|
case 'projects:branches:create':
|
||||||
|
@ -273,7 +276,7 @@ import DueDateSelectors from './due_date_select';
|
||||||
}
|
}
|
||||||
case 'projects:merge_requests:creations:diffs':
|
case 'projects:merge_requests:creations:diffs':
|
||||||
case 'projects:merge_requests:edit':
|
case 'projects:merge_requests:edit':
|
||||||
new gl.Diff();
|
new Diff();
|
||||||
shortcut_handler = new ShortcutsNavigation();
|
shortcut_handler = new ShortcutsNavigation();
|
||||||
new GLForm($('.merge-request-form'), true);
|
new GLForm($('.merge-request-form'), true);
|
||||||
new IssuableForm($('.merge-request-form'));
|
new IssuableForm($('.merge-request-form'));
|
||||||
|
@ -307,7 +310,7 @@ import DueDateSelectors from './due_date_select';
|
||||||
new GLForm($('.release-form'), true);
|
new GLForm($('.release-form'), true);
|
||||||
break;
|
break;
|
||||||
case 'projects:merge_requests:show':
|
case 'projects:merge_requests:show':
|
||||||
new gl.Diff();
|
new Diff();
|
||||||
shortcut_handler = new ShortcutsIssuable(true);
|
shortcut_handler = new ShortcutsIssuable(true);
|
||||||
new ZenMode();
|
new ZenMode();
|
||||||
|
|
||||||
|
@ -323,7 +326,7 @@ import DueDateSelectors from './due_date_select';
|
||||||
new gl.Activities();
|
new gl.Activities();
|
||||||
break;
|
break;
|
||||||
case 'projects:commit:show':
|
case 'projects:commit:show':
|
||||||
new gl.Diff();
|
new Diff();
|
||||||
new ZenMode();
|
new ZenMode();
|
||||||
shortcut_handler = new ShortcutsNavigation();
|
shortcut_handler = new ShortcutsNavigation();
|
||||||
new MiniPipelineGraph({
|
new MiniPipelineGraph({
|
||||||
|
@ -411,7 +414,7 @@ import DueDateSelectors from './due_date_select';
|
||||||
break;
|
break;
|
||||||
case 'projects:project_members:index':
|
case 'projects:project_members:index':
|
||||||
memberExpirationDate('.js-access-expiration-date-groups');
|
memberExpirationDate('.js-access-expiration-date-groups');
|
||||||
new GroupsSelect();
|
groupsSelect();
|
||||||
memberExpirationDate();
|
memberExpirationDate();
|
||||||
new Members();
|
new Members();
|
||||||
new UsersSelect();
|
new UsersSelect();
|
||||||
|
@ -422,11 +425,11 @@ import DueDateSelectors from './due_date_select';
|
||||||
case 'admin:groups:create':
|
case 'admin:groups:create':
|
||||||
BindInOut.initAll();
|
BindInOut.initAll();
|
||||||
new Group();
|
new Group();
|
||||||
new GroupAvatar();
|
groupAvatar();
|
||||||
break;
|
break;
|
||||||
case 'groups:edit':
|
case 'groups:edit':
|
||||||
case 'admin:groups:edit':
|
case 'admin:groups:edit':
|
||||||
new GroupAvatar();
|
groupAvatar();
|
||||||
break;
|
break;
|
||||||
case 'projects:tree:show':
|
case 'projects:tree:show':
|
||||||
shortcut_handler = new ShortcutsNavigation();
|
shortcut_handler = new ShortcutsNavigation();
|
||||||
|
@ -473,7 +476,7 @@ import DueDateSelectors from './due_date_select';
|
||||||
const $el = $(el);
|
const $el = $(el);
|
||||||
|
|
||||||
if ($el.find('.dropdown-group-label').length) {
|
if ($el.find('.dropdown-group-label').length) {
|
||||||
new gl.GroupLabelSubscription($el);
|
new GroupLabelSubscription($el);
|
||||||
} else {
|
} else {
|
||||||
new gl.ProjectLabelSubscription($el);
|
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
|
/* Developer beware! Do not add logic to showButton or hideButton
|
||||||
* that will force a reflow. Doing so will create a signficant performance
|
* that will force a reflow. Doing so will create a signficant performance
|
||||||
* bottleneck for pages with large diffs. For a comprehensive list of what
|
* bottleneck for pages with large diffs. For a comprehensive list of what
|
||||||
|
@ -20,8 +17,10 @@ const DIFF_EXPANDED_CLASS = 'diff-expanded';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
init($diffFile) {
|
init($diffFile) {
|
||||||
/* Caching is used only when the following members are *true*. This is because there are likely to be
|
/* Caching is used only when the following members are *true*.
|
||||||
* differently configured versions of diffs in the same session. However if these values are true, they
|
* 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 */
|
* will be true in all cases */
|
||||||
|
|
||||||
if (!this.userCanCreateNote) {
|
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 */
|
export default function groupAvatar() {
|
||||||
|
$('.js-choose-group-avatar-button').on('click', function onClickGroupAvatar() {
|
||||||
window.GroupAvatar = (function() {
|
const form = $(this).closest('form');
|
||||||
function GroupAvatar() {
|
return form.find('.js-group-avatar-input').click();
|
||||||
$('.js-choose-group-avatar-button').on("click", function() {
|
});
|
||||||
var form;
|
$('.js-group-avatar-input').on('change', function onChangeAvatarInput() {
|
||||||
form = $(this).closest("form");
|
const form = $(this).closest('form');
|
||||||
return form.find(".js-group-avatar-input").click();
|
// eslint-disable-next-line no-useless-escape
|
||||||
});
|
const filename = $(this).val().replace(/^.*[\\\/]/, '');
|
||||||
$('.js-group-avatar-input').on("change", function() {
|
return form.find('.js-avatar-filename').text(filename);
|
||||||
var filename, form;
|
});
|
||||||
form = $(this).closest("form");
|
}
|
||||||
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 */
|
export default class GroupLabelSubscription {
|
||||||
|
|
||||||
class GroupLabelSubscription {
|
|
||||||
constructor(container) {
|
constructor(container) {
|
||||||
const $container = $(container);
|
const $container = $(container);
|
||||||
this.$dropdown = $container.find('.dropdown');
|
this.$dropdown = $container.find('.dropdown');
|
||||||
|
@ -18,7 +16,7 @@ class GroupLabelSubscription {
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: url
|
url,
|
||||||
}).done(() => {
|
}).done(() => {
|
||||||
this.toggleSubscriptionButtons();
|
this.toggleSubscriptionButtons();
|
||||||
this.$unsubscribeButtons.removeAttr('data-url');
|
this.$unsubscribeButtons.removeAttr('data-url');
|
||||||
|
@ -35,7 +33,7 @@ class GroupLabelSubscription {
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: url
|
url,
|
||||||
}).done(() => {
|
}).done(() => {
|
||||||
this.toggleSubscriptionButtons();
|
this.toggleSubscriptionButtons();
|
||||||
});
|
});
|
||||||
|
@ -47,6 +45,3 @@ class GroupLabelSubscription {
|
||||||
this.$unsubscribeButtons.toggleClass('hidden');
|
this.$unsubscribeButtons.toggleClass('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.gl = window.gl || {};
|
|
||||||
window.gl.GroupLabelSubscription = GroupLabelSubscription;
|
|
||||||
|
|
|
@ -1,121 +1,86 @@
|
||||||
/* 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 Api from './api';
|
||||||
import { normalizeCRLFHeaders } from './lib/utils/common_utils';
|
import { normalizeCRLFHeaders } from './lib/utils/common_utils';
|
||||||
|
|
||||||
var slice = [].slice;
|
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',
|
||||||
|
multiple: $select.hasClass('multiselect'),
|
||||||
|
minimumInputLength: 0,
|
||||||
|
ajax: {
|
||||||
|
url: Api.buildUrl(Api.groupsPath),
|
||||||
|
dataType: 'json',
|
||||||
|
quietMillis: 250,
|
||||||
|
transport(params) {
|
||||||
|
return $.ajax(params)
|
||||||
|
.then((data, status, xhr) => {
|
||||||
|
const results = data || [];
|
||||||
|
|
||||||
window.GroupsSelect = (function() {
|
const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders());
|
||||||
function GroupsSelect() {
|
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
|
||||||
$('.ajax-groups-select').each((function(_this) {
|
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
|
||||||
const self = _this;
|
const more = currentPage < totalPages;
|
||||||
|
|
||||||
return function(i, select) {
|
|
||||||
var all_available, skip_groups;
|
|
||||||
const $select = $(select);
|
|
||||||
all_available = $select.data('all-available');
|
|
||||||
skip_groups = $select.data('skip-groups') || [];
|
|
||||||
|
|
||||||
$select.select2({
|
|
||||||
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) => {
|
|
||||||
const results = data || [];
|
|
||||||
|
|
||||||
const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders());
|
|
||||||
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
|
|
||||||
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
|
|
||||||
const more = currentPage < totalPages;
|
|
||||||
|
|
||||||
return {
|
|
||||||
results,
|
|
||||||
pagination: {
|
|
||||||
more,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}).then(params.success).fail(params.error);
|
|
||||||
},
|
|
||||||
data: function (search, page) {
|
|
||||||
return {
|
|
||||||
search,
|
|
||||||
page,
|
|
||||||
per_page: GroupsSelect.PER_PAGE,
|
|
||||||
all_available,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
results: function (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);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
results,
|
results,
|
||||||
page,
|
pagination: {
|
||||||
more,
|
more,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
})
|
||||||
},
|
.then(params.success)
|
||||||
initSelection: function(element, callback) {
|
.fail(params.error);
|
||||||
var id;
|
},
|
||||||
id = $(element).val();
|
data(search, page) {
|
||||||
if (id !== "") {
|
return {
|
||||||
return Api.group(id, callback);
|
search,
|
||||||
}
|
page,
|
||||||
},
|
per_page: window.GROUP_SELECT_PER_PAGE,
|
||||||
formatResult: function() {
|
all_available: allAvailable,
|
||||||
var args;
|
};
|
||||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
},
|
||||||
return self.formatResult.apply(self, args);
|
results(data, page) {
|
||||||
},
|
if (data.length) return { results: [] };
|
||||||
formatSelection: function() {
|
|
||||||
var args;
|
|
||||||
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
|
||||||
return self.formatSelection.apply(self, args);
|
|
||||||
},
|
|
||||||
dropdownCssClass: "ajax-groups-dropdown select2-infinite",
|
|
||||||
// we do not want to escape markup since we are displaying html in results
|
|
||||||
escapeMarkup: function(m) {
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.dropdown = document.querySelector('.select2-infinite .select2-results');
|
const groups = data.length ? data : data.results || [];
|
||||||
|
const more = data.pagination ? data.pagination.more : false;
|
||||||
|
const results = groups.filter(group => skipGroups.indexOf(group.id) === -1);
|
||||||
|
|
||||||
$select.on('select2-loaded', self.forceOverflow.bind(self));
|
return {
|
||||||
};
|
results,
|
||||||
})(this));
|
page,
|
||||||
}
|
more,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
initSelection(element, callback) {
|
||||||
|
const id = $(element).val();
|
||||||
|
if (id !== '') {
|
||||||
|
return Api.group(id, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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(object) {
|
||||||
|
return object.full_name;
|
||||||
|
},
|
||||||
|
dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
|
||||||
|
// we do not want to escape markup since we are displaying html in results
|
||||||
|
escapeMarkup(m) {
|
||||||
|
return m;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
GroupsSelect.prototype.formatResult = function(group) {
|
$select.on('select2-loaded', () => {
|
||||||
var avatar;
|
const dropdown = document.querySelector('.select2-infinite .select2-results');
|
||||||
if (group.avatar_url) {
|
dropdown.style.height = `${Math.floor(dropdown.scrollHeight)}px`;
|
||||||
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';
|
import stickyMonitor from './lib/utils/sticky';
|
||||||
|
|
||||||
export default () => {
|
export default (stickyTop) => {
|
||||||
stickyMonitor(document.querySelector('.js-diff-files-changed'));
|
stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop);
|
||||||
|
|
||||||
$('.js-diff-stats-dropdown').glDropdown({
|
$('.js-diff-stats-dropdown').glDropdown({
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
|
|
@ -51,20 +51,19 @@ const PARTICIPANTS_ROW_COUNT = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
IssuableContext.prototype.initParticipants = function() {
|
IssuableContext.prototype.initParticipants = function() {
|
||||||
$(document).on("click", ".js-participants-more", this.toggleHiddenParticipants);
|
$(document).on('click', '.js-participants-more', this.toggleHiddenParticipants);
|
||||||
return $(".js-participants-author").each(function(i) {
|
return $('.js-participants-author').each(function(i) {
|
||||||
if (i >= PARTICIPANTS_ROW_COUNT) {
|
if (i >= PARTICIPANTS_ROW_COUNT) {
|
||||||
return $(this).addClass("js-participants-hidden").hide();
|
return $(this).addClass('js-participants-hidden').hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
IssuableContext.prototype.toggleHiddenParticipants = function(e) {
|
IssuableContext.prototype.toggleHiddenParticipants = function() {
|
||||||
var currentText, lessText, originalText;
|
const currentText = $(this).text().trim();
|
||||||
e.preventDefault();
|
const lessText = $(this).data('less-text');
|
||||||
currentText = $(this).text().trim();
|
const originalText = $(this).data('original-text');
|
||||||
lessText = $(this).data("less-text");
|
|
||||||
originalText = $(this).data("original-text");
|
|
||||||
if (currentText === originalText) {
|
if (currentText === originalText) {
|
||||||
$(this).text(lessText);
|
$(this).text(lessText);
|
||||||
|
|
||||||
|
@ -73,7 +72,7 @@ const PARTICIPANTS_ROW_COUNT = 7;
|
||||||
$(this).text(originalText);
|
$(this).text(originalText);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(".js-participants-hidden").toggle();
|
$('.js-participants-hidden').toggle();
|
||||||
};
|
};
|
||||||
|
|
||||||
return IssuableContext;
|
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 */
|
/* 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 _ from 'underscore';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import NewNavSidebar from './new_sidebar';
|
import ContextualSidebar from './contextual_sidebar';
|
||||||
import initFlyOutNav from './fly_out_nav';
|
import initFlyOutNav from './fly_out_nav';
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
@ -51,8 +51,8 @@ import initFlyOutNav from './fly_out_nav';
|
||||||
});
|
});
|
||||||
|
|
||||||
$(() => {
|
$(() => {
|
||||||
const newNavSidebar = new NewNavSidebar();
|
const contextualSidebar = new ContextualSidebar();
|
||||||
newNavSidebar.bindEvents();
|
contextualSidebar.bindEvents();
|
||||||
|
|
||||||
initFlyOutNav();
|
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;
|
if (!el) return;
|
||||||
|
|
||||||
const computedStyle = window.getComputedStyle(el);
|
if (typeof CSS === 'undefined' || !(CSS.supports('(position: -webkit-sticky) or (position: sticky)'))) return;
|
||||||
|
|
||||||
if (!/sticky/.test(computedStyle.position)) return;
|
|
||||||
|
|
||||||
const stickyTop = parseInt(computedStyle.top, 10);
|
|
||||||
|
|
||||||
document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop, insertPlaceholder), {
|
document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop, insertPlaceholder), {
|
||||||
passive: true,
|
passive: true,
|
||||||
|
|
|
@ -50,16 +50,11 @@ import './compare_autocomplete';
|
||||||
import './confirm_danger_modal';
|
import './confirm_danger_modal';
|
||||||
import './copy_as_gfm';
|
import './copy_as_gfm';
|
||||||
import './copy_to_clipboard';
|
import './copy_to_clipboard';
|
||||||
import './diff';
|
|
||||||
import './files_comment_button';
|
|
||||||
import Flash, { removeFlashClickListener } from './flash';
|
import Flash, { removeFlashClickListener } from './flash';
|
||||||
import './gl_dropdown';
|
import './gl_dropdown';
|
||||||
import './gl_field_error';
|
import './gl_field_error';
|
||||||
import './gl_field_errors';
|
import './gl_field_errors';
|
||||||
import './gl_form';
|
import './gl_form';
|
||||||
import './group_avatar';
|
|
||||||
import './group_label_subscription';
|
|
||||||
import './groups_select';
|
|
||||||
import './header';
|
import './header';
|
||||||
import './importer_status';
|
import './importer_status';
|
||||||
import './issuable_index';
|
import './issuable_index';
|
||||||
|
|
|
@ -11,8 +11,8 @@ import {
|
||||||
handleLocationHash,
|
handleLocationHash,
|
||||||
isMetaClick,
|
isMetaClick,
|
||||||
} from './lib/utils/common_utils';
|
} from './lib/utils/common_utils';
|
||||||
|
|
||||||
import initDiscussionTab from './image_diff/init_discussion_tab';
|
import initDiscussionTab from './image_diff/init_discussion_tab';
|
||||||
|
import Diff from './diff';
|
||||||
|
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
// MergeRequestTabs
|
// MergeRequestTabs
|
||||||
|
@ -67,6 +67,10 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
|
||||||
class MergeRequestTabs {
|
class MergeRequestTabs {
|
||||||
|
|
||||||
constructor({ action, setUrl, stubLocation } = {}) {
|
constructor({ action, setUrl, stubLocation } = {}) {
|
||||||
|
const mergeRequestTabs = document.querySelector('.js-tabs-affix');
|
||||||
|
const navbar = document.querySelector('.navbar-gitlab');
|
||||||
|
const paddingTop = 16;
|
||||||
|
|
||||||
this.diffsLoaded = false;
|
this.diffsLoaded = false;
|
||||||
this.pipelinesLoaded = false;
|
this.pipelinesLoaded = false;
|
||||||
this.commitsLoaded = false;
|
this.commitsLoaded = false;
|
||||||
|
@ -76,6 +80,11 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
|
||||||
this.setCurrentAction = this.setCurrentAction.bind(this);
|
this.setCurrentAction = this.setCurrentAction.bind(this);
|
||||||
this.tabShown = this.tabShown.bind(this);
|
this.tabShown = this.tabShown.bind(this);
|
||||||
this.showTab = this.showTab.bind(this);
|
this.showTab = this.showTab.bind(this);
|
||||||
|
this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
|
||||||
|
|
||||||
|
if (mergeRequestTabs) {
|
||||||
|
this.stickyTop += mergeRequestTabs.offsetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
if (stubLocation) {
|
if (stubLocation) {
|
||||||
location = stubLocation;
|
location = stubLocation;
|
||||||
|
@ -278,7 +287,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
|
||||||
const $container = $('#diffs');
|
const $container = $('#diffs');
|
||||||
$container.html(data.html);
|
$container.html(data.html);
|
||||||
|
|
||||||
initChangesDropdown();
|
initChangesDropdown(this.stickyTop);
|
||||||
|
|
||||||
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
|
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
|
||||||
gl.diffNotesCompileComponents();
|
gl.diffNotesCompileComponents();
|
||||||
|
@ -292,7 +301,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
|
||||||
}
|
}
|
||||||
this.diffsLoaded = true;
|
this.diffsLoaded = true;
|
||||||
|
|
||||||
new gl.Diff();
|
new Diff();
|
||||||
this.scrollToElement('#diffs');
|
this.scrollToElement('#diffs');
|
||||||
|
|
||||||
$('.diff-file').each((i, el) => {
|
$('.diff-file').each((i, el) => {
|
||||||
|
|
|
@ -1280,10 +1280,12 @@ export default class Notes {
|
||||||
* Get data from Form attributes to use for saving/submitting comment.
|
* Get data from Form attributes to use for saving/submitting comment.
|
||||||
*/
|
*/
|
||||||
getFormData($form) {
|
getFormData($form) {
|
||||||
|
const content = $form.find('.js-note-text').val();
|
||||||
return {
|
return {
|
||||||
formData: $form.serialize(),
|
formData: $form.serialize(),
|
||||||
formContent: _.escape($form.find('.js-note-text').val()),
|
formContent: _.escape(content),
|
||||||
formAction: $form.attr('action'),
|
formAction: $form.attr('action'),
|
||||||
|
formContentOriginal: content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1415,7 +1417,7 @@ export default class Notes {
|
||||||
const isMainForm = $form.hasClass('js-main-target-form');
|
const isMainForm = $form.hasClass('js-main-target-form');
|
||||||
const isDiscussionForm = $form.hasClass('js-discussion-note-form');
|
const isDiscussionForm = $form.hasClass('js-discussion-note-form');
|
||||||
const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
|
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 noteUniqueId;
|
||||||
let systemNoteUniqueId;
|
let systemNoteUniqueId;
|
||||||
let hasQuickActions = false;
|
let hasQuickActions = false;
|
||||||
|
@ -1574,7 +1576,7 @@ export default class Notes {
|
||||||
$form = $notesContainer.parent().find('form');
|
$form = $notesContainer.parent().find('form');
|
||||||
}
|
}
|
||||||
|
|
||||||
$form.find('.js-note-text').val(formContent);
|
$form.find('.js-note-text').val(formContentOriginal);
|
||||||
this.reenableTargetFormSubmitButton(e);
|
this.reenableTargetFormSubmitButton(e);
|
||||||
this.addNoteError($form);
|
this.addNoteError($form);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
|
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
|
||||||
import issueNoteEditedText from './issue_note_edited_text.vue';
|
import issueNoteEditedText from './issue_note_edited_text.vue';
|
||||||
import issueNoteForm from './issue_note_form.vue';
|
import issueNoteForm from './issue_note_form.vue';
|
||||||
import placeholderNote from './issue_placeholder_note.vue';
|
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
|
||||||
import placeholderSystemNote from './issue_placeholder_system_note.vue';
|
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
|
||||||
import autosave from '../mixins/autosave';
|
import autosave from '../mixins/autosave';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
import * as constants from '../constants';
|
import * as constants from '../constants';
|
||||||
import issueNote from './issue_note.vue';
|
import issueNote from './issue_note.vue';
|
||||||
import issueDiscussion from './issue_discussion.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 issueCommentForm from './issue_comment_form.vue';
|
||||||
import placeholderNote from './issue_placeholder_note.vue';
|
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
|
||||||
import placeholderSystemNote from './issue_placeholder_system_note.vue';
|
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
|
||||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
components: {
|
components: {
|
||||||
issueNote,
|
issueNote,
|
||||||
issueDiscussion,
|
issueDiscussion,
|
||||||
issueSystemNote,
|
systemNote,
|
||||||
issueCommentForm,
|
issueCommentForm,
|
||||||
loadingIcon,
|
loadingIcon,
|
||||||
placeholderNote,
|
placeholderNote,
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
}
|
}
|
||||||
return placeholderNote;
|
return placeholderNote;
|
||||||
} else if (note.individual_note) {
|
} else if (note.individual_note) {
|
||||||
return note.notes[0].system ? issueSystemNote : issueNote;
|
return note.notes[0].system ? systemNote : issueNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
return issueDiscussion;
|
return issueDiscussion;
|
||||||
|
|
|
@ -12,6 +12,15 @@
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
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: {
|
components: {
|
||||||
tablePagination,
|
tablePagination,
|
||||||
|
@ -187,7 +196,7 @@
|
||||||
:empty-state-svg-path="emptyStateSvgPath"
|
:empty-state-svg-path="emptyStateSvgPath"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<error-state
|
<error-state
|
||||||
v-if="shouldRenderErrorState"
|
v-if="shouldRenderErrorState"
|
||||||
:error-state-svg-path="errorStateSvgPath"
|
:error-state-svg-path="errorStateSvgPath"
|
||||||
/>
|
/>
|
||||||
|
@ -206,6 +215,7 @@
|
||||||
:pipelines="state.pipelines"
|
:pipelines="state.pipelines"
|
||||||
:update-graph-dropdown="updateGraphDropdown"
|
:update-graph-dropdown="updateGraphDropdown"
|
||||||
:auto-devops-help-path="autoDevopsPath"
|
:auto-devops-help-path="autoDevopsPath"
|
||||||
|
:view-type="viewType"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
viewType: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
pipelinesTableRowComponent,
|
pipelinesTableRowComponent,
|
||||||
|
@ -59,6 +63,7 @@
|
||||||
:pipeline="model"
|
:pipeline="model"
|
||||||
:update-graph-dropdown="updateGraphDropdown"
|
:update-graph-dropdown="updateGraphDropdown"
|
||||||
:auto-devops-help-path="autoDevopsHelpPath"
|
:auto-devops-help-path="autoDevopsHelpPath"
|
||||||
|
:view-type="viewType"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -29,6 +29,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
viewType: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
asyncButtonComponent,
|
asyncButtonComponent,
|
||||||
|
@ -203,9 +207,13 @@ export default {
|
||||||
|
|
||||||
displayPipelineActions() {
|
displayPipelineActions() {
|
||||||
return this.pipeline.flags.retryable ||
|
return this.pipeline.flags.retryable ||
|
||||||
this.pipeline.flags.cancelable ||
|
this.pipeline.flags.cancelable ||
|
||||||
this.pipeline.details.manual_actions.length ||
|
this.pipeline.details.manual_actions.length ||
|
||||||
this.pipeline.details.artifacts.length;
|
this.pipeline.details.artifacts.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
isChildView() {
|
||||||
|
return this.viewType === 'child';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -218,7 +226,10 @@ export default {
|
||||||
Status
|
Status
|
||||||
</div>
|
</div>
|
||||||
<div class="table-mobile-content">
|
<div class="table-mobile-content">
|
||||||
<ci-badge :status="pipelineStatus"/>
|
<ci-badge
|
||||||
|
:status="pipelineStatus"
|
||||||
|
:show-text="!isChildView"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -240,7 +251,9 @@ export default {
|
||||||
:commit-url="commitUrl"
|
:commit-url="commitUrl"
|
||||||
:short-sha="commitShortSha"
|
:short-sha="commitShortSha"
|
||||||
:title="commitTitle"
|
:title="commitTitle"
|
||||||
:author="commitAuthor"/>
|
:author="commitAuthor"
|
||||||
|
:show-branch="!isChildView"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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 PopupDialog from '../../vue_shared/components/popup_dialog.vue';
|
||||||
import Store from '../stores/repo_store';
|
import Store from '../stores/repo_store';
|
||||||
import Helper from '../helpers/repo_helper';
|
import Helper from '../helpers/repo_helper';
|
||||||
|
import Service from '../services/repo_service';
|
||||||
import MonacoLoaderHelper from '../helpers/monaco_loader_helper';
|
import MonacoLoaderHelper from '../helpers/monaco_loader_helper';
|
||||||
|
import eventHub from '../event_hub';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
@ -24,12 +26,19 @@ export default {
|
||||||
PopupDialog,
|
PopupDialog,
|
||||||
RepoPreview,
|
RepoPreview,
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
eventHub.$on('createNewBranch', this.createNewBranch);
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
Helper.getContent().catch(Helper.loadingError);
|
Helper.getContent().catch(Helper.loadingError);
|
||||||
},
|
},
|
||||||
|
destroyed() {
|
||||||
|
eventHub.$off('createNewBranch', this.createNewBranch);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getCurrentLocation() {
|
||||||
|
return location.href;
|
||||||
|
},
|
||||||
toggleDialogOpen(toggle) {
|
toggleDialogOpen(toggle) {
|
||||||
this.dialog.open = toggle;
|
this.dialog.open = toggle;
|
||||||
},
|
},
|
||||||
|
@ -37,9 +46,30 @@ export default {
|
||||||
dialogSubmitted(status) {
|
dialogSubmitted(status) {
|
||||||
this.toggleDialogOpen(false);
|
this.toggleDialogOpen(false);
|
||||||
this.dialog.status = status;
|
this.dialog.status = status;
|
||||||
},
|
|
||||||
|
|
||||||
|
// remove tmp files
|
||||||
|
Helper.removeAllTmpFiles('openedFiles');
|
||||||
|
Helper.removeAllTmpFiles('files');
|
||||||
|
},
|
||||||
toggleBlobView: Store.toggleBlobView,
|
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>
|
</script>
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default {
|
||||||
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
|
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
|
||||||
const commitMessage = this.commitMessage;
|
const commitMessage = this.commitMessage;
|
||||||
const actions = this.changedFiles.map(f => ({
|
const actions = this.changedFiles.map(f => ({
|
||||||
action: 'update',
|
action: f.tempFile ? 'create' : 'update',
|
||||||
file_path: f.path,
|
file_path: f.path,
|
||||||
content: f.newContent,
|
content: f.newContent,
|
||||||
}));
|
}));
|
||||||
|
@ -62,7 +62,6 @@ export default {
|
||||||
if (newBranch) {
|
if (newBranch) {
|
||||||
payload.start_branch = this.currentBranch;
|
payload.start_branch = this.currentBranch;
|
||||||
}
|
}
|
||||||
this.submitCommitsLoading = true;
|
|
||||||
Service.commitFiles(payload)
|
Service.commitFiles(payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.resetCommitState();
|
this.resetCommitState();
|
||||||
|
@ -78,6 +77,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
tryCommit(e, skipBranchCheck = false, newBranch = false) {
|
tryCommit(e, skipBranchCheck = false, newBranch = false) {
|
||||||
|
this.submitCommitsLoading = true;
|
||||||
|
|
||||||
if (skipBranchCheck) {
|
if (skipBranchCheck) {
|
||||||
this.makeCommit(newBranch);
|
this.makeCommit(newBranch);
|
||||||
} else {
|
} else {
|
||||||
|
@ -90,6 +91,7 @@ export default {
|
||||||
this.makeCommit(newBranch);
|
this.makeCommit(newBranch);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
this.submitCommitsLoading = false;
|
||||||
Flash('An error occurred while committing your changes');
|
Flash('An error occurred while committing your changes');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ const RepoEditor = {
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
Service.getRaw(this.activeFile.raw_path)
|
Service.getRaw(this.activeFile)
|
||||||
.then((rawResponse) => {
|
.then((rawResponse) => {
|
||||||
Store.blobRaw = rawResponse.data;
|
Store.blobRaw = rawResponse.data;
|
||||||
Store.activeFile.plain = rawResponse.data;
|
Store.activeFile.plain = rawResponse.data;
|
||||||
|
|
|
@ -11,7 +11,12 @@ const RepoFileButtons = {
|
||||||
mixins: [RepoMixin],
|
mixins: [RepoMixin],
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
showButtons() {
|
||||||
|
return this.activeFile.raw_path ||
|
||||||
|
this.activeFile.blame_path ||
|
||||||
|
this.activeFile.commits_path ||
|
||||||
|
this.activeFile.permalink;
|
||||||
|
},
|
||||||
rawDownloadButtonLabel() {
|
rawDownloadButtonLabel() {
|
||||||
return this.binary ? 'Download' : 'Raw';
|
return this.binary ? 'Download' : 'Raw';
|
||||||
},
|
},
|
||||||
|
@ -30,7 +35,10 @@ export default RepoFileButtons;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="repo-file-buttons">
|
<div
|
||||||
|
v-if="showButtons"
|
||||||
|
class="repo-file-buttons"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
:href="activeFile.raw_path"
|
:href="activeFile.raw_path"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
|
@ -18,8 +18,8 @@ const RepoTab = {
|
||||||
},
|
},
|
||||||
changedClass() {
|
changedClass() {
|
||||||
const tabChangedObj = {
|
const tabChangedObj = {
|
||||||
'fa-times close-icon': !this.tab.changed,
|
'fa-times close-icon': !this.tab.changed && !this.tab.tempFile,
|
||||||
'fa-circle unsaved-icon': this.tab.changed,
|
'fa-circle unsaved-icon': this.tab.changed || this.tab.tempFile,
|
||||||
};
|
};
|
||||||
return tabChangedObj;
|
return tabChangedObj;
|
||||||
},
|
},
|
||||||
|
@ -30,7 +30,7 @@ const RepoTab = {
|
||||||
Store.setActiveFiles(file);
|
Store.setActiveFiles(file);
|
||||||
},
|
},
|
||||||
closeTab(file) {
|
closeTab(file) {
|
||||||
if (file.changed) return;
|
if (file.changed || file.tempFile) return;
|
||||||
|
|
||||||
Store.removeFromOpenedFiles(file);
|
Store.removeFromOpenedFiles(file);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
|
|
||||||
import Service from '../services/repo_service';
|
import Service from '../services/repo_service';
|
||||||
import Store from '../stores/repo_store';
|
import Store from '../stores/repo_store';
|
||||||
import Flash from '../../flash';
|
import Flash from '../../flash';
|
||||||
|
@ -8,6 +7,7 @@ const RepoHelper = {
|
||||||
|
|
||||||
getDefaultActiveFile() {
|
getDefaultActiveFile() {
|
||||||
return {
|
return {
|
||||||
|
id: '',
|
||||||
active: true,
|
active: true,
|
||||||
binary: false,
|
binary: false,
|
||||||
extension: '',
|
extension: '',
|
||||||
|
@ -62,6 +62,7 @@ const RepoHelper = {
|
||||||
});
|
});
|
||||||
|
|
||||||
RepoHelper.updateHistoryEntry(tree.url, title);
|
RepoHelper.updateHistoryEntry(tree.url, title);
|
||||||
|
Store.path = tree.path;
|
||||||
},
|
},
|
||||||
|
|
||||||
setDirectoryToClosed(entry) {
|
setDirectoryToClosed(entry) {
|
||||||
|
@ -96,8 +97,8 @@ const RepoHelper = {
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (response.headers && response.headers['page-title']) data.pageTitle = decodeURI(response.headers['page-title']);
|
if (response.headers && response.headers['page-title']) data.pageTitle = decodeURI(response.headers['page-title']);
|
||||||
if (response.headers && response.headers['is-root'] && !Store.isInitialRoot) {
|
if (data.path && !Store.isInitialRoot) {
|
||||||
Store.isRoot = convertPermissionToBoolean(response.headers['is-root']);
|
Store.isRoot = data.path === '/';
|
||||||
Store.isInitialRoot = Store.isRoot;
|
Store.isInitialRoot = Store.isRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +111,7 @@ const RepoHelper = {
|
||||||
RepoHelper.setBinaryDataAsBase64(data);
|
RepoHelper.setBinaryDataAsBase64(data);
|
||||||
Store.setViewToPreview();
|
Store.setViewToPreview();
|
||||||
} else if (!Store.isPreviewView() && !data.render_error) {
|
} else if (!Store.isPreviewView() && !data.render_error) {
|
||||||
Service.getRaw(data.raw_path)
|
Service.getRaw(data)
|
||||||
.then((rawResponse) => {
|
.then((rawResponse) => {
|
||||||
Store.blobRaw = rawResponse.data;
|
Store.blobRaw = rawResponse.data;
|
||||||
data.plain = rawResponse.data;
|
data.plain = rawResponse.data;
|
||||||
|
@ -138,6 +139,10 @@ const RepoHelper = {
|
||||||
|
|
||||||
addToDirectory(file, data) {
|
addToDirectory(file, data) {
|
||||||
const tree = file || Store;
|
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));
|
const files = tree.files.concat(this.dataToListOfFiles(data, file ? file.level + 1 : 0));
|
||||||
|
|
||||||
tree.files = files;
|
tree.files = files;
|
||||||
|
@ -157,7 +162,18 @@ const RepoHelper = {
|
||||||
},
|
},
|
||||||
|
|
||||||
serializeRepoEntity(type, entity, level = 0) {
|
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 {
|
return {
|
||||||
id,
|
id,
|
||||||
|
@ -165,11 +181,14 @@ const RepoHelper = {
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
tree_url,
|
tree_url,
|
||||||
|
path,
|
||||||
level,
|
level,
|
||||||
|
tempFile,
|
||||||
icon: `fa-${icon}`,
|
icon: `fa-${icon}`,
|
||||||
files: [],
|
files: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
opened: false,
|
opened,
|
||||||
|
active,
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
lastCommit: last_commit ? {
|
lastCommit: last_commit ? {
|
||||||
url: `${Store.projectUrl}/commit/${last_commit.id}`,
|
url: `${Store.projectUrl}/commit/${last_commit.id}`,
|
||||||
|
@ -213,7 +232,7 @@ const RepoHelper = {
|
||||||
},
|
},
|
||||||
|
|
||||||
findOpenedFileFromActive() {
|
findOpenedFileFromActive() {
|
||||||
return Store.openedFiles.find(openedFile => Store.activeFile.url === openedFile.url);
|
return Store.openedFiles.find(openedFile => Store.activeFile.id === openedFile.id);
|
||||||
},
|
},
|
||||||
|
|
||||||
getFileFromPath(path) {
|
getFileFromPath(path) {
|
||||||
|
@ -223,6 +242,76 @@ const RepoHelper = {
|
||||||
loadingError() {
|
loadingError() {
|
||||||
Flash('Unable to load this content at this time.');
|
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;
|
export default RepoHelper;
|
||||||
|
|
|
@ -5,6 +5,8 @@ import Service from './services/repo_service';
|
||||||
import Store from './stores/repo_store';
|
import Store from './stores/repo_store';
|
||||||
import Repo from './components/repo.vue';
|
import Repo from './components/repo.vue';
|
||||||
import RepoEditButton from './components/repo_edit_button.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';
|
import Translate from '../vue_shared/translate';
|
||||||
|
|
||||||
function initDropdowns() {
|
function initDropdowns() {
|
||||||
|
@ -27,6 +29,7 @@ function setInitialStore(data) {
|
||||||
Store.service = Service;
|
Store.service = Service;
|
||||||
Store.service.url = data.url;
|
Store.service.url = data.url;
|
||||||
Store.service.refsUrl = data.refsUrl;
|
Store.service.refsUrl = data.refsUrl;
|
||||||
|
Store.path = data.currentPath;
|
||||||
Store.projectId = data.projectId;
|
Store.projectId = data.projectId;
|
||||||
Store.projectName = data.projectName;
|
Store.projectName = data.projectName;
|
||||||
Store.projectUrl = data.projectUrl;
|
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() {
|
function initRepoBundle() {
|
||||||
const repo = document.getElementById('repo');
|
const repo = document.getElementById('repo');
|
||||||
const editButton = document.querySelector('.editable-mode');
|
const editButton = document.querySelector('.editable-mode');
|
||||||
|
const newDropdownHolder = document.querySelector('.js-new-dropdown');
|
||||||
setInitialStore(repo.dataset);
|
setInitialStore(repo.dataset);
|
||||||
addEventsForNonVueEls();
|
addEventsForNonVueEls();
|
||||||
initDropdowns();
|
initDropdowns();
|
||||||
|
@ -73,6 +109,8 @@ function initRepoBundle() {
|
||||||
|
|
||||||
initRepo(repo);
|
initRepo(repo);
|
||||||
initRepoEditButton(editButton);
|
initRepoEditButton(editButton);
|
||||||
|
initNewBranchForm();
|
||||||
|
initNewDropdown(newDropdownHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(initRepoBundle);
|
$(initRepoBundle);
|
||||||
|
|
|
@ -8,7 +8,7 @@ const RepoMixin = {
|
||||||
|
|
||||||
changedFiles() {
|
changedFiles() {
|
||||||
const changedFileList = this.openedFiles
|
const changedFileList = this.openedFiles
|
||||||
.filter(file => file.changed);
|
.filter(file => file.changed || file.tempFile);
|
||||||
return changedFileList;
|
return changedFileList;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import csrf from '../../lib/utils/csrf';
|
||||||
import Store from '../stores/repo_store';
|
import Store from '../stores/repo_store';
|
||||||
import Api from '../../api';
|
import Api from '../../api';
|
||||||
import Helper from '../helpers/repo_helper';
|
import Helper from '../helpers/repo_helper';
|
||||||
|
|
||||||
|
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
|
||||||
|
|
||||||
const RepoService = {
|
const RepoService = {
|
||||||
url: '',
|
url: '',
|
||||||
options: {
|
options: {
|
||||||
|
@ -10,10 +13,17 @@ const RepoService = {
|
||||||
format: 'json',
|
format: 'json',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
createBranchPath: '/api/:version/projects/:id/repository/branches',
|
||||||
richExtensionRegExp: /md/,
|
richExtensionRegExp: /md/,
|
||||||
|
|
||||||
getRaw(url) {
|
getRaw(file) {
|
||||||
return axios.get(url, {
|
if (file.tempFile) {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return axios.get(file.raw_path, {
|
||||||
// Stop Axios from parsing a JSON file into a JS object
|
// Stop Axios from parsing a JSON file into a JS object
|
||||||
transformResponse: [res => res],
|
transformResponse: [res => res],
|
||||||
});
|
});
|
||||||
|
@ -73,6 +83,12 @@ const RepoService = {
|
||||||
.then(this.commitFlash);
|
.then(this.commitFlash);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createBranch(payload) {
|
||||||
|
const url = Api.buildUrl(this.createBranchPath)
|
||||||
|
.replace(':id', Store.projectId);
|
||||||
|
return axios.post(url, payload);
|
||||||
|
},
|
||||||
|
|
||||||
commitFlash(data) {
|
commitFlash(data) {
|
||||||
if (data.short_id && data.stats) {
|
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');
|
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: '',
|
projectId: '',
|
||||||
projectName: '',
|
projectName: '',
|
||||||
projectUrl: '',
|
projectUrl: '',
|
||||||
|
branchUrl: '',
|
||||||
blobRaw: '',
|
blobRaw: '',
|
||||||
currentBlobView: 'repo-preview',
|
currentBlobView: 'repo-preview',
|
||||||
openedFiles: [],
|
openedFiles: [],
|
||||||
|
@ -38,6 +39,7 @@ const RepoStore = {
|
||||||
newMrTemplateUrl: '',
|
newMrTemplateUrl: '',
|
||||||
branchChanged: false,
|
branchChanged: false,
|
||||||
commitMessage: '',
|
commitMessage: '',
|
||||||
|
path: '',
|
||||||
loading: {
|
loading: {
|
||||||
tree: false,
|
tree: false,
|
||||||
blob: false,
|
blob: false,
|
||||||
|
@ -76,21 +78,23 @@ const RepoStore = {
|
||||||
} else if (file.newContent || file.plain) {
|
} else if (file.newContent || file.plain) {
|
||||||
RepoStore.blobRaw = file.newContent || file.plain;
|
RepoStore.blobRaw = file.newContent || file.plain;
|
||||||
} else {
|
} else {
|
||||||
Service.getRaw(file.raw_path)
|
Service.getRaw(file)
|
||||||
.then((rawResponse) => {
|
.then((rawResponse) => {
|
||||||
RepoStore.blobRaw = rawResponse.data;
|
RepoStore.blobRaw = rawResponse.data;
|
||||||
Helper.findOpenedFileFromActive().plain = rawResponse.data;
|
Helper.findOpenedFileFromActive().plain = rawResponse.data;
|
||||||
}).catch(Helper.loadingError);
|
}).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.binary = file.binary;
|
||||||
RepoStore.setActiveLine(-1);
|
RepoStore.setActiveLine(-1);
|
||||||
},
|
},
|
||||||
|
|
||||||
setFileActivity(file, openedFile, i) {
|
setFileActivity(file, openedFile, i) {
|
||||||
const activeFile = openedFile;
|
const activeFile = openedFile;
|
||||||
activeFile.active = file.url === activeFile.url;
|
activeFile.active = file.id === activeFile.id;
|
||||||
|
|
||||||
if (activeFile.active) RepoStore.setActiveFile(activeFile, i);
|
if (activeFile.active) RepoStore.setActiveFile(activeFile, i);
|
||||||
|
|
||||||
|
@ -98,7 +102,7 @@ const RepoStore = {
|
||||||
},
|
},
|
||||||
|
|
||||||
setActiveFile(activeFile, i) {
|
setActiveFile(activeFile, i) {
|
||||||
RepoStore.activeFile = Object.assign({}, RepoStore.activeFile, activeFile);
|
RepoStore.activeFile = Object.assign({}, Helper.getDefaultActiveFile(), activeFile);
|
||||||
RepoStore.activeFileIndex = i;
|
RepoStore.activeFileIndex = i;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -120,6 +124,11 @@ const RepoStore = {
|
||||||
return openedFile.path !== file.path;
|
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.
|
// now activate the right tab based on what you closed.
|
||||||
if (RepoStore.openedFiles.length === 0) {
|
if (RepoStore.openedFiles.length === 0) {
|
||||||
RepoStore.activeFile = {};
|
RepoStore.activeFile = {};
|
||||||
|
@ -169,7 +178,7 @@ const RepoStore = {
|
||||||
// getters
|
// getters
|
||||||
|
|
||||||
isActiveFile(file) {
|
isActiveFile(file) {
|
||||||
return file && file.url === RepoStore.activeFile.url;
|
return file && file.id === RepoStore.activeFile.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
isPreviewView() {
|
isPreviewView() {
|
||||||
|
|
|
@ -286,6 +286,7 @@ export default {
|
||||||
<input
|
<input
|
||||||
id="remove-source-branch-input"
|
id="remove-source-branch-input"
|
||||||
v-model="removeSourceBranch"
|
v-model="removeSourceBranch"
|
||||||
|
class="js-remove-source-branch-checkbox"
|
||||||
:disabled="isRemoveSourceBranchButtonDisabled"
|
:disabled="isRemoveSourceBranchButtonDisabled"
|
||||||
type="checkbox"/> Remove source branch
|
type="checkbox"/> Remove source branch
|
||||||
</label>
|
</label>
|
||||||
|
@ -311,8 +312,8 @@ export default {
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="bold">
|
<span class="bold js-resolve-mr-widget-items-message">
|
||||||
The pipeline for this merge request has not succeeded yet
|
You can only merge once the items above are resolved
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,52 +1,64 @@
|
||||||
<script>
|
<script>
|
||||||
import ciIcon from './ci_icon.vue';
|
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.
|
* Renders CI Badge link with CI icon and status text based on
|
||||||
*
|
* API response shared between all places where it is used.
|
||||||
* Receives status object containing:
|
*
|
||||||
* status: {
|
* Receives status object containing:
|
||||||
* details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url
|
* status: {
|
||||||
* group:"running" // used for CSS class
|
* details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url
|
||||||
* icon: "icon_status_running" // used to render the icon
|
* group:"running" // used for CSS class
|
||||||
* label:"running" // used for potential tooltip
|
* icon: "icon_status_running" // used to render the icon
|
||||||
* text:"running" // text rendered
|
* label:"running" // used for potential tooltip
|
||||||
* }
|
* text:"running" // text rendered
|
||||||
*
|
* }
|
||||||
* Used in:
|
*
|
||||||
* - Pipelines table - first column
|
* Used in:
|
||||||
* - Jobs table - first column
|
* - Pipelines table - first column
|
||||||
* - Pipeline show view - header
|
* - Jobs table - first column
|
||||||
* - Job show view - header
|
* - Pipeline show view - header
|
||||||
* - MR widget
|
* - Job show view - header
|
||||||
*/
|
* - MR widget
|
||||||
|
*/
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
status: {
|
status: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
showText: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
components: {
|
||||||
|
ciIcon,
|
||||||
components: {
|
|
||||||
ciIcon,
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
cssClass() {
|
|
||||||
const className = this.status.group;
|
|
||||||
|
|
||||||
return className ? `ci-status ci-${this.status.group}` : 'ci-status';
|
|
||||||
},
|
},
|
||||||
},
|
directives: {
|
||||||
};
|
tooltip,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
cssClass() {
|
||||||
|
const className = this.status.group;
|
||||||
|
|
||||||
|
return className ? `ci-status ci-${className}` : 'ci-status';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
:href="status.details_path"
|
:href="status.details_path"
|
||||||
:class="cssClass">
|
:class="cssClass"
|
||||||
|
v-tooltip
|
||||||
|
:title="!showText ? status.text : ''">
|
||||||
<ci-icon :status="status" />
|
<ci-icon :status="status" />
|
||||||
{{status.text}}
|
|
||||||
|
<template v-if="showText">
|
||||||
|
{{status.text}}
|
||||||
|
</template>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -63,14 +63,17 @@
|
||||||
required: false,
|
required: false,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
|
showBranch: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
/**
|
/**
|
||||||
* Used to verify if all the properties needed to render the commit
|
* Used to verify if all the properties needed to render the commit
|
||||||
* ref section were provided.
|
* ref section were provided.
|
||||||
*
|
*
|
||||||
* TODO: Improve this! Use lodash _.has when we have it.
|
|
||||||
*
|
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
hasCommitRef() {
|
hasCommitRef() {
|
||||||
|
@ -80,8 +83,6 @@
|
||||||
* Used to verify if all the properties needed to render the commit
|
* Used to verify if all the properties needed to render the commit
|
||||||
* author section were provided.
|
* author section were provided.
|
||||||
*
|
*
|
||||||
* TODO: Improve this! Use lodash _.has when we have it.
|
|
||||||
*
|
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
hasAuthor() {
|
hasAuthor() {
|
||||||
|
@ -114,31 +115,30 @@
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="branch-commit">
|
<div class="branch-commit">
|
||||||
<div
|
<template v-if="hasCommitRef && showBranch">
|
||||||
v-if="hasCommitRef"
|
<div
|
||||||
class="icon-container hidden-xs">
|
class="icon-container hidden-xs">
|
||||||
<i
|
<i
|
||||||
v-if="tag"
|
v-if="tag"
|
||||||
class="fa fa-tag"
|
class="fa fa-tag"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
</i>
|
</i>
|
||||||
<i
|
<i
|
||||||
v-if="!tag"
|
v-if="!tag"
|
||||||
class="fa fa-code-fork"
|
class="fa fa-code-fork"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a
|
|
||||||
v-if="hasCommitRef"
|
|
||||||
class="ref-name hidden-xs"
|
|
||||||
:href="commitRef.ref_url"
|
|
||||||
v-tooltip
|
|
||||||
data-container="body"
|
|
||||||
:title="commitRef.name">
|
|
||||||
{{commitRef.name}}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="ref-name hidden-xs"
|
||||||
|
:href="commitRef.ref_url"
|
||||||
|
v-tooltip
|
||||||
|
data-container="body"
|
||||||
|
:title="commitRef.name">
|
||||||
|
{{commitRef.name}}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
<div
|
<div
|
||||||
v-html="commitIconSvg"
|
v-html="commitIconSvg"
|
||||||
class="commit-icon js-commit-icon">
|
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>
|
<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 { 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 {
|
export default {
|
||||||
name: 'issuePlaceholderNote',
|
name: 'placeholderNote',
|
||||||
props: {
|
props: {
|
||||||
note: {
|
note: {
|
||||||
type: Object,
|
type: Object,
|
|
@ -1,4 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
|
/**
|
||||||
|
* Common component to render a placeholder system note.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <placeholder-system-note
|
||||||
|
* :note="{ body: 'Commands are being applied'}"
|
||||||
|
* />
|
||||||
|
*/
|
||||||
export default {
|
export default {
|
||||||
name: 'placeholderSystemNote',
|
name: 'placeholderSystemNote',
|
||||||
props: {
|
props: {
|
|
@ -1,6 +1,24 @@
|
||||||
<script>
|
<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 { 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 {
|
export default {
|
||||||
name: 'systemNote',
|
name: 'systemNote',
|
||||||
|
@ -24,7 +42,7 @@
|
||||||
return this.targetNoteHash === this.noteAnchorId;
|
return this.targetNoteHash === this.noteAnchorId;
|
||||||
},
|
},
|
||||||
iconHtml() {
|
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"
|
:author="note.author"
|
||||||
:created-at="note.created_at"
|
:created-at="note.created_at"
|
||||||
:note-id="note.id"
|
:note-id="note.id"
|
||||||
:action-text-html="note.note_html" />
|
:action-text-html="note.note_html"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -9,7 +9,7 @@ export default {
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: false,
|
||||||
},
|
},
|
||||||
kind: {
|
kind: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -82,14 +82,15 @@ export default {
|
||||||
type="button"
|
type="button"
|
||||||
class="btn"
|
class="btn"
|
||||||
:class="btnCancelKindClass"
|
:class="btnCancelKindClass"
|
||||||
@click="emitSubmit(false)">
|
@click="close">
|
||||||
{{closeButtonLabel}}
|
{{ closeButtonLabel }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button
|
||||||
|
type="button"
|
||||||
class="btn"
|
class="btn"
|
||||||
:class="btnKindClass"
|
:class="btnKindClass"
|
||||||
@click="emitSubmit(true)">
|
@click="emitSubmit(true)">
|
||||||
{{primaryButtonLabel}}
|
{{ primaryButtonLabel }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,12 +12,14 @@
|
||||||
:img-alt="tooltipText"
|
:img-alt="tooltipText"
|
||||||
:img-size="20"
|
:img-size="20"
|
||||||
:tooltip-text="tooltipText"
|
:tooltip-text="tooltipText"
|
||||||
tooltip-placement="top"
|
:tooltip-placement="top"
|
||||||
|
:username="username"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import userAvatarImage from './user_avatar_image.vue';
|
import userAvatarImage from './user_avatar_image.vue';
|
||||||
|
import tooltip from '../../directives/tooltip';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UserAvatarLink',
|
name: 'UserAvatarLink',
|
||||||
|
@ -60,6 +62,22 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: 'top',
|
default: 'top',
|
||||||
},
|
},
|
||||||
|
username: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
shouldShowUsername() {
|
||||||
|
return this.username.length > 0;
|
||||||
|
},
|
||||||
|
avatarTooltipText() {
|
||||||
|
return this.shouldShowUsername ? '' : this.tooltipText;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
tooltip,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -73,8 +91,13 @@ export default {
|
||||||
:img-alt="imgAlt"
|
:img-alt="imgAlt"
|
||||||
:css-classes="imgCssClasses"
|
:css-classes="imgCssClasses"
|
||||||
:size="imgSize"
|
:size="imgSize"
|
||||||
:tooltip-text="tooltipText"
|
:tooltip-text="avatarTooltipText"
|
||||||
:tooltip-placement="tooltipPlacement"
|
:tooltip-placement="tooltipPlacement"
|
||||||
/>
|
/><span
|
||||||
|
v-if="shouldShowUsername"
|
||||||
|
v-tooltip
|
||||||
|
:title="tooltipText"
|
||||||
|
:tooltip-placement="tooltipPlacement"
|
||||||
|
>{{username}}</span>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
@import "framework/layout";
|
@import "framework/layout";
|
||||||
|
|
||||||
@import "framework/animations";
|
@import "framework/animations";
|
||||||
|
@import "framework/vue_transitions";
|
||||||
@import "framework/avatar";
|
@import "framework/avatar";
|
||||||
@import "framework/asciidoctor";
|
@import "framework/asciidoctor";
|
||||||
@import "framework/banner";
|
@import "framework/banner";
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
@import "framework/secondary-navigation-elements";
|
@import "framework/secondary-navigation-elements";
|
||||||
@import "framework/selects";
|
@import "framework/selects";
|
||||||
@import "framework/sidebar";
|
@import "framework/sidebar";
|
||||||
@import "framework/new-sidebar";
|
@import "framework/contextual-sidebar";
|
||||||
@import "framework/tables";
|
@import "framework/tables";
|
||||||
@import "framework/notes";
|
@import "framework/notes";
|
||||||
@import "framework/tabs";
|
@import "framework/tabs";
|
||||||
|
|
|
@ -292,6 +292,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-align-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-group {
|
.btn-group {
|
||||||
&.btn-grouped {
|
&.btn-grouped {
|
||||||
@include btn-with-margin;
|
@include btn-with-margin;
|
||||||
|
|
|
@ -1,24 +1,10 @@
|
||||||
@import "framework/variables";
|
.page-with-contextual-sidebar {
|
||||||
@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 {
|
|
||||||
@media (min-width: $screen-md-min) {
|
@media (min-width: $screen-md-min) {
|
||||||
padding-left: $new-sidebar-collapsed-width;
|
padding-left: $contextual-sidebar-collapsed-width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $screen-lg-min) {
|
@media (min-width: $screen-lg-min) {
|
||||||
padding-left: $new-sidebar-width;
|
padding-left: $contextual-sidebar-width;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override position: absolute
|
// Override position: absolute
|
||||||
|
@ -34,7 +20,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
|
|
||||||
.page-with-icon-sidebar {
|
.page-with-icon-sidebar {
|
||||||
@media (min-width: $screen-sm-min) {
|
@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,
|
&:hover,
|
||||||
a:hover {
|
a:hover {
|
||||||
background-color: $hover-background;
|
background-color: $link-hover-background;
|
||||||
color: $hover-color;
|
color: $gl-text-color;
|
||||||
|
|
||||||
.settings-avatar {
|
.settings-avatar {
|
||||||
svg {
|
svg {
|
||||||
fill: $hover-color;
|
fill: $gl-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +71,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
.nav-sidebar {
|
.nav-sidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 400;
|
z-index: 400;
|
||||||
width: $new-sidebar-width;
|
width: $contextual-sidebar-width;
|
||||||
transition: left $sidebar-transition-duration;
|
transition: left $sidebar-transition-duration;
|
||||||
top: $header-height;
|
top: $header-height;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -103,7 +89,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
|
|
||||||
&.sidebar-icons-only {
|
&.sidebar-icons-only {
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: $new-sidebar-collapsed-width;
|
min-width: $contextual-sidebar-collapsed-width;
|
||||||
|
|
||||||
.nav-sidebar-inner-scroll {
|
.nav-sidebar-inner-scroll {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
@ -149,11 +135,11 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
color: $inactive-color;
|
color: $gl-text-color-secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: $inactive-color;
|
fill: $gl-text-color-secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +154,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $screen-xs-max) {
|
@media (max-width: $screen-xs-max) {
|
||||||
left: (-$new-sidebar-width);
|
left: (-$contextual-sidebar-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-icon-container {
|
.nav-icon-container {
|
||||||
|
@ -210,8 +196,8 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: $active-hover-background;
|
background: $link-active-background;
|
||||||
color: $active-hover-color;
|
color: $gl-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +206,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
&,
|
&,
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: $active-background;
|
background: $link-active-background;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -308,11 +294,11 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
background-color: $inactive-badge-background;
|
background-color: $inactive-badge-background;
|
||||||
color: $inactive-color;
|
color: $gl-text-color-secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: $active-background;
|
background: $link-active-background;
|
||||||
|
|
||||||
> a {
|
> a {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
@ -330,7 +316,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
|
|
||||||
&.active > a:hover,
|
&.active > a:hover,
|
||||||
&.is-over > a {
|
&.is-over > a {
|
||||||
background-color: $hover-background;
|
background-color: $link-hover-background;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,7 +326,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
|
|
||||||
.toggle-sidebar-button,
|
.toggle-sidebar-button,
|
||||||
.close-nav-button {
|
.close-nav-button {
|
||||||
width: $new-sidebar-width - 2px;
|
width: $contextual-sidebar-width - 2px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
@ -407,7 +393,7 @@ $new-sidebar-collapsed-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-sidebar-button {
|
.toggle-sidebar-button {
|
||||||
width: $new-sidebar-collapsed-width - 2px;
|
width: $contextual-sidebar-collapsed-width - 2px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
||||||
.collapse-text,
|
.collapse-text,
|
|
@ -65,7 +65,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
-webkit-flex: 1;
|
-webkit-flex: 1;
|
||||||
padding-left: 30px;
|
padding-left: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
@ -221,10 +221,6 @@
|
||||||
box-shadow: 0 0 4px $search-input-focus-shadow-color;
|
box-shadow: 0 0 4px $search-input-focus-shadow-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.focus .fa-filter {
|
|
||||||
color: $common-gray-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
gl-emoji {
|
gl-emoji {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
@ -251,13 +247,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa-filter {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
left: 10px;
|
|
||||||
color: $gray-darkest;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-times {
|
.fa-times {
|
||||||
right: 10px;
|
right: 10px;
|
||||||
color: $gray-darkest;
|
color: $gray-darkest;
|
||||||
|
|
|
@ -9,6 +9,8 @@ $sidebar-transition-duration: .15s;
|
||||||
$sidebar-breakpoint: 1024px;
|
$sidebar-breakpoint: 1024px;
|
||||||
$default-transition-duration: .15s;
|
$default-transition-duration: .15s;
|
||||||
$right-sidebar-transition-duration: .3s;
|
$right-sidebar-transition-duration: .3s;
|
||||||
|
$contextual-sidebar-width: 220px;
|
||||||
|
$contextual-sidebar-collapsed-width: 50px;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Color schema
|
* Color schema
|
||||||
|
@ -358,6 +360,13 @@ $dropdown-item-hover-bg: $gray-darker;
|
||||||
$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09);
|
$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09);
|
||||||
$dropdown-hover-color: $blue-400;
|
$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
|
* Buttons
|
||||||
*/
|
*/
|
||||||
|
@ -404,7 +413,6 @@ $note-targe3-inside: #ffffd3;
|
||||||
$note-line2-border: #ddd;
|
$note-line2-border: #ddd;
|
||||||
$note-icon-gutter-width: 55px;
|
$note-icon-gutter-width: 55px;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Zen
|
* 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;
|
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 {
|
.issuable-sidebar-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,13 +249,12 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.discussion-actions {
|
.discussion-actions {
|
||||||
display: table;
|
display: table;
|
||||||
|
|
||||||
.new-issue-for-discussion path {
|
.btn-default path {
|
||||||
fill: $gray-darkest;
|
fill: $gray-darkest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -466,6 +466,10 @@ ul.notes {
|
||||||
float: right;
|
float: right;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: $gray-darkest;
|
color: $gray-darkest;
|
||||||
|
|
||||||
|
.btn-group > .discussion-next-btn {
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-actions {
|
.note-actions {
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity $sidebar-transition-duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-loader {
|
.monaco-loader {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -206,7 +201,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#repo-file-buttons {
|
.repo-file-buttons {
|
||||||
background-color: $white-light;
|
background-color: $white-light;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-top: 1px solid $white-normal;
|
border-top: 1px solid $white-normal;
|
||||||
|
|
|
@ -57,6 +57,10 @@ class HelpController < ApplicationController
|
||||||
def shortcuts
|
def shortcuts
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def instance_configuration
|
||||||
|
@instance_configuration = InstanceConfiguration.new
|
||||||
|
end
|
||||||
|
|
||||||
def ui
|
def ui
|
||||||
@user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
|
@user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
|
||||||
end
|
end
|
||||||
|
|
|
@ -205,6 +205,7 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
tree_path = path_segments.join('/')
|
tree_path = path_segments.join('/')
|
||||||
|
|
||||||
render json: json.merge(
|
render json: json.merge(
|
||||||
|
id: @blob.id,
|
||||||
path: blob.path,
|
path: blob.path,
|
||||||
name: blob.name,
|
name: blob.name,
|
||||||
extension: blob.extension,
|
extension: blob.extension,
|
||||||
|
|
|
@ -15,6 +15,8 @@ class Projects::BranchesController < Projects::ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
@refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
|
@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
|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37429
|
||||||
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||||
@max_commits = @branches.reduce(0) do |memo, branch|
|
@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]
|
before_action :authorize_create_issue!, only: [:new, :create]
|
||||||
|
|
||||||
# Allow modify issue
|
# 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
|
# Allow create a new branch and empty WIP merge request from current issue
|
||||||
before_action :authorize_create_merge_request!, only: [:create_merge_request]
|
before_action :authorize_create_merge_request!, only: [:create_merge_request]
|
||||||
|
@ -63,6 +63,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
respond_with(@issue)
|
respond_with(@issue)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
respond_with(@issue)
|
||||||
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@noteable = @issue
|
@noteable = @issue
|
||||||
@note = @project.notes.new(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)
|
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
format.html do
|
||||||
|
recaptcha_check_with_fallback { render :edit }
|
||||||
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
render_issue_json
|
render_issue_json
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,7 +36,6 @@ class Projects::TreeController < Projects::ApplicationController
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
|
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
|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
|
||||||
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||||
|
|
|
@ -23,7 +23,7 @@ class BranchesFinder
|
||||||
|
|
||||||
def filter_by_name(branches)
|
def filter_by_name(branches)
|
||||||
if search
|
if search
|
||||||
branches.select { |branch| branch.name.include?(search) }
|
branches.select { |branch| branch.name.upcase.include?(search.upcase) }
|
||||||
else
|
else
|
||||||
branches
|
branches
|
||||||
end
|
end
|
||||||
|
|
|
@ -120,6 +120,15 @@ module ApplicationSettingsHelper
|
||||||
message.html_safe
|
message.html_safe
|
||||||
end
|
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
|
def circuitbreaker_failure_wait_time_help_text
|
||||||
_("When access to a storage fails. GitLab will prevent access to the "\
|
_("When access to a storage fails. GitLab will prevent access to the "\
|
||||||
"storage for the time specified here. This allows the filesystem to "\
|
"storage for the time specified here. This allows the filesystem to "\
|
||||||
|
@ -144,6 +153,8 @@ module ApplicationSettingsHelper
|
||||||
:akismet_api_key,
|
:akismet_api_key,
|
||||||
:akismet_enabled,
|
:akismet_enabled,
|
||||||
:auto_devops_enabled,
|
:auto_devops_enabled,
|
||||||
|
:circuitbreaker_access_retries,
|
||||||
|
:circuitbreaker_backoff_threshold,
|
||||||
:circuitbreaker_failure_count_threshold,
|
:circuitbreaker_failure_count_threshold,
|
||||||
:circuitbreaker_failure_reset_time,
|
:circuitbreaker_failure_reset_time,
|
||||||
:circuitbreaker_failure_wait_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
|
module NavHelper
|
||||||
def page_with_sidebar_class
|
def page_with_sidebar_class
|
||||||
class_name = page_gutter_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 << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar
|
||||||
|
|
||||||
class_name
|
class_name
|
||||||
|
|
|
@ -16,17 +16,16 @@ module StorageHealthHelper
|
||||||
def message_for_circuit_breaker(circuit_breaker)
|
def message_for_circuit_breaker(circuit_breaker)
|
||||||
maximum_failures = circuit_breaker.failure_count_threshold
|
maximum_failures = circuit_breaker.failure_count_threshold
|
||||||
current_failures = circuit_breaker.failure_count
|
current_failures = circuit_breaker.failure_count
|
||||||
permanently_broken = circuit_breaker.circuit_broken? && current_failures >= maximum_failures
|
|
||||||
|
|
||||||
translation_params = { number_of_failures: current_failures,
|
translation_params = { number_of_failures: current_failures,
|
||||||
maximum_failures: maximum_failures,
|
maximum_failures: maximum_failures,
|
||||||
number_of_seconds: circuit_breaker.failure_wait_time }
|
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 "\
|
s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
|
||||||
"retry automatically. Reset storage information when the problem is "\
|
"retry automatically. Reset storage information when the problem is "\
|
||||||
"resolved.") % translation_params
|
"resolved.") % translation_params
|
||||||
elsif circuit_breaker.circuit_broken?
|
elsif circuit_breaker.backing_off?
|
||||||
_("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
|
_("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
|
||||||
"block access for %{number_of_seconds} seconds.") % translation_params
|
"block access for %{number_of_seconds} seconds.") % translation_params
|
||||||
else
|
else
|
||||||
|
|
|
@ -153,13 +153,25 @@ class ApplicationSetting < ActiveRecord::Base
|
||||||
presence: true,
|
presence: true,
|
||||||
numericality: { greater_than_or_equal_to: 0 }
|
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_wait_time,
|
||||||
:circuitbreaker_failure_reset_time,
|
:circuitbreaker_failure_reset_time,
|
||||||
:circuitbreaker_storage_timeout,
|
:circuitbreaker_storage_timeout,
|
||||||
presence: true,
|
presence: true,
|
||||||
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
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|
|
SUPPORTED_KEY_TYPES.each do |type|
|
||||||
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
|
||||||
end
|
end
|
||||||
|
|
|
@ -249,9 +249,7 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
def commit
|
def commit
|
||||||
@commit ||= project.commit(sha)
|
@commit ||= project.commit_by(oid: sha)
|
||||||
rescue
|
|
||||||
nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def branch?
|
def branch?
|
||||||
|
|
|
@ -110,7 +110,7 @@ class Environment < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def ref_path
|
def ref_path
|
||||||
"refs/#{Repository::REF_ENVIRONMENTS}/#{Shellwords.shellescape(name)}"
|
"refs/#{Repository::REF_ENVIRONMENTS}/#{generate_slug}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def formatted_external_url
|
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,
|
key: Gitlab::Application.secrets.db_key_base,
|
||||||
algorithm: 'aes-256-cbc'
|
algorithm: 'aes-256-cbc'
|
||||||
|
|
||||||
after_create :update
|
after_create :update_daemon
|
||||||
after_save :update
|
after_save :update_daemon
|
||||||
after_destroy :update
|
after_destroy :update_daemon
|
||||||
|
|
||||||
def to_param
|
def to_param
|
||||||
domain
|
domain
|
||||||
|
@ -80,7 +80,7 @@ class PagesDomain < ActiveRecord::Base
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def update
|
def update_daemon
|
||||||
::Projects::UpdatePagesConfigurationService.new(project).execute
|
::Projects::UpdatePagesConfigurationService.new(project).execute
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -540,6 +540,10 @@ class Project < ActiveRecord::Base
|
||||||
repository.commit(ref)
|
repository.commit(ref)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def commit_by(oid:)
|
||||||
|
repository.commit_by(oid: oid)
|
||||||
|
end
|
||||||
|
|
||||||
# ref can't be HEAD, can only be branch/tag name or SHA
|
# ref can't be HEAD, can only be branch/tag name or SHA
|
||||||
def latest_successful_builds_for(ref = default_branch)
|
def latest_successful_builds_for(ref = default_branch)
|
||||||
latest_pipeline = pipelines.latest_successful_for(ref)
|
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)
|
def merge_base_commit(first_commit_id, second_commit_id)
|
||||||
sha = repository.merge_base(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
|
end
|
||||||
|
|
||||||
def saved?
|
def saved?
|
||||||
|
|
|
@ -3,6 +3,8 @@ class JiraService < IssueTrackerService
|
||||||
|
|
||||||
validates :url, url: true, presence: true, if: :activated?
|
validates :url, url: true, presence: true, if: :activated?
|
||||||
validates :api_url, url: true, allow_blank: true
|
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
|
prop_accessor :username, :password, :url, :api_url, :jira_issue_transition_id, :title, :description
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,10 @@ class KubernetesService < DeploymentService
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_namespace
|
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
|
end
|
||||||
|
|
||||||
def build_kubeclient!(api_path: 'api', api_version: 'v1')
|
def build_kubeclient!(api_path: 'api', api_version: 'v1')
|
||||||
|
|
|
@ -76,6 +76,7 @@ class Repository
|
||||||
@full_path = full_path
|
@full_path = full_path
|
||||||
@disk_path = disk_path || full_path
|
@disk_path = disk_path || full_path
|
||||||
@project = project
|
@project = project
|
||||||
|
@commit_cache = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def ==(other)
|
def ==(other)
|
||||||
|
@ -103,18 +104,17 @@ class Repository
|
||||||
|
|
||||||
def commit(ref = 'HEAD')
|
def commit(ref = 'HEAD')
|
||||||
return nil unless exists?
|
return nil unless exists?
|
||||||
|
return ref if ref.is_a?(::Commit)
|
||||||
|
|
||||||
commit =
|
find_commit(ref)
|
||||||
if ref.is_a?(Gitlab::Git::Commit)
|
end
|
||||||
ref
|
|
||||||
else
|
|
||||||
Gitlab::Git::Commit.find(raw_repository, ref)
|
|
||||||
end
|
|
||||||
|
|
||||||
commit = ::Commit.new(commit, @project) if commit
|
# Finding a commit by the passed SHA
|
||||||
commit
|
# Also takes care of caching, based on the SHA
|
||||||
rescue Rugged::OdbError, Rugged::TreeError
|
def commit_by(oid:)
|
||||||
nil
|
return @commit_cache[oid] if @commit_cache.key?(oid)
|
||||||
|
|
||||||
|
@commit_cache[oid] = find_commit(oid)
|
||||||
end
|
end
|
||||||
|
|
||||||
def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
|
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
|
# branches or tags, but we want to keep some of these commits around, for
|
||||||
# example if they have comments or CI builds.
|
# example if they have comments or CI builds.
|
||||||
def keep_around(sha)
|
def keep_around(sha)
|
||||||
return unless sha && commit(sha)
|
return unless sha && commit_by(oid: sha)
|
||||||
|
|
||||||
return if kept_around?(sha)
|
return if kept_around?(sha)
|
||||||
|
|
||||||
|
@ -862,22 +862,12 @@ class Repository
|
||||||
end
|
end
|
||||||
|
|
||||||
def ff_merge(user, source, target_branch, merge_request: nil)
|
def ff_merge(user, source, target_branch, merge_request: nil)
|
||||||
our_commit = rugged.branches[target_branch].target
|
their_commit_id = commit(source)&.id
|
||||||
their_commit =
|
raise 'Invalid merge source' if their_commit_id.nil?
|
||||||
if source.is_a?(Gitlab::Git::Commit)
|
|
||||||
source.raw_commit
|
|
||||||
else
|
|
||||||
rugged.lookup(source)
|
|
||||||
end
|
|
||||||
|
|
||||||
raise 'Invalid merge target' if our_commit.nil?
|
merge_request&.update(in_progress_merge_commit_sha: their_commit_id)
|
||||||
raise 'Invalid merge source' if their_commit.nil?
|
|
||||||
|
|
||||||
with_branch(user, target_branch) do |start_commit|
|
with_cache_hooks { raw.ff_merge(user, their_commit_id, target_branch) }
|
||||||
merge_request&.update(in_progress_merge_commit_sha: their_commit.oid)
|
|
||||||
|
|
||||||
their_commit.oid
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def revert(
|
def revert(
|
||||||
|
@ -912,18 +902,27 @@ class Repository
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def merged_to_root_ref?(branch_name)
|
def merged_to_root_ref?(branch_or_name, pre_loaded_merged_branches = nil)
|
||||||
branch_commit = commit(branch_name)
|
branch = Gitlab::Git::Branch.find(self, branch_or_name)
|
||||||
root_ref_commit = commit(root_ref)
|
|
||||||
|
|
||||||
if branch_commit
|
if branch
|
||||||
same_head = branch_commit.id == root_ref_commit.id
|
root_ref_sha = commit(root_ref).sha
|
||||||
!same_head && ancestor?(branch_commit.id, root_ref_commit.id)
|
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
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
delegate :merged_branch_names, to: :raw_repository
|
||||||
|
|
||||||
def merge_base(first_commit_id, second_commit_id)
|
def merge_base(first_commit_id, second_commit_id)
|
||||||
first_commit_id = commit(first_commit_id).try(:id) || first_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
|
second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
|
||||||
|
@ -1031,6 +1030,10 @@ class Repository
|
||||||
if instance_variable_defined?(ivar)
|
if instance_variable_defined?(ivar)
|
||||||
instance_variable_get(ivar)
|
instance_variable_get(ivar)
|
||||||
else
|
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
|
begin
|
||||||
value =
|
value =
|
||||||
if memoize_only
|
if memoize_only
|
||||||
|
@ -1040,8 +1043,9 @@ class Repository
|
||||||
end
|
end
|
||||||
instance_variable_set(ivar, value)
|
instance_variable_set(ivar, value)
|
||||||
rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
|
rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
|
||||||
# if e.g. HEAD or the entire repository doesn't exist we want to
|
# Even if the above `#exists?` check passes these errors might still
|
||||||
# gracefully handle this and not cache anything.
|
# occur (for example because of a non-existing HEAD). We want to
|
||||||
|
# gracefully handle this and not cache anything
|
||||||
fallback
|
fallback
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1069,6 +1073,18 @@ class Repository
|
||||||
|
|
||||||
private
|
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)
|
def blob_data_at(sha, path)
|
||||||
blob = blob_at(sha, path)
|
blob = blob_at(sha, path)
|
||||||
return unless blob
|
return unless blob
|
||||||
|
@ -1107,12 +1123,12 @@ class Repository
|
||||||
|
|
||||||
def last_commit_for_path_by_gitaly(sha, path)
|
def last_commit_for_path_by_gitaly(sha, path)
|
||||||
c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
|
c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
|
||||||
commit(c)
|
commit_by(oid: c)
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_commit_for_path_by_rugged(sha, path)
|
def last_commit_for_path_by_rugged(sha, path)
|
||||||
sha = last_commit_id_for_path_by_shelling_out(sha, path)
|
sha = last_commit_id_for_path_by_shelling_out(sha, path)
|
||||||
commit(sha)
|
commit_by(oid: sha)
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_commit_id_for_path_by_shelling_out(sha, path)
|
def last_commit_id_for_path_by_shelling_out(sha, path)
|
||||||
|
|
|
@ -16,8 +16,8 @@ module Users
|
||||||
user_cache_key
|
user_cache_key
|
||||||
]
|
]
|
||||||
|
|
||||||
if event.project.forked?
|
if forked_from = event.project.forked_from_project
|
||||||
keys << project_cache_key(event.project.forked_from_project)
|
keys << project_cache_key(forked_from)
|
||||||
end
|
end
|
||||||
|
|
||||||
keys.each { |key| set_key(key, event.id) }
|
keys.each { |key| set_key(key, event.id) }
|
||||||
|
|
|
@ -533,29 +533,41 @@
|
||||||
%fieldset
|
%fieldset
|
||||||
%legend Git Storage Circuitbreaker settings
|
%legend Git Storage Circuitbreaker settings
|
||||||
.form-group
|
.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
|
.col-sm-10
|
||||||
= f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
|
= f.number_field :circuitbreaker_access_retries, class: 'form-control'
|
||||||
.help-block
|
.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
|
.form-group
|
||||||
= f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2'
|
= f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2'
|
||||||
.col-sm-10
|
.col-sm-10
|
||||||
= f.number_field :circuitbreaker_failure_wait_time, class: 'form-control'
|
= f.number_field :circuitbreaker_failure_wait_time, class: 'form-control'
|
||||||
.help-block
|
.help-block
|
||||||
= circuitbreaker_failure_wait_time_help_text
|
= 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
|
.form-group
|
||||||
= f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'control-label col-sm-2'
|
= f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'control-label col-sm-2'
|
||||||
.col-sm-10
|
.col-sm-10
|
||||||
= f.number_field :circuitbreaker_failure_reset_time, class: 'form-control'
|
= f.number_field :circuitbreaker_failure_reset_time, class: 'form-control'
|
||||||
.help-block
|
.help-block
|
||||||
= circuitbreaker_failure_reset_time_help_text
|
= 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
|
%fieldset
|
||||||
%legend Repository Checks
|
%legend Repository Checks
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
.fade-right= icon('angle-right')
|
.fade-right= icon('angle-right')
|
||||||
%ul.nav-links.scrolling-tabs
|
%ul.nav-links.scrolling-tabs
|
||||||
= nav_link(page: [dashboard_projects_path, root_path]) do
|
= 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
|
Your projects
|
||||||
= nav_link(page: starred_dashboard_projects_path) do
|
= 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
|
Starred projects
|
||||||
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
|
= 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
|
Explore projects
|
||||||
|
|
||||||
.nav-controls
|
.nav-controls
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
%span= Gitlab::VERSION
|
%span= Gitlab::VERSION
|
||||||
%small= link_to Gitlab::REVISION, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab::REVISION)
|
%small= link_to Gitlab::REVISION, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab::REVISION)
|
||||||
= version_status_badge
|
= version_status_badge
|
||||||
|
|
||||||
%p.slead
|
%p.slead
|
||||||
GitLab is open source software to collaborate on code.
|
GitLab is open source software to collaborate on code.
|
||||||
%br
|
%br
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises.
|
Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises.
|
||||||
%br
|
%br
|
||||||
Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank', rel: 'noopener noreferrer'}.
|
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
|
%hr
|
||||||
|
|
||||||
.row.prepend-top-default
|
.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)
|
- commit = @repository.commit(branch.dereferenced_target)
|
||||||
- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
|
- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
|
||||||
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
|
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
|
||||||
|
@ -12,7 +13,7 @@
|
||||||
|
|
||||||
- if branch.name == @repository.root_ref
|
- if branch.name == @repository.root_ref
|
||||||
%span.label.label-primary default
|
%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 } }
|
%span.label.label-info.has-tooltip{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
|
||||||
= s_('Branches|merged')
|
= s_('Branches|merged')
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@
|
||||||
target: "#modal-delete-branch",
|
target: "#modal-delete-branch",
|
||||||
delete_path: project_branch_path(@project, branch.name),
|
delete_path: project_branch_path(@project, branch.name),
|
||||||
branch_name: 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")
|
= icon("trash-o")
|
||||||
- else
|
- else
|
||||||
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
|
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
- if @branches.any?
|
- if @branches.any?
|
||||||
%ul.content-list.all-branches
|
%ul.content-list.all-branches
|
||||||
- @branches.each do |branch|
|
- @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'
|
= paginate @branches, theme: 'gitlab'
|
||||||
- else
|
- else
|
||||||
.nothing-here-block
|
.nothing-here-block
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
|
|
||||||
- if can?(current_user, :update_cluster, @cluster)
|
- if can?(current_user, :update_cluster, @cluster)
|
||||||
.form-group
|
.form-group
|
||||||
= field.submit s_('ClusterIntegration|Save'), class: 'btn btn-success'
|
= field.submit _('Save'), class: 'btn btn-success'
|
||||||
|
|
||||||
%section.settings#js-cluster-details
|
%section.settings#js-cluster-details
|
||||||
.settings-header
|
.settings-header
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
|
|
||||||
%section.settings#js-cluster-advanced-settings
|
%section.settings#js-cluster-advanced-settings
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4= s_('ClusterIntegration|Advanced settings')
|
%h4= _('Advanced settings')
|
||||||
%button.btn.js-settings-toggle
|
%button.btn.js-settings-toggle
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p= s_('ClusterIntegration|Manage Cluster integration on your GitLab project')
|
%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) }
|
#{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) }
|
||||||
.mr-widget-pipeline-graph
|
.mr-widget-pipeline-graph
|
||||||
= render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
|
= render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
|
||||||
in
|
- if last_pipeline.duration
|
||||||
= time_interval_in_words last_pipeline.duration
|
in
|
||||||
|
= time_interval_in_words last_pipeline.duration
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
%pre.light-well
|
%pre.light-well
|
||||||
:preserve
|
:preserve
|
||||||
cd existing_repo
|
cd existing_repo
|
||||||
|
git remote rename origin old-origin
|
||||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
|
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
|
||||||
git push -u origin --all
|
git push -u origin --all
|
||||||
git push -u origin --tags
|
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-container
|
||||||
.tree-ref-holder
|
.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'
|
= render 'projects/tree/old_tree_header'
|
||||||
|
|
||||||
.tree-controls
|
.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
|
- dropdown_toggle_text = @ref || @project.default_branch
|
||||||
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
|
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
|
||||||
= hidden_field_tag :destination, destination
|
= hidden_field_tag :destination, destination
|
||||||
|
@ -7,8 +8,20 @@
|
||||||
= hidden_field_tag key, value, id: nil
|
= hidden_field_tag key, value, id: nil
|
||||||
.dropdown
|
.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_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_title _("Switch branch/tag")
|
.dropdown-page-one
|
||||||
= dropdown_filter _("Search branches and tags")
|
= dropdown_title _("Switch branch/tag")
|
||||||
= dropdown_content
|
= dropdown_filter _("Search branches and tags")
|
||||||
= dropdown_loading
|
= 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)
|
= link_to_member(@project, participant, name: false, size: 24, lazy_load: true)
|
||||||
- if participants_extra > 0
|
- if participants_extra > 0
|
||||||
.hide-collapsed.participants-more
|
.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
|
+ #{participants_extra} more
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
%ul.tokens-container.list-unstyled
|
%ul.tokens-container.list-unstyled
|
||||||
%li.input-token
|
%li.input-token
|
||||||
%input.form-control.filtered-search{ search_filter_input_options(type) }
|
%input.form-control.filtered-search{ search_filter_input_options(type) }
|
||||||
= icon('filter')
|
|
||||||
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
|
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
|
||||||
%ul{ data: { dropdown: true } }
|
%ul{ data: { dropdown: true } }
|
||||||
%li.filter-dropdown-item{ data: { action: 'submit' } }
|
%li.filter-dropdown-item{ data: { action: 'submit' } }
|
||||||
|
|
|
@ -7,4 +7,5 @@
|
||||||
blob_url: namespace_project_blob_path(project.namespace, project, '{{branch}}'),
|
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}}' }),
|
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,
|
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