Commit 2fe03c94 authored by Filipe Freire's avatar Filipe Freire

Merge branch 'master' of https://gitlab.com/filipefreire/gitlab-ce into filipefreire_155

parents a1a5d142 66ae7560
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
*.swp *.swp
*.mo *.mo
*.edit.po *.edit.po
*.rej
.DS_Store .DS_Store
.bundle .bundle
.chef .chef
......
...@@ -61,6 +61,9 @@ stages: ...@@ -61,6 +61,9 @@ stages:
.use-pg: &use-pg .use-pg: &use-pg
services: services:
# As of Jan 2018, we don't have a strong reason to upgrade to 9.6 for CI yet,
# so using the least common denominator ensures backwards compatibility
# (as many users are still using 9.2).
- postgres:9.2 - postgres:9.2
- redis:alpine - redis:alpine
......
...@@ -11,4 +11,6 @@ See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#ch ...@@ -11,4 +11,6 @@ See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#ch
- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location. - [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location.
- [ ] Make sure internal links pointing to the document in question are not broken. - [ ] Make sure internal links pointing to the document in question are not broken.
- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory. - [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory.
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/doc_styleguide.html#redirections-for-pages-with-disqus-comments) to the new document if there are any Disqus comments on the old document thread.
- [ ] If working on CE, submit an MR to EE with the changes as well. - [ ] If working on CE, submit an MR to EE with the changes as well.
- [ ] Ping one of the technical writers for review.
...@@ -171,7 +171,7 @@ Assigning a team label makes sure issues get the attention of the appropriate ...@@ -171,7 +171,7 @@ Assigning a team label makes sure issues get the attention of the appropriate
people. people.
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge, The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge,
~Geo, ~Gitaly, ~Platform, ~Prometheus, ~Release, and ~"UX". ~Geo, ~Gitaly, ~Platform, ~Monitoring, ~Release, and ~"UX".
The descriptions on the [labels page][labels-page] explain what falls under the The descriptions on the [labels page][labels-page] explain what falls under the
responsibility of each team. responsibility of each team.
......
...@@ -403,7 +403,7 @@ group :ed25519 do ...@@ -403,7 +403,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.69.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.73.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -284,7 +284,7 @@ GEM ...@@ -284,7 +284,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.69.0) gitaly-proto (0.73.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)
...@@ -340,6 +340,8 @@ GEM ...@@ -340,6 +340,8 @@ GEM
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.0)
google-protobuf (3.4.1.1) google-protobuf (3.4.1.1)
googleapis-common-protos-types (1.0.1)
google-protobuf (~> 3.0)
googleauth (0.5.3) googleauth (0.5.3)
faraday (~> 0.12) faraday (~> 0.12)
jwt (~> 1.4) jwt (~> 1.4)
...@@ -366,9 +368,10 @@ GEM ...@@ -366,9 +368,10 @@ GEM
rake rake
grape_logging (1.7.0) grape_logging (1.7.0)
grape grape
grpc (1.4.5) grpc (1.8.3)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleauth (~> 0.5.1) googleapis-common-protos-types (~> 1.0.0)
googleauth (>= 0.5.1, < 0.7)
haml (4.0.7) haml (4.0.7)
tilt tilt
haml_lint (0.26.0) haml_lint (0.26.0)
...@@ -1051,7 +1054,7 @@ DEPENDENCIES ...@@ -1051,7 +1054,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.69.0) gitaly-proto (~> 0.73.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)
......
...@@ -81,8 +81,7 @@ ...@@ -81,8 +81,7 @@
{ {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html" gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))} ${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
</a>`,
}, },
false, false,
); );
......
/* global Flash */
import axios from './lib/utils/axios_utils';
import { n__, s__ } from './locale';
export function getHeaderText(childElementCount, mergeRequestCount) {
if (childElementCount === 0) {
return `${mergeRequestCount} ${n__('merge request', 'merge requests', mergeRequestCount)}`;
}
return ',';
}
export function createHeader(childElementCount, mergeRequestCount) {
const headerText = getHeaderText(childElementCount, mergeRequestCount);
return $('<span />', {
class: 'append-right-5',
text: headerText,
});
}
export function createLink(mergeRequest) {
return $('<a />', {
class: 'append-right-5',
href: mergeRequest.path,
text: `!${mergeRequest.iid}`,
});
}
export function createTitle(mergeRequest) {
return $('<span />', {
text: mergeRequest.title,
});
}
export function createItem(mergeRequest) {
const $item = $('<span />');
const $link = createLink(mergeRequest);
const $title = createTitle(mergeRequest);
$item.append($link);
$item.append($title);
return $item;
}
export function createContent(mergeRequests) {
const $content = $('<span />');
if (mergeRequests.length === 0) {
$content.text(s__('Commits|No related merge requests found'));
} else {
mergeRequests.forEach((mergeRequest) => {
const $header = createHeader($content.children().length, mergeRequests.length);
const $item = createItem(mergeRequest);
$content.append($header);
$content.append($item);
});
}
return $content;
}
export function fetchCommitMergeRequests() {
const $container = $('.merge-requests');
axios.get($container.data('projectCommitPath'))
.then((response) => {
const $content = createContent(response.data);
$container.html($content);
})
.catch(() => Flash(s__('Commits|An error occurred while fetching merge requests data.')));
}
import _ from 'underscore'; import _ from 'underscore';
export default class ProtectedTagDropdown { export default class CreateItemDropdown {
/** /**
* @param {Object} options containing * @param {Object} options containing
* `$dropdown` target element * `$dropdown` target element
...@@ -8,11 +8,14 @@ export default class ProtectedTagDropdown { ...@@ -8,11 +8,14 @@ export default class ProtectedTagDropdown {
* $dropdown must be an element created using `dropdown_tag()` rails helper * $dropdown must be an element created using `dropdown_tag()` rails helper
*/ */
constructor(options) { constructor(options) {
this.onSelect = options.onSelect; this.defaultToggleLabel = options.defaultToggleLabel;
this.fieldName = options.fieldName;
this.onSelect = options.onSelect || (() => {});
this.getDataOption = options.getData;
this.$dropdown = options.$dropdown; this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent(); this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer'); this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
this.$protectedTag = this.$dropdownContainer.find('.js-create-new-protected-tag'); this.$createButton = this.$dropdownContainer.find('.js-dropdown-create-new-item');
this.buildDropdown(); this.buildDropdown();
this.bindEvents(); this.bindEvents();
...@@ -23,7 +26,7 @@ export default class ProtectedTagDropdown { ...@@ -23,7 +26,7 @@ export default class ProtectedTagDropdown {
buildDropdown() { buildDropdown() {
this.$dropdown.glDropdown({ this.$dropdown.glDropdown({
data: this.getProtectedTags.bind(this), data: this.getData.bind(this),
filterable: true, filterable: true,
remote: false, remote: false,
search: { search: {
...@@ -31,14 +34,14 @@ export default class ProtectedTagDropdown { ...@@ -31,14 +34,14 @@ export default class ProtectedTagDropdown {
}, },
selectable: true, selectable: true,
toggleLabel(selected) { toggleLabel(selected) {
return (selected && 'id' in selected) ? selected.title : 'Protected Tag'; return (selected && 'id' in selected) ? selected.title : this.defaultToggleLabel;
}, },
fieldName: 'protected_tag[name]', fieldName: this.fieldName,
text(protectedTag) { text(item) {
return _.escape(protectedTag.title); return _.escape(item.title);
}, },
id(protectedTag) { id(item) {
return _.escape(protectedTag.id); return _.escape(item.id);
}, },
onFilter: this.toggleCreateNewButton.bind(this), onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => { clicked: (options) => {
...@@ -49,37 +52,37 @@ export default class ProtectedTagDropdown { ...@@ -49,37 +52,37 @@ export default class ProtectedTagDropdown {
} }
bindEvents() { bindEvents() {
this.$protectedTag.on('click', this.onClickCreateWildcard.bind(this)); this.$createButton.on('click', this.onClickCreateWildcard.bind(this));
} }
onClickCreateWildcard(e) { onClickCreateWildcard(e) {
e.preventDefault();
// Refresh the dropdown's data, which ends up calling `getData`
this.$dropdown.data('glDropdown').remote.execute(); this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex(); this.$dropdown.data('glDropdown').selectRowAtIndex();
e.preventDefault();
} }
getProtectedTags(term, callback) { getData(term, callback) {
if (this.selectedTag) { this.getDataOption(term, (data = []) => {
callback(gon.open_tags.concat(this.selectedTag)); callback(data.concat(this.selectedItem || []));
} else { });
callback(gon.open_tags);
}
} }
toggleCreateNewButton(tagName) { toggleCreateNewButton(item) {
if (tagName) { if (item) {
this.selectedTag = { this.selectedItem = {
title: tagName, title: item,
id: tagName, id: item,
text: tagName, text: item,
}; };
this.$dropdownContainer this.$dropdownContainer
.find('.js-create-new-protected-tag code') .find('.js-dropdown-create-new-item code')
.text(tagName); .text(item);
} }
this.toggleFooter(!tagName); this.toggleFooter(!item);
} }
toggleFooter(toggleState) { toggleFooter(toggleState) {
......
This diff is collapsed.
import LabelManager from './label_manager';
import GroupLabelSubscription from './group_label_subscription';
import ProjectLabelSubscription from './project_label_subscription';
export default () => {
if ($('.prioritized-labels').length) {
new LabelManager(); // eslint-disable-line no-new
}
$('.label-subscription').each((i, el) => {
const $el = $(el);
if ($el.find('.dropdown-group-label').length) {
new GroupLabelSubscription($el); // eslint-disable-line no-new
} else {
new ProjectLabelSubscription($el); // eslint-disable-line no-new
}
});
};
...@@ -30,8 +30,12 @@ ...@@ -30,8 +30,12 @@
shouldRenderContent() { shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length; return !this.isLoading && Object.keys(this.job).length;
}, },
/**
* When job has not started the key will be `false`
* When job started the key will be a string with a date.
*/
jobStarted() { jobStarted() {
return this.job.started; return !this.job.started === false;
}, },
}, },
watch: { watch: {
...@@ -72,6 +76,7 @@ ...@@ -72,6 +76,7 @@
<loading-icon <loading-icon
v-if="isLoading" v-if="isLoading"
size="2" size="2"
class="prepend-top-default append-bottom-default"
/> />
</div> </div>
</template> </template>
...@@ -110,7 +110,7 @@ MergeRequest.prototype.initCommitMessageListeners = function() { ...@@ -110,7 +110,7 @@ MergeRequest.prototype.initCommitMessageListeners = function() {
}); });
}; };
MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) { MergeRequest.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
$('.detail-page-header .status-box') $('.detail-page-header .status-box')
.removeClass(classToRemove) .removeClass(classToRemove)
.addClass(classToAdd) .addClass(classToAdd)
...@@ -118,14 +118,14 @@ MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, ne ...@@ -118,14 +118,14 @@ MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, ne
.text(newStatusText); .text(newStatusText);
}; };
MergeRequest.prototype.decreaseCounter = function(by = 1) { MergeRequest.decreaseCounter = function(by = 1) {
const $el = $('.nav-links .js-merge-counter'); const $el = $('.js-merge-counter');
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0); const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
$el.text(addDelimiter(count)); $el.text(addDelimiter(count));
}; };
MergeRequest.prototype.hideCloseButton = function() { MergeRequest.hideCloseButton = function() {
const el = document.querySelector('.merge-request .js-issuable-actions'); const el = document.querySelector('.merge-request .js-issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item'); const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) { if (closeDropdownItem) {
......
...@@ -271,7 +271,7 @@ Please check your network connection and try again.`; ...@@ -271,7 +271,7 @@ Please check your network connection and try again.`;
<div class="timeline-content timeline-content-form"> <div class="timeline-content timeline-content-form">
<form <form
ref="commentForm" ref="commentForm"
class="new-note js-quick-submit common-note-form gfm-form js-main-target-form" class="new-note common-note-form gfm-form js-main-target-form"
> >
<div class="error-alert"></div> <div class="error-alert"></div>
...@@ -301,7 +301,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea" ...@@ -301,7 +301,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea"
:disabled="isSubmitting" :disabled="isSubmitting"
placeholder="Write a comment or drag your files here..." placeholder="Write a comment or drag your files here..."
@keydown.up="editCurrentUserLastNote()" @keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()"> @keydown.meta.enter="handleSave()"
@keydown.ctrl.enter="handleSave()">
</textarea> </textarea>
</markdown-field> </markdown-field>
<div class="note-form-actions"> <div class="note-form-actions">
......
...@@ -155,6 +155,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea" ...@@ -155,6 +155,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
slot="textarea" slot="textarea"
placeholder="Write a comment or drag your files here..." placeholder="Write a comment or drag your files here..."
@keydown.meta.enter="handleUpdate()" @keydown.meta.enter="handleUpdate()"
@keydown.ctrl.enter="handleUpdate()"
@keydown.up="editMyLastNote()" @keydown.up="editMyLastNote()"
@keydown.esc="cancelHandler(true)"> @keydown.esc="cancelHandler(true)">
</textarea> </textarea>
......
import initForm from '../../../../shared/milestones/form';
export default () => initForm(false);
import initForm from '../../../../shared/milestones/form';
export default () => initForm(false);
import initBlob from '~/pages/projects/init_blob';
export default initBlob;
import BlobViewer from '~/blob/viewer/index';
import initBlob from '~/pages/projects/init_blob';
export default () => {
new BlobViewer(); // eslint-disable-line no-new
initBlob();
};
import AjaxLoadingSpinner from '~/ajax_loading_spinner';
import DeleteModal from '~/branches/branches_delete_modal';
export default () => {
AjaxLoadingSpinner.init();
new DeleteModal(); // eslint-disable-line no-new
};
import NewBranchForm from '~/new_branch_form';
export default () => new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
import ClustersIndex from '~/clusters/clusters_index';
export default () => {
new ClustersIndex(); // eslint-disable-line no-new
};
import ClustersBundle from '~/clusters/clusters_bundle';
export default () => {
new ClustersBundle(); // eslint-disable-line no-new
};
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
export default () => {
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
};
/* eslint-disable no-new */
import Diff from '~/diff';
import ZenMode from '~/zen_mode';
import ShortcutsNavigation from '~/shortcuts_navigation';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import initNotes from '~/init_notes';
import initChangesDropdown from '~/init_changes_dropdown';
import { fetchCommitMergeRequests } from '~/commit_merge_requests';
export default () => {
new Diff();
new ZenMode();
new ShortcutsNavigation();
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
initNotes();
const stickyBarPaddingTop = 16;
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
fetchCommitMergeRequests();
};
import CommitsList from '~/commits';
import GpgBadges from '~/gpg_badges';
import ShortcutsNavigation from '~/shortcuts_navigation';
export default () => {
CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
new ShortcutsNavigation(); // eslint-disable-line no-new
GpgBadges.fetch();
};
import initCompareAutocomplete from '~/compare_autocomplete';
export default () => {
initCompareAutocomplete();
};
import Diff from '~/diff';
import initChangesDropdown from '~/init_changes_dropdown';
export default () => {
new Diff(); // eslint-disable-line no-new
const paddingTop = 16;
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
};
import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
export default () => {
setupProjectEdit();
// Initialize expandable settings panels
initSettingsPanels();
};
import ProjectFindFile from '~/project_find_file';
import ShortcutsFindFile from '~/shortcuts_find_file';
export default () => {
const findElement = document.querySelector('.js-file-finder');
const projectFindFile = new ProjectFindFile($('.file-finder-holder'), {
url: findElement.dataset.fileFindUrl,
treeUrl: findElement.dataset.findTreeUrl,
blobUrlTemplate: findElement.dataset.blobUrlTemplate,
});
new ShortcutsFindFile(projectFindFile); // eslint-disable-line no-new
};
import ProjectFork from '~/project_fork';
export default () => {
new ProjectFork(); // eslint-disable-line no-new
};
import ProjectImport from '~/project_import';
export default () => {
new ProjectImport(); // eslint-disable-line no-new
};
import LineHighlighter from '~/line_highlighter';
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
import ShortcutsNavigation from '~/shortcuts_navigation';
import ShortcutsBlob from '~/shortcuts_blob';
import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
export default () => {
new LineHighlighter(); // eslint-disable-line no-new
new BlobLinePermalinkUpdater( // eslint-disable-line no-new
document.querySelector('#blob-content-holder'),
'.diff-line-num[data-line-number]',
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
);
const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsNavigation(); // eslint-disable-line no-new
new ShortcutsBlob({ // eslint-disable-line no-new
skipResetBindings: true,
fileBlobPermalinkUrl,
});
new BlobForkSuggestion({ // eslint-disable-line no-new
openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'),
forkButtons: document.querySelectorAll('.js-fork-suggestion-button'),
cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
}).init();
};
import initForm from '../form';
export default () => {
initForm();
};
/* eslint-disable no-new */
import GLForm from '~/gl_form';
import IssuableForm from '~/issuable_form';
import LabelsSelect from '~/labels_select';
import MilestoneSelect from '~/milestone_select';
import ShortcutsNavigation from '~/shortcuts_navigation';
import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
export default () => {
new ShortcutsNavigation();
new GLForm($('.issue-form'), true);
new IssuableForm($('.issue-form'));
new LabelsSelect();
new MilestoneSelect();
new IssuableTemplateSelectors();
};
/* eslint-disable no-new */
import IssuableIndex from '~/issuable_index';
import ShortcutsNavigation from '~/shortcuts_navigation';
import UsersSelect from '~/users_select';
export default () => {
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
const filteredSearchManager = new gl.FilteredSearchManager('issues');
filteredSearchManager.setup();
}
new IssuableIndex('issue_');
new ShortcutsNavigation();
new UsersSelect();
};
import initForm from '../form';
export default () => {
initForm();
};
/* eslint-disable no-new */
import initIssuableSidebar from '~/init_issuable_sidebar';
import Issue from '~/issue';
import ShortcutsIssuable from '~/shortcuts_issuable';
import ZenMode from '~/zen_mode';
export default () => {
new Issue();
new ShortcutsIssuable();
new ZenMode();
initIssuableSidebar();
};
import Labels from '~/labels';
export default () => new Labels();
import initLabels from '~/init_labels';
export default initLabels;
import Labels from '~/labels';
export default () => new Labels();
import IssuableIndex from '~/issuable_index';
import ShortcutsNavigation from '~/shortcuts_navigation';
import UsersSelect from '~/users_select';
export default () => {
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
const filteredSearchManager = new gl.FilteredSearchManager('merge_requests');
filteredSearchManager.setup();
}
new IssuableIndex('merge_request_'); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
};
import initForm from '../../../../shared/milestones/form';
export default () => initForm();
import initForm from '../../../../shared/milestones/form';
export default () => initForm();
import RefSelectDropdown from '../../../../ref_select_dropdown';
import ZenMode from '../../../../zen_mode';
import GLForm from '../../../../gl_form';
export default () => {
new ZenMode(); // eslint-disable-line no-new
new GLForm($('.tag-form'), true); // eslint-disable-line no-new
new RefSelectDropdown($('.js-branch-select')); // eslint-disable-line no-new
};
import TreeView from '../../../../tree';
import ShortcutsNavigation from '../../../../shortcuts_navigation';
import BlobViewer from '../../../../blob/viewer';
import NewCommitForm from '../../../../new_commit_form';
import { ajaxGet } from '../../../../lib/utils/common_utils';
export default () => {
new ShortcutsNavigation(); // eslint-disable-line no-new
new TreeView(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
$('#tree-slider').waitForImages(() =>
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
};
import form from '../form';
export default form;
import GLForm from '~/gl_form';
import ZenMode from '~/zen_mode';
export default () => {
new GLForm($('.snippet-form'), false); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
};
import form from '../form';
export default form;
...@@ -92,6 +92,7 @@ ...@@ -92,6 +92,7 @@
<loading-icon <loading-icon
v-if="isLoading" v-if="isLoading"
size="2" size="2"
class="prepend-top-default append-bottom-default"
/> />
</div> </div>
</template> </template>
...@@ -19,12 +19,9 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -19,12 +19,9 @@ document.addEventListener('DOMContentLoaded', () => {
return; return;
} }
$(navEl).on('show.bs.dropdown', (e) => { $(navEl).on('shown.bs.dropdown', () => {
const dropdownEl = $(e.currentTarget).find('.projects-dropdown-menu');
dropdownEl.one('transitionend', () => {
eventHub.$emit('dropdownOpen'); eventHub.$emit('dropdownOpen');
}); });
});
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
......
import _ from 'underscore'; import _ from 'underscore';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown'; import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
import ProtectedBranchDropdown from './protected_branch_dropdown'; import CreateItemDropdown from '../create_item_dropdown';
import AccessorUtilities from '../lib/utils/accessor'; import AccessorUtilities from '../lib/utils/accessor';
const PB_LOCAL_STORAGE_KEY = 'protected-branches-defaults'; const PB_LOCAL_STORAGE_KEY = 'protected-branches-defaults';
...@@ -35,10 +35,12 @@ export default class ProtectedBranchCreate { ...@@ -35,10 +35,12 @@ export default class ProtectedBranchCreate {
onSelect: this.onSelectCallback, onSelect: this.onSelectCallback,
}); });
// Protected branch dropdown this.createItemDropdown = new CreateItemDropdown({
this.protectedBranchDropdown = new ProtectedBranchDropdown({
$dropdown: $protectedBranchDropdown, $dropdown: $protectedBranchDropdown,
defaultToggleLabel: 'Protected Branch',
fieldName: 'protected_branch[name]',
onSelect: this.onSelectCallback, onSelect: this.onSelectCallback,
getData: ProtectedBranchCreate.getProtectedBranches,
}); });
this.loadPreviousSelection($allowedToMergeDropdown.data('glDropdown'), $allowedToPushDropdown.data('glDropdown')); this.loadPreviousSelection($allowedToMergeDropdown.data('glDropdown'), $allowedToPushDropdown.data('glDropdown'));
...@@ -60,6 +62,10 @@ export default class ProtectedBranchCreate { ...@@ -60,6 +62,10 @@ export default class ProtectedBranchCreate {
this.$form.find('input[type="submit"]').attr('disabled', completedForm); this.$form.find('input[type="submit"]').attr('disabled', completedForm);
} }
static getProtectedBranches(term, callback) {
callback(gon.open_branches);
}
loadPreviousSelection(mergeDropdown, pushDropdown) { loadPreviousSelection(mergeDropdown, pushDropdown) {
let mergeIndex = 0; let mergeIndex = 0;
let pushIndex = 0; let pushIndex = 0;
......
import _ from 'underscore';
export default class ProtectedBranchDropdown {
/**
* @param {Object} options containing
* `$dropdown` target element
* `onSelect` event callback
* $dropdown must be an element created using `dropdown_branch()` rails helper
*/
constructor(options) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
this.$protectedBranch = this.$dropdownContainer.find('.js-create-new-protected-branch');
this.buildDropdown();
this.bindEvents();
// Hide footer
this.toggleFooter(true);
}
buildDropdown() {
this.$dropdown.glDropdown({
data: this.getProtectedBranches.bind(this),
filterable: true,
remote: false,
search: {
fields: ['title'],
},
selectable: true,
toggleLabel(selected) {
return (selected && 'id' in selected) ? selected.title : 'Protected Branch';
},
fieldName: 'protected_branch[name]',
text(protectedBranch) {
return _.escape(protectedBranch.title);
},
id(protectedBranch) {
return _.escape(protectedBranch.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
options.e.preventDefault();
this.onSelect();
},
});
}
bindEvents() {
this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this));
}
onClickCreateWildcard(e) {
e.preventDefault();
// Refresh the dropdown's data, which ends up calling `getProtectedBranches`
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex();
}
getProtectedBranches(term, callback) {
if (this.selectedBranch) {
callback(gon.open_branches.concat(this.selectedBranch));
} else {
callback(gon.open_branches);
}
}
toggleCreateNewButton(branchName) {
if (branchName) {
this.selectedBranch = {
title: branchName,
id: branchName,
text: branchName,
};
this.$dropdownContainer
.find('.js-create-new-protected-branch code')
.text(branchName);
}
this.toggleFooter(!branchName);
}
toggleFooter(toggleState) {
this.$dropdownFooter.toggleClass('hidden', toggleState);
}
}
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
import ProtectedTagDropdown from './protected_tag_dropdown'; import CreateItemDropdown from '../create_item_dropdown';
export default class ProtectedTagCreate { export default class ProtectedTagCreate {
constructor() { constructor() {
...@@ -24,9 +24,12 @@ export default class ProtectedTagCreate { ...@@ -24,9 +24,12 @@ export default class ProtectedTagCreate {
$allowedToCreateDropdown.data('glDropdown').selectRowAtIndex(0); $allowedToCreateDropdown.data('glDropdown').selectRowAtIndex(0);
// Protected tag dropdown // Protected tag dropdown
this.protectedTagDropdown = new ProtectedTagDropdown({ this.createItemDropdown = new CreateItemDropdown({
$dropdown: this.$form.find('.js-protected-tag-select'), $dropdown: this.$form.find('.js-protected-tag-select'),
defaultToggleLabel: 'Protected Tag',
fieldName: 'protected_tag[name]',
onSelect: this.onSelectCallback, onSelect: this.onSelectCallback,
getData: ProtectedTagCreate.getProtectedTags,
}); });
} }
...@@ -38,4 +41,8 @@ export default class ProtectedTagCreate { ...@@ -38,4 +41,8 @@ export default class ProtectedTagCreate {
this.$form.find('input[type="submit"]').attr('disabled', !($tagInput.val() && $allowedToCreateInput.length)); this.$form.find('input[type="submit"]').attr('disabled', !($tagInput.val() && $allowedToCreateInput.length));
} }
static getProtectedTags(term, callback) {
callback(gon.open_tags);
}
} }
import ZenMode from '../../zen_mode';
import DueDateSelectors from '../../due_date_select';
import GLForm from '../../gl_form';
export default (initGFM = true) => {
new ZenMode(); // eslint-disable-line no-new
new DueDateSelectors(); // eslint-disable-line no-new
new GLForm($('.milestone-form'), initGFM); // eslint-disable-line no-new
};
<script> <script>
/* eslint-disable vue/require-default-prop */ import { __ } from '~/locale';
import { __ } from '../../../locale'; import icon from '~/vue_shared/components/icon.vue';
import toggleButton from '~/vue_shared/components/toggle_button.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import loadingButton from '../../../vue_shared/components/loading_button.vue';
const ICON_ON = 'notifications';
const ICON_OFF = 'notifications-off';
const LABEL_ON = __('Notifications on');
const LABEL_OFF = __('Notifications off');
export default { export default {
directives: {
tooltip,
},
components: { components: {
loadingButton, icon,
toggleButton,
}, },
props: { props: {
loading: { loading: {
...@@ -17,22 +27,23 @@ ...@@ -17,22 +27,23 @@
subscribed: { subscribed: {
type: Boolean, type: Boolean,
required: false, required: false,
default: null,
}, },
id: { id: {
type: Number, type: Number,
required: false, required: false,
default: null,
}, },
}, },
computed: { computed: {
buttonLabel() { showLoadingState() {
let label; return this.subscribed === null;
if (this.subscribed === false) { },
label = __('Subscribe'); notificationIcon() {
} else if (this.subscribed === true) { return this.subscribed ? ICON_ON : ICON_OFF;
label = __('Unsubscribe'); },
} notificationTooltip() {
return this.subscribed ? LABEL_ON : LABEL_OFF;
return label;
}, },
}, },
methods: { methods: {
...@@ -46,21 +57,29 @@ ...@@ -46,21 +57,29 @@
<template> <template>
<div> <div>
<div class="sidebar-collapsed-icon"> <div class="sidebar-collapsed-icon">
<i <span
class="fa fa-rss" v-tooltip
aria-hidden="true" :title="notificationTooltip"
data-container="body"
data-placement="left"
> >
</i> <icon
:name="notificationIcon"
:size="16"
aria-hidden="true"
class="sidebar-item-icon is-active"
/>
</span>
</div> </div>
<span class="issuable-header-text hide-collapsed pull-left"> <span class="issuable-header-text hide-collapsed pull-left">
{{ __('Notifications') }} {{ __('Notifications') }}
</span> </span>
<loading-button <toggle-button
ref="loadingButton" ref="toggleButton"
class="btn btn-default pull-right hide-collapsed js-issuable-subscribe-button" class="pull-right hide-collapsed js-issuable-subscribe-button"
:loading="loading" :is-loading="showLoadingState"
:label="buttonLabel" :value="subscribed"
@click="toggleSubscription" @change="toggleSubscription"
/> />
</div> </div>
</template> </template>
import successSvg from 'icons/_icon_status_success.svg'; import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg'; import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll'; import simplePoll from '~/lib/utils/simple_poll';
import MergeRequest from '../../../merge_request';
import Flash from '../../../flash'; import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
...@@ -165,11 +166,9 @@ export default { ...@@ -165,11 +166,9 @@ export default {
// If state is merged we should update the widget and stop the polling // If state is merged we should update the widget and stop the polling
eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('FetchActionsContent'); eventHub.$emit('FetchActionsContent');
if (window.mergeRequest) { MergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged');
window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged'); MergeRequest.hideCloseButton();
window.mergeRequest.hideCloseButton(); MergeRequest.decreaseCounter();
window.mergeRequest.decreaseCounter();
}
stopPolling(); stopPolling();
// If user checked remove source branch and we didn't remove the branch yet // If user checked remove source branch and we didn't remove the branch yet
......
...@@ -122,7 +122,7 @@ ...@@ -122,7 +122,7 @@
> >
<button <button
type="button" type="button"
class="btn pull-left" class="btn"
:class="btnCancelKindClass" :class="btnCancelKindClass"
@click="emitCancel($event)" @click="emitCancel($event)"
data-dismiss="modal" data-dismiss="modal"
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
<button <button
v-if="primaryButtonLabel" v-if="primaryButtonLabel"
type="button" type="button"
class="btn pull-right js-primary-button" class="btn js-primary-button"
:disabled="submitDisabled" :disabled="submitDisabled"
:class="btnKindClass" :class="btnKindClass"
@click="emitSubmit($event)" @click="emitSubmit($event)"
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
props: {
cssClass: {
type: String,
required: false,
default: '',
},
successLabel: {
type: String,
required: true,
},
failureLabel: {
type: String,
required: true,
},
neutralLabel: {
type: String,
required: true,
},
successCount: {
type: Number,
required: true,
},
failureCount: {
type: Number,
required: true,
},
totalCount: {
type: Number,
required: true,
},
},
computed: {
neutralCount() {
return this.totalCount - this.successCount - this.failureCount;
},
successPercent() {
return this.getPercent(this.successCount);
},
successBarStyle() {
return this.barStyle(this.successPercent);
},
successTooltip() {
return this.getTooltip(this.successLabel, this.successCount);
},
failurePercent() {
return this.getPercent(this.failureCount);
},
failureBarStyle() {
return this.barStyle(this.failurePercent);
},
failureTooltip() {
return this.getTooltip(this.failureLabel, this.failureCount);
},
neutralPercent() {
return this.getPercent(this.neutralCount);
},
neutralBarStyle() {
return this.barStyle(this.neutralPercent);
},
neutralTooltip() {
return this.getTooltip(this.neutralLabel, this.neutralCount);
},
},
methods: {
getPercent(count) {
return Math.ceil((count / this.totalCount) * 100);
},
barStyle(percent) {
return `width: ${percent}%;`;
},
getTooltip(label, count) {
return `${label}: ${count}`;
},
},
};
</script>
<template>
<div
class="stacked-progress-bar"
:class="cssClass"
>
<span
v-if="!totalCount"
class="status-unavailable"
>
{{ __("Not available") }}
</span>
<span
v-tooltip
v-if="successPercent"
class="status-green"
data-placement="bottom"
:title="successTooltip"
:style="successBarStyle"
>
{{ successPercent }}%
</span>
<span
v-tooltip
v-if="neutralPercent"
class="status-neutral"
data-placement="bottom"
:title="neutralTooltip"
:style="neutralBarStyle"
>
{{ neutralPercent }}%
</span>
<span
v-tooltip
v-if="failurePercent"
class="status-red"
data-placement="bottom"
:title="failureTooltip"
:style="failureBarStyle"
>
{{ failurePercent }}%
</span>
</div>
</template>
...@@ -23,11 +23,12 @@ ...@@ -23,11 +23,12 @@
name: { name: {
type: String, type: String,
required: false, required: false,
default: '', default: null,
}, },
value: { value: {
type: Boolean, type: Boolean,
required: true, required: false,
default: null,
}, },
disabledInput: { disabledInput: {
type: Boolean, type: Boolean,
...@@ -61,6 +62,7 @@ ...@@ -61,6 +62,7 @@
<template> <template>
<label class="toggle-wrapper"> <label class="toggle-wrapper">
<input <input
v-if="name"
type="hidden" type="hidden"
:name="name" :name="name"
:value="value" :value="value"
......
...@@ -59,3 +59,4 @@ ...@@ -59,3 +59,4 @@
@import "framework/snippets"; @import "framework/snippets";
@import "framework/memory_graph"; @import "framework/memory_graph";
@import "framework/responsive_tables"; @import "framework/responsive_tables";
@import "framework/stacked-progress-bar";
...@@ -666,6 +666,16 @@ ...@@ -666,6 +666,16 @@
} }
} }
.dropdown-create-new-item-button {
@include dropdown-link;
width: 100%;
background-color: transparent;
border: 0;
text-align: left;
text-overflow: ellipsis;
}
.dropdown-loading { .dropdown-loading {
position: absolute; position: absolute;
top: 0; top: 0;
......
...@@ -104,7 +104,10 @@ ...@@ -104,7 +104,10 @@
img { img {
height: 28px; height: 28px;
margin-right: 8px;
+ .logo-text {
margin-left: 8px;
}
} }
&.wrap { &.wrap {
......
.modal-header { .modal-header {
background-color: $modal-body-bg;
padding: #{3 * $grid-size} #{2 * $grid-size}; padding: #{3 * $grid-size} #{2 * $grid-size};
.page-title { .page-title {
...@@ -8,6 +9,7 @@ ...@@ -8,6 +9,7 @@
.modal-body { .modal-body {
background-color: $modal-body-bg; background-color: $modal-body-bg;
min-height: $modal-body-height;
position: relative; position: relative;
padding: #{3 * $grid-size} #{2 * $grid-size}; padding: #{3 * $grid-size} #{2 * $grid-size};
...@@ -20,6 +22,30 @@ ...@@ -20,6 +22,30 @@
} }
} }
.modal-footer {
display: flex;
flex-direction: row;
.btn + .btn {
margin-left: $grid-size;
}
@media (max-width: $screen-xs-max) {
flex-direction: column;
.btn + .btn {
margin-left: 0;
margin-top: $grid-size;
}
}
@media (min-width: $screen-sm-min) {
.btn:first-of-type {
margin-left: auto;
}
}
}
body.modal-open { body.modal-open {
overflow: hidden; overflow: hidden;
} }
...@@ -32,12 +58,6 @@ body.modal-open { ...@@ -32,12 +58,6 @@ body.modal-open {
} }
} }
@media (min-width: $screen-md-min) {
.modal-dialog {
width: 860px;
}
}
@media (min-width: $screen-lg-min) { @media (min-width: $screen-lg-min) {
.modal-full { .modal-full {
width: 98%; width: 98%;
......
.stacked-progress-bar {
display: flex;
height: 16px;
border-radius: 10px;
overflow: hidden;
background-color: $theme-gray-100;
.status-unavailable,
.status-green,
.status-neutral,
.status-red, {
height: 100%;
min-width: 25px;
padding: 0 5px;
font-size: $tooltip-font-size;
font-weight: normal;
color: $white-light;
line-height: 16px;
&:hover {
cursor: pointer;
}
}
.status-unavailable {
padding: 0 10px;
color: $theme-gray-700;
}
.status-green {
background-color: $green-500;
&:hover {
background-color: $green-600;
}
}
.status-neutral {
background-color: $theme-gray-200;
color: $gl-gray-dark;
&:hover {
background-color: $theme-gray-300;
}
}
.status-red {
background-color: $red-500;
&:hover {
background-color: $red-600;
}
}
}
...@@ -194,6 +194,6 @@ $modal-body-bg: $white-light; ...@@ -194,6 +194,6 @@ $modal-body-bg: $white-light;
//** Modal footer border color //** Modal footer border color
// $modal-footer-border-color: $modal-header-border-color // $modal-footer-border-color: $modal-header-border-color
// $modal-lg: 900px $modal-lg: 860px;
// $modal-md: 600px $modal-md: 540px;
// $modal-sm: 300px // $modal-sm: 300px
...@@ -733,3 +733,8 @@ $popup-box-shadow-color: rgba(90, 90, 90, 0.05); ...@@ -733,3 +733,8 @@ $popup-box-shadow-color: rgba(90, 90, 90, 0.05);
Multi file editor Multi file editor
*/ */
$border-color-settings: #e1e1e1; $border-color-settings: #e1e1e1;
/*
Modals
*/
$modal-body-height: 134px;
...@@ -162,10 +162,6 @@ ...@@ -162,10 +162,6 @@
border: 0; border: 0;
} }
span {
display: inline-block;
}
.select2-container span { .select2-container span {
margin-top: 0; margin-top: 0;
} }
......
...@@ -895,17 +895,6 @@ pre.light-well { ...@@ -895,17 +895,6 @@ pre.light-well {
} }
} }
.create-new-protected-branch-button,
.create-new-protected-tag-button {
@include dropdown-link;
width: 100%;
background-color: transparent;
border: 0;
text-align: left;
text-overflow: ellipsis;
}
.protected-branches-list, .protected-branches-list,
.protected-tags-list { .protected-tags-list {
margin-bottom: 30px; margin-bottom: 30px;
......
...@@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_read_pipeline!, only: [:pipelines] before_action :authorize_read_pipeline!, only: [:pipelines]
before_action :commit before_action :commit
before_action :define_commit_vars, only: [:show, :diff_for_path, :pipelines] before_action :define_commit_vars, only: [:show, :diff_for_path, :pipelines, :merge_requests]
before_action :define_note_vars, only: [:show, :diff_for_path] before_action :define_note_vars, only: [:show, :diff_for_path]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick] before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
...@@ -52,6 +52,18 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -52,6 +52,18 @@ class Projects::CommitController < Projects::ApplicationController
end end
end end
def merge_requests
@merge_requests = @commit.merge_requests.map do |mr|
{ iid: mr.iid, path: merge_request_path(mr), title: mr.title }
end
respond_to do |format|
format.json do
render json: @merge_requests.to_json
end
end
end
def branches def branches
# branch_names_contains/tag_names_contains can take a long time when there are thousands of # branch_names_contains/tag_names_contains can take a long time when there are thousands of
# branches/tags - each `git branch --contains xxx` request can consume a cpu core. # branches/tags - each `git branch --contains xxx` request can consume a cpu core.
......
...@@ -29,7 +29,7 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -29,7 +29,7 @@ class Projects::JobsController < Projects::ApplicationController
:project, :project,
:tags :tags
]) ])
@builds = @builds.page(params[:page]).per(30) @builds = @builds.page(params[:page]).per(30).without_count
end end
def cancel_all def cancel_all
......
...@@ -43,11 +43,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -43,11 +43,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
end end
def diffs def diffs
@diffs = if @merge_request.can_be_created @diffs = @merge_request.diffs(diff_options) if @merge_request.can_be_created
@merge_request.diffs(diff_options)
else
[]
end
@diff_notes_disabled = true @diff_notes_disabled = true
......
module BlobHelper module BlobHelper
def highlight(blob_name, blob_content, repository: nil, plain: false) def highlight(blob_name, blob_content, repository: nil, plain: false)
plain ||= blob_content.length > Blob::MAXIMUM_TEXT_HIGHLIGHT_SIZE
highlighted = Gitlab::Highlight.highlight(blob_name, blob_content, plain: plain, repository: repository) highlighted = Gitlab::Highlight.highlight(blob_name, blob_content, plain: plain, repository: repository)
raw %(<pre class="code highlight"><code>#{highlighted}</code></pre>) raw %(<pre class="code highlight"><code>#{highlighted}</code></pre>)
end end
......
...@@ -8,6 +8,7 @@ module Ci ...@@ -8,6 +8,7 @@ module Ci
MissingDependenciesError = Class.new(StandardError) MissingDependenciesError = Class.new(StandardError)
belongs_to :project, inverse_of: :builds
belongs_to :runner belongs_to :runner
belongs_to :trigger_request belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User' belongs_to :erased_by, class_name: 'User'
......
...@@ -7,7 +7,7 @@ module Ci ...@@ -7,7 +7,7 @@ module Ci
include Presentable include Presentable
include Gitlab::OptimisticLocking include Gitlab::OptimisticLocking
belongs_to :project belongs_to :project, inverse_of: :pipelines
belongs_to :user belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
......
...@@ -238,6 +238,10 @@ class Commit ...@@ -238,6 +238,10 @@ class Commit
notes.includes(:author) notes.includes(:author)
end end
def merge_requests
@merge_requests ||= project.merge_requests.by_commit_sha(sha)
end
def method_missing(method, *args, &block) def method_missing(method, *args, &block)
@raw.__send__(method, *args, &block) # rubocop:disable GitlabSecurity/PublicSend @raw.__send__(method, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end end
...@@ -342,10 +346,11 @@ class Commit ...@@ -342,10 +346,11 @@ class Commit
@merged_merge_request_hash[current_user] @merged_merge_request_hash[current_user]
end end
def has_been_reverted?(current_user, noteable = self) def has_been_reverted?(current_user, notes_association = nil)
ext = all_references(current_user) ext = all_references(current_user)
notes_association ||= notes_with_associations
noteable.notes_with_associations.system.each do |note| notes_association.system.each do |note|
note.all_references(current_user, extractor: ext) note.all_references(current_user, extractor: ext)
end end
...@@ -367,19 +372,19 @@ class Commit ...@@ -367,19 +372,19 @@ class Commit
# uri_type('doc/README.md') # => :blob # uri_type('doc/README.md') # => :blob
# uri_type('doc/logo.png') # => :raw # uri_type('doc/logo.png') # => :raw
# uri_type('doc/api') # => :tree # uri_type('doc/api') # => :tree
# uri_type('not/found') # => :nil # uri_type('not/found') # => nil
# #
# Returns a symbol # Returns a symbol
def uri_type(path) def uri_type(path)
entry = @raw.rugged_tree_entry(path) entry = @raw.tree_entry(path)
return unless entry
if entry[:type] == :blob if entry[:type] == :blob
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project) blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project)
blob.image? || blob.video? ? :raw : :blob blob.image? || blob.video? ? :raw : :blob
else else
entry[:type] entry[:type]
end end
rescue Rugged::TreeError
nil
end end
def raw_diffs(*args) def raw_diffs(*args)
......
module ResolvableDiscussion module ResolvableDiscussion
extend ActiveSupport::Concern extend ActiveSupport::Concern
include ::Gitlab::Utils::StrongMemoize
included do included do
# A number of properties of this `Discussion`, like `first_note` and `resolvable?`, are memoized. # A number of properties of this `Discussion`, like `first_note` and `resolvable?`, are memoized.
...@@ -31,27 +32,37 @@ module ResolvableDiscussion ...@@ -31,27 +32,37 @@ module ResolvableDiscussion
end end
def resolvable? def resolvable?
@resolvable ||= potentially_resolvable? && notes.any?(&:resolvable?) strong_memoize(:resolvable) do
potentially_resolvable? && notes.any?(&:resolvable?)
end
end end
def resolved? def resolved?
@resolved ||= resolvable? && notes.none?(&:to_be_resolved?) strong_memoize(:resolved) do
resolvable? && notes.none?(&:to_be_resolved?)
end
end end
def first_note def first_note
@first_note ||= notes.first strong_memoize(:first_note) do
notes.first
end
end end
def first_note_to_resolve def first_note_to_resolve
return unless resolvable? return unless resolvable?
@first_note_to_resolve ||= notes.find(&:to_be_resolved?) # rubocop:disable Gitlab/ModuleWithInstanceVariables strong_memoize(:first_note_to_resolve) do
notes.find(&:to_be_resolved?)
end
end end
def last_resolved_note def last_resolved_note
return unless resolved? return unless resolved?
@last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last # rubocop:disable Gitlab/ModuleWithInstanceVariables strong_memoize(:last_resolved_note) do
resolved_notes.sort_by(&:resolved_at).last
end
end end
def resolved_notes def resolved_notes
...@@ -93,8 +104,8 @@ module ResolvableDiscussion ...@@ -93,8 +104,8 @@ module ResolvableDiscussion
# Set the notes array to the updated notes # Set the notes array to the updated notes
@notes = notes_relation.fresh.to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables @notes = notes_relation.fresh.to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables
self.class.memoized_values.each do |var| self.class.memoized_values.each do |name|
instance_variable_set(:"@#{var}", nil) clear_memoization(name)
end end
end end
end end
...@@ -3,6 +3,7 @@ module ShaAttribute ...@@ -3,6 +3,7 @@ module ShaAttribute
module ClassMethods module ClassMethods
def sha_attribute(name) def sha_attribute(name)
return if ENV['STATIC_VERIFICATION']
return unless table_exists? return unless table_exists?
column = columns.find { |c| c.name == name.to_s } column = columns.find { |c| c.name == name.to_s }
......
...@@ -140,7 +140,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -140,7 +140,9 @@ class MergeRequest < ActiveRecord::Base
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
scope :from_source_branches, ->(branches) { where(source_branch: branches) } scope :from_source_branches, ->(branches) { where(source_branch: branches) }
scope :by_commit_sha, ->(sha) do
where('EXISTS (?)', MergeRequestDiff.select(1).where('merge_requests.latest_merge_request_diff_id = merge_request_diffs.id').by_commit_sha(sha)).reorder(nil)
end
scope :join_project, -> { joins(:target_project) } scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) } scope :references_project, -> { references(:target_project) }
scope :assigned, -> { where("assignee_id IS NOT NULL") } scope :assigned, -> { where("assignee_id IS NOT NULL") }
...@@ -982,7 +984,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -982,7 +984,16 @@ class MergeRequest < ActiveRecord::Base
end end
def can_be_reverted?(current_user) def can_be_reverted?(current_user)
merge_commit && !merge_commit.has_been_reverted?(current_user, self) return false unless merge_commit
merged_at = metrics&.merged_at
notes_association = notes_with_associations
if merged_at
notes_association = notes_association.where('created_at > ?', merged_at)
end
!merge_commit.has_been_reverted?(current_user, notes_association)
end end
def can_be_cherry_picked? def can_be_cherry_picked?
......
...@@ -28,6 +28,9 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -28,6 +28,9 @@ class MergeRequestDiff < ActiveRecord::Base
end end
scope :viewable, -> { without_state(:empty) } scope :viewable, -> { without_state(:empty) }
scope :by_commit_sha, ->(sha) do
joins(:merge_request_diff_commits).where(merge_request_diff_commits: { sha: sha }).reorder(nil)
end
scope :recent, -> { order(id: :desc).limit(100) } scope :recent, -> { order(id: :desc).limit(100) }
......
...@@ -20,6 +20,7 @@ class Project < ActiveRecord::Base ...@@ -20,6 +20,7 @@ class Project < ActiveRecord::Base
include GroupDescendant include GroupDescendant
include Gitlab::SQL::Pattern include Gitlab::SQL::Pattern
include DeploymentPlatform include DeploymentPlatform
include ::Gitlab::Utils::StrongMemoize
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
extend Gitlab::CurrentSettings extend Gitlab::CurrentSettings
...@@ -198,13 +199,13 @@ class Project < ActiveRecord::Base ...@@ -198,13 +199,13 @@ class Project < ActiveRecord::Base
has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :commit_statuses has_many :commit_statuses
has_many :pipelines, class_name: 'Ci::Pipeline' has_many :pipelines, class_name: 'Ci::Pipeline', inverse_of: :project
# Ci::Build objects store data on the file system such as artifact files and # Ci::Build objects store data on the file system such as artifact files and
# build traces. Currently there's no efficient way of removing this data in # build traces. Currently there's no efficient way of removing this data in
# bulk that doesn't involve loading the rows into memory. As a result we're # bulk that doesn't involve loading the rows into memory. As a result we're
# still using `dependent: :destroy` here. # still using `dependent: :destroy` here.
has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName' has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName'
has_many :runner_projects, class_name: 'Ci::RunnerProject' has_many :runner_projects, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
...@@ -993,9 +994,13 @@ class Project < ActiveRecord::Base ...@@ -993,9 +994,13 @@ class Project < ActiveRecord::Base
end end
def repo_exists? def repo_exists?
@repo_exists ||= repository.exists? strong_memoize(:repo_exists) do
begin
repository.exists?
rescue rescue
@repo_exists = false false
end
end
end end
def root_ref?(branch) def root_ref?(branch)
......
...@@ -46,10 +46,11 @@ class PushEvent < Event ...@@ -46,10 +46,11 @@ class PushEvent < Event
# Returns PushEvent instances for which no merge requests have been created. # Returns PushEvent instances for which no merge requests have been created.
def self.without_existing_merge_requests def self.without_existing_merge_requests
existing_mrs = MergeRequest.except(:order) existing_mrs = MergeRequest.except(:order, :where)
.select(1) .select(1)
.where('merge_requests.source_project_id = events.project_id') .where('merge_requests.source_project_id = events.project_id')
.where('merge_requests.source_branch = push_event_payloads.ref') .where('merge_requests.source_branch = push_event_payloads.ref')
.where(state: :opened)
# For reasons unknown the use of #eager_load will result in the # For reasons unknown the use of #eager_load will result in the
# "push_event_payload" association not being set. Because of this we're # "push_event_payload" association not being set. Because of this we're
......
...@@ -831,13 +831,12 @@ class Repository ...@@ -831,13 +831,12 @@ class Repository
end end
def can_be_merged?(source_sha, target_branch) def can_be_merged?(source_sha, target_branch)
our_commit = rugged.branches[target_branch].target raw_repository.gitaly_migrate(:can_be_merged) do |is_enabled|
their_commit = rugged.lookup(source_sha) if is_enabled
gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
if our_commit && their_commit
!rugged.merge_commits(our_commit, their_commit).conflicts?
else else
false rugged_can_be_merged?(source_sha, target_branch)
end
end end
end end
...@@ -895,15 +894,18 @@ class Repository ...@@ -895,15 +894,18 @@ class Repository
branch = Gitlab::Git::Branch.find(self, branch_or_name) branch = Gitlab::Git::Branch.find(self, branch_or_name)
if branch if branch
@root_ref_sha ||= commit(root_ref).sha same_head = branch.target == root_ref_sha
same_head = branch.target == @root_ref_sha merged = ancestor?(branch.target, root_ref_sha)
merged = ancestor?(branch.target, @root_ref_sha)
!same_head && merged !same_head && merged
else else
nil nil
end end
end end
def root_ref_sha
@root_ref_sha ||= commit(root_ref).sha
end
delegate :merged_branch_names, to: :raw_repository 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)
...@@ -930,15 +932,17 @@ class Repository ...@@ -930,15 +932,17 @@ class Repository
return [] if empty? || query.blank? return [] if empty? || query.blank?
offset = 2 offset = 2
args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
run_git(args).first.scrub.split(/^--$/) run_git(args).first.scrub.split(/^--$/)
end end
def search_files_by_name(query, ref) def search_files_by_name(query, ref)
return [] if empty? || query.blank? safe_query = Regexp.escape(query.sub(/^\/*/, ""))
args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)}) return [] if empty? || safe_query.blank?
args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query})
run_git(args).first.lines.map(&:strip) run_git(args).first.lines.map(&:strip)
end end
...@@ -1129,6 +1133,14 @@ class Repository ...@@ -1129,6 +1133,14 @@ class Repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki)) Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki))
end end
def gitaly_can_be_merged?(their_commit, our_commit)
!raw_repository.gitaly_conflicts_client(our_commit, their_commit).conflicts?
end
def rugged_can_be_merged?(their_commit, our_commit)
!rugged.merge_commits(our_commit, their_commit).conflicts?
end
def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset) def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
ref ||= root_ref ref ||= root_ref
......
...@@ -8,7 +8,7 @@ class Route < ActiveRecord::Base ...@@ -8,7 +8,7 @@ class Route < ActiveRecord::Base
presence: true, presence: true,
uniqueness: { case_sensitive: false } uniqueness: { case_sensitive: false }
validate :ensure_permanent_paths validate :ensure_permanent_paths, if: :path_changed?
after_create :delete_conflicting_redirects after_create :delete_conflicting_redirects
after_update :delete_conflicting_redirects, if: :path_changed? after_update :delete_conflicting_redirects, if: :path_changed?
......
...@@ -55,6 +55,9 @@ class User < ActiveRecord::Base ...@@ -55,6 +55,9 @@ class User < ActiveRecord::Base
devise :lockable, :recoverable, :rememberable, :trackable, devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable :validatable, :omniauthable, :confirmable, :registerable
BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \
"administrator if you think this is an error.".freeze
# Override Devise::Models::Trackable#update_tracked_fields! # Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour # to limit database writes to at most once every hour
def update_tracked_fields!(request) def update_tracked_fields!(request)
...@@ -217,8 +220,7 @@ class User < ActiveRecord::Base ...@@ -217,8 +220,7 @@ class User < ActiveRecord::Base
end end
def inactive_message def inactive_message
"Your account has been blocked. Please contact your GitLab " \ BLOCKED_MESSAGE
"administrator if you think this is an error."
end end
end end
end end
......
...@@ -41,8 +41,11 @@ class SystemHooksService ...@@ -41,8 +41,11 @@ class SystemHooksService
when User when User
data.merge!(user_data(model)) data.merge!(user_data(model))
if event == :rename case event
when :rename
data[:old_username] = model.username_was data[:old_username] = model.username_was
when :failed_login
data[:state] = model.state
end end
when ProjectMember when ProjectMember
data.merge!(project_member_data(model)) data.merge!(project_member_data(model))
......
...@@ -68,8 +68,6 @@ module SystemNoteService ...@@ -68,8 +68,6 @@ module SystemNoteService
# #
# Returns the created Note object # Returns the created Note object
def change_issue_assignees(issue, project, author, old_assignees) def change_issue_assignees(issue, project, author, old_assignees)
body =
if issue.assignees.any? && old_assignees.any?
unassigned_users = old_assignees - issue.assignees unassigned_users = old_assignees - issue.assignees
added_users = issue.assignees.to_a - old_assignees added_users = issue.assignees.to_a - old_assignees
...@@ -77,12 +75,7 @@ module SystemNoteService ...@@ -77,12 +75,7 @@ module SystemNoteService
text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any? text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any? text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any?
text_parts.join(' and ') body = text_parts.join(' and ')
elsif old_assignees.any?
"removed assignee"
elsif issue.assignees.any?
"assigned to #{issue.assignees.map(&:to_reference).to_sentence}"
end
create_note(NoteSummary.new(issue, project, author, body, action: 'assignee')) create_note(NoteSummary.new(issue, project, author, body, action: 'assignee'))
end end
......
...@@ -3,6 +3,34 @@ ...@@ -3,6 +3,34 @@
%div{ class: container_class } %div{ class: container_class }
.admin-dashboard.prepend-top-default .admin-dashboard.prepend-top-default
.row
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
= link_to admin_projects_path do
%h3.text-center
Projects:
= number_with_delimiter(Project.cached_count)
%hr
= link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
= link_to admin_users_path do
%h3.text-center
Users:
= number_with_delimiter(User.count)
%hr
= link_to 'New user', new_admin_user_path, class: "btn btn-new"
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
= link_to admin_groups_path do
%h3.text-center
Groups:
= number_with_delimiter(Group.count)
%hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new"
.row .row
.col-md-4 .col-md-4
.info-well .info-well
...@@ -135,34 +163,6 @@ ...@@ -135,34 +163,6 @@
= Gitlab::Database.adapter_name = Gitlab::Database.adapter_name
%span.pull-right %span.pull-right
= Gitlab::Database.version = Gitlab::Database.version
.row
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
= link_to admin_projects_path do
%h3.text-center
Projects:
= number_with_delimiter(Project.cached_count)
%hr
= link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
= link_to admin_users_path do
%h3.text-center
Users:
= number_with_delimiter(User.count)
%hr
= link_to 'New user', new_admin_user_path, class: "btn btn-new"
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
= link_to admin_groups_path do
%h3.text-center
Groups:
= number_with_delimiter(Group.count)
%hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new"
.row .row
.col-md-4 .col-md-4
.info-well .info-well
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%ul.nav-links %ul.nav-links
%li{ class: active_when(params[:filter].nil?) }> %li{ class: active_when(params[:filter].nil?) }>
= link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects Your projects
%li{ class: active_when(params[:filter] == 'starred') }> %li{ class: active_when(params[:filter] == 'starred') }>
= link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do = link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do
Starred Projects Starred projects
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
%ul.nav-links %ul.nav-links
= nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
= link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
Your Snippets Your snippets
= nav_link(page: explore_snippets_path) do = nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
Explore Snippets Explore snippets
- if current_user - if current_user
.nav-controls.hidden-xs .nav-controls.hidden-xs
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .nav-controls
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do = link_to params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
= icon('rss') = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
......
...@@ -6,8 +6,10 @@ ...@@ -6,8 +6,10 @@
%h1.title %h1.title
= link_to root_path, title: 'Dashboard', id: 'logo' do = link_to root_path, title: 'Dashboard', id: 'logo' do
= brand_header_logo = brand_header_logo
- logo_text = brand_header_logo_type
- if logo_text.present?
%span.logo-text.hidden-xs %span.logo-text.hidden-xs
= brand_header_logo_type = logo_text
- if current_user - if current_user
= render "layouts/nav/dashboard" = render "layouts/nav/dashboard"
......
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - issues_count = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute.count
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute - merge_requests_count = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute.count
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll .nav-sidebar-inner-scroll
...@@ -39,14 +39,14 @@ ...@@ -39,14 +39,14 @@
= sprite_icon('issues') = sprite_icon('issues')
%span.nav-item-name %span.nav-item-name
Issues Issues
%span.badge.count= number_with_delimiter(issues.count) %span.badge.count= number_with_delimiter(issues_count)
%ul.sidebar-sub-level-items %ul.sidebar-sub-level-items
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index'], html_options: { class: "fly-out-top-item" } ) do = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index'], html_options: { class: "fly-out-top-item" } ) do
= link_to issues_group_path(@group) do = link_to issues_group_path(@group) do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Issues') } #{ _('Issues') }
%span.badge.count.issue_counter.fly-out-badge= number_with_delimiter(issues.count) %span.badge.count.issue_counter.fly-out-badge= number_with_delimiter(issues_count)
%li.divider.fly-out-top-item %li.divider.fly-out-top-item
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
= link_to issues_group_path(@group), title: 'List' do = link_to issues_group_path(@group), title: 'List' do
...@@ -69,13 +69,13 @@ ...@@ -69,13 +69,13 @@
= sprite_icon('git-merge') = sprite_icon('git-merge')
%span.nav-item-name %span.nav-item-name
Merge Requests Merge Requests
%span.badge.count= number_with_delimiter(merge_requests.count) %span.badge.count= number_with_delimiter(merge_requests_count)
%ul.sidebar-sub-level-items.is-fly-out-only %ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(path: 'groups#merge_requests', html_options: { class: "fly-out-top-item" } ) do = nav_link(path: 'groups#merge_requests', html_options: { class: "fly-out-top-item" } ) do
= link_to merge_requests_group_path(@group) do = link_to merge_requests_group_path(@group) do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Merge Requests') } #{ _('Merge Requests') }
%span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count) %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests_count)
= nav_link(path: 'group_members#index') do = nav_link(path: 'group_members#index') do
= link_to group_group_members_path(@group) do = link_to group_group_members_path(@group) do
.nav-icon-container .nav-icon-container
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
= render 'profiles/head' = render 'profiles/head'
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
= page_title = page_title
%p %p
GPG keys allow you to verify signed commits. GPG keys allow you to verify signed commits.
.col-lg-9 .col-lg-8
%h5.prepend-top-0 %h5.prepend-top-0
Add a GPG key Add a GPG key
%p.profile-settings-content %p.profile-settings-content
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%li CI variables %li CI variables
%li Any encrypted tokens %li Any encrypted tokens
%p %p
Once the exported file is ready, you will receive a notification email with a download link. Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- if project.export_project_path - if project.export_project_path
= link_to 'Download export', download_export_project_path(project), = link_to 'Download export', download_export_project_path(project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default" rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
......
#modal-create-new-dir.modal #modal-create-new-dir.modal
.modal-dialog .modal-dialog.modal-lg
.modal-content .modal-content
.modal-header .modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } × %a.close{ href: "#", "data-dismiss" => "modal" } ×
......
#modal-upload-blob.modal #modal-upload-blob.modal
.modal-dialog .modal-dialog.modal-lg
.modal-content .modal-content
.modal-header .modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } × %a.close{ href: "#", "data-dismiss" => "modal" } ×
......
...@@ -10,37 +10,28 @@ ...@@ -10,37 +10,28 @@
- if can_create_issue - if can_create_issue
%li %li
= link_to new_project_issue_path(@project) do = link_to _('New issue'), new_project_issue_path(@project)
#{ _('New issue') }
- if merge_project - if merge_project
%li %li
= link_to project_new_merge_request_path(merge_project) do = link_to _('New merge request'), project_new_merge_request_path(merge_project)
#{ _('New merge request') }
- if can_create_snippet - if can_create_snippet
%li %li
= link_to new_project_snippet_path(@project) do = link_to _('New snippet'), new_project_snippet_path(@project)
#{ _('New snippet') }
- if can_create_issue || merge_project || can_create_snippet - if can_create_issue || merge_project || can_create_snippet
%li.divider %li.divider
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
%li %li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
#{ _('New file') }
- unless @project.empty_repo? - unless @project.empty_repo?
%li %li
= link_to new_project_branch_path(@project) do = link_to _('New branch'), new_project_branch_path(@project)
#{ _('New branch') }
%li %li
= link_to new_project_tag_path(@project) do = link_to _('New tag'), new_project_tag_path(@project)
#{ _('New tag') }
- elsif current_user && current_user.already_forked?(@project) - elsif current_user && current_user.already_forked?(@project)
%li %li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
#{ _('New file') }
- elsif can?(current_user, :fork_project, @project) - elsif can?(current_user, :fork_project, @project)
%li %li
- continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'), - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
...@@ -48,5 +39,4 @@ ...@@ -48,5 +39,4 @@
notice_now: edit_in_new_fork_notice_now } notice_now: edit_in_new_fork_notice_now }
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params) continue: continue_params)
= link_to fork_path, method: :post do = link_to _('New file'), fork_path, method: :post
#{ _('New file') }
...@@ -64,6 +64,12 @@ ...@@ -64,6 +64,12 @@
.commit-info.branches .commit-info.branches
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
.well-segment.merge-request-info
.icon-container
= custom_icon('mr_bold')
%span.commit-info.merge-requests{ 'data-project-commit-path' => merge_requests_project_commit_path(@project, @commit.id, format: :json) }
= icon('spinner spin')
- if @commit.last_pipeline - if @commit.last_pipeline
- last_pipeline = @commit.last_pipeline - last_pipeline = @commit.last_pipeline
.well-segment.pipeline-info .well-segment.pipeline-info
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment