Commit 3a565d5d authored by Mike Greiling's avatar Mike Greiling

Merge branch 'master' into pawel/connect_to_prometheus_through_proxy-30480

* master: (119 commits)
  [ci skip] Fix example commands to refer to the correct versions
  Use axios instead of jquery ajax for setCiStatusFavicon
  refactor groups controller to match EE
  Fix broken test
  Introduce a new QA::Gitlab::Page::Component::Dropzone class
  Gitaly Server info for admin panel
  Add note about being in the `qa` directory for `bin/qa` to work
  Remove N+1 queries with /projects/:project_id/{access_requests,members} API endpoints
  Fix not all events being shown in group dashboard
  Add support for PreReceiveError in UserMergeBranch RPC
  Migrate Git::Repository#delete_refs to Gitaly
  Replace $.get in render math with axios
  Move mr widget related links into a vue file
  Make those files as close as EE to reduce conflicts
  Don't assume postgresql in two initializers
  Move Repository#can_be_merged? to Gitlab::Git::Repository
  Remove brakeman from .codeclimate.yml since it's now covered by the sast CI job
  Update Nokogiri to 1.8.2
  Ignore conflicts in db/schema.rb in Gitlab::EeCompatCheck
  Remove Rugged exception in cache rescue
  ...
parents e25f383d 560c93e6
---
engines:
brakeman:
enabled: true
bundler-audit:
enabled: true
duplication:
......
......@@ -321,6 +321,7 @@ setup-test-env:
expire_in: 7d
paths:
- tmp/tests
- config/secrets.yml
rspec-pg 0 27: *rspec-metadata-pg
rspec-pg 1 27: *rspec-metadata-pg
......
......@@ -2,6 +2,22 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.4.2 (2018-01-30)
### Fixed (6 changes)
- Fix copy/paste on iOS devices due to a bug in webkit. !15804
- Fix missing "allow users to request access" option in public project permissions. !16485
- Fix encoding issue when counting commit count. !16637
- Fixes destination already exists, and some particular service errors on Import/Export error. !16714
- Fix cache clear bug withg using : on Windows. !16740
- Use has_table_privilege for TRIGGER on PostgreSQL.
### Changed (1 change)
- Vendor Auto DevOps template with DAST security checks enabled. !16691
## 10.4.1 (2018-01-24)
### Fixed (4 changes)
......
......@@ -132,7 +132,7 @@ gem 'asciidoctor-plantuml', '0.0.7'
gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.1'
gem 'nokogiri', '~> 1.8.2'
# Diffs
gem 'diffy', '~> 3.1.0'
......@@ -325,7 +325,7 @@ group :development, :test do
gem 'spinach-rerun-reporter', '~> 0.0.2'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
gem 'rspec-parameterized'
gem 'rspec-parameterized', require: false
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
......@@ -406,7 +406,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.78.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.78.0)
gitaly-proto (0.83.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -304,7 +304,7 @@ GEM
mime-types (>= 1.16)
posix-spawn (~> 0.3)
gitlab-markup (1.6.3)
gitlab-styles (2.3.1)
gitlab-styles (2.3.2)
rubocop (~> 0.51)
rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19)
......@@ -340,7 +340,7 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
google-protobuf (3.4.1.1)
google-protobuf (3.5.1.1-universal-darwin)
googleapis-common-protos-types (1.0.1)
google-protobuf (~> 3.0)
googleauth (0.5.3)
......@@ -369,7 +369,7 @@ GEM
rake
grape_logging (1.7.0)
grape
grpc (1.8.3)
grpc (1.8.3-universal-darwin)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
googleauth (>= 0.5.1, < 0.7)
......@@ -513,7 +513,7 @@ GEM
net-ldap (0.16.0)
net-ssh (4.1.0)
netrc (0.11.0)
nokogiri (1.8.1)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
numerizer (0.1.1)
oauth (0.5.1)
......@@ -1056,7 +1056,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.78.0)
gitaly-proto (~> 0.83.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
......@@ -1100,7 +1100,7 @@ DEPENDENCIES
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 4.1.0)
nokogiri (~> 1.8.1)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.6.2)
oj (~> 2.17.4)
......
import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
const Api = {
groupsPath: '/api/:version/groups.json',
groupPath: '/api/:version/groups/:id.json',
groupPath: '/api/:version/groups/:id',
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
......@@ -23,42 +23,44 @@ const Api = {
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath)
.replace(':id', groupId);
return $.ajax({
url,
dataType: 'json',
})
.done(group => callback(group));
return axios.get(url)
.then(({ data }) => {
callback(data);
return data;
});
},
// Return groups list. Filtered by query
groups(query, options, callback) {
const url = Api.buildUrl(Api.groupsPath);
return $.ajax({
url,
data: Object.assign({
return axios.get(url, {
params: Object.assign({
search: query,
per_page: 20,
}, options),
dataType: 'json',
})
.done(groups => callback(groups));
.then(({ data }) => {
callback(data);
return data;
});
},
// Return namespaces list. Filtered by query
namespaces(query, callback) {
const url = Api.buildUrl(Api.namespacesPath);
return $.ajax({
url,
data: {
return axios.get(url, {
params: {
search: query,
per_page: 20,
},
dataType: 'json',
}).done(namespaces => callback(namespaces));
})
.then(({ data }) => callback(data));
},
// Return projects list. Filtered by query
projects(query, options, callback) {
projects(query, options, callback = _.noop) {
const url = Api.buildUrl(Api.projectsPath);
const defaults = {
search: query,
......@@ -70,12 +72,14 @@ const Api = {
defaults.membership = true;
}
return $.ajax({
url,
data: Object.assign(defaults, options),
dataType: 'json',
return axios.get(url, {
params: Object.assign(defaults, options),
})
.done(projects => callback(projects));
.then(({ data }) => {
callback(data);
return data;
});
},
// Return single project
......@@ -97,41 +101,34 @@ const Api = {
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
}
return $.ajax({
url,
type: 'POST',
data: { label: data },
dataType: 'json',
return axios.post(url, {
label: data,
})
.done(label => callback(label))
.fail(message => callback(message.responseJSON));
.then(res => callback(res.data))
.catch(e => callback(e.response.data));
},
// Return group projects list. Filtered by query
groupProjects(groupId, query, callback) {
const url = Api.buildUrl(Api.groupProjectsPath)
.replace(':id', groupId);
return $.ajax({
url,
data: {
return axios.get(url, {
params: {
search: query,
per_page: 20,
},
dataType: 'json',
})
.done(projects => callback(projects));
.then(({ data }) => callback(data));
},
commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitPath)
.replace(':id', encodeURIComponent(id));
return this.wrapAjaxCall({
url,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
dataType: 'json',
return axios.post(url, JSON.stringify(data), {
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
});
},
......@@ -140,40 +137,37 @@ const Api = {
.replace(':id', encodeURIComponent(id))
.replace(':branch', branch);
return this.wrapAjaxCall({
url,
type: 'GET',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
});
return axios.get(url);
},
// Return text for a specific license
licenseText(key, data, callback) {
const url = Api.buildUrl(Api.licensePath)
.replace(':key', key);
return $.ajax({
url,
data,
return axios.get(url, {
params: data,
})
.done(license => callback(license));
.then(res => callback(res.data));
},
gitignoreText(key, callback) {
const url = Api.buildUrl(Api.gitignorePath)
.replace(':key', key);
return $.get(url, gitignore => callback(gitignore));
return axios.get(url)
.then(({ data }) => callback(data));
},
gitlabCiYml(key, callback) {
const url = Api.buildUrl(Api.gitlabCiYmlPath)
.replace(':key', key);
return $.get(url, file => callback(file));
return axios.get(url)
.then(({ data }) => callback(data));
},
dockerfileYml(key, callback) {
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
$.get(url, callback);
return axios.get(url)
.then(({ data }) => callback(data));
},
issueTemplate(namespacePath, projectPath, key, type, callback) {
......@@ -182,23 +176,18 @@ const Api = {
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
$.ajax({
url,
dataType: 'json',
})
.done(file => callback(null, file))
.fail(callback);
return axios.get(url)
.then(({ data }) => callback(null, data))
.catch(callback);
},
users(query, options) {
const url = Api.buildUrl(this.usersPath);
return Api.wrapAjaxCall({
url,
data: Object.assign({
return axios.get(url, {
params: Object.assign({
search: query,
per_page: 20,
}, options),
dataType: 'json',
});
},
......@@ -209,21 +198,6 @@ const Api = {
}
return urlRoot + url.replace(':version', gon.api_version);
},
wrapAjaxCall(options) {
return new Promise((resolve, reject) => {
// jQuery 2 is not Promises/A+ compatible (missing catch)
$.ajax(options) // eslint-disable-line promise/catch-or-return
.then(data => resolve(data),
(jqXHR, textStatus, errorThrown) => {
const error = new Error(`${options.url}: ${errorThrown}`);
error.textStatus = textStatus;
if (jqXHR && jqXHR.responseJSON) error.responseJSON = jqXHR.responseJSON;
reject(error);
},
);
});
},
};
export default Api;
import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils';
export default class BlobViewer {
constructor() {
......@@ -127,25 +128,18 @@ export default class BlobViewer {
const viewer = viewerParam;
const url = viewer.getAttribute('data-url');
return new Promise((resolve, reject) => {
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
resolve(viewer);
return;
}
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
return Promise.resolve(viewer);
}
viewer.setAttribute('data-loading', 'true');
viewer.setAttribute('data-loading', 'true');
$.ajax({
url,
dataType: 'JSON',
})
.fail(reject)
.done((data) => {
return axios.get(url)
.then(({ data }) => {
viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true');
resolve(viewer);
return viewer;
});
});
}
}
/* global ace */
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import TemplateSelectorMediator from '../blob/file_template_mediator';
export default class EditBlob {
......@@ -56,12 +59,14 @@ export default class EditBlob {
if (paneId === '#preview') {
this.$toggleButton.hide();
return $.post(currentLink.data('preview-url'), {
axios.post(currentLink.data('preview-url'), {
content: this.editor.getValue(),
}, (response) => {
currentPane.empty().append(response);
return currentPane.renderGFM();
});
})
.then(({ data }) => {
currentPane.empty().append(data);
currentPane.renderGFM();
})
.catch(() => createFlash(__('An error occurred previewing the blob')));
}
this.$toggleButton.show();
......
......@@ -5,6 +5,7 @@
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager';
import axios from './lib/utils/axios_utils';
export default (function () {
const CommitsList = {};
......@@ -43,29 +44,30 @@ export default (function () {
CommitsList.filterResults = function () {
const form = $('.commits-search-form');
const search = CommitsList.searchField.val();
if (search === CommitsList.lastSearch) return;
if (search === CommitsList.lastSearch) return Promise.resolve();
const commitsUrl = form.attr('action') + '?' + form.serialize();
CommitsList.content.fadeTo('fast', 0.5);
return $.ajax({
type: 'GET',
url: form.attr('action'),
data: form.serialize(),
complete: function () {
return CommitsList.content.fadeTo('fast', 1.0);
},
success: function (data) {
const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, {
[obj.name]: obj.value,
}), {});
return axios.get(form.attr('action'), {
params,
})
.then(({ data }) => {
CommitsList.lastSearch = search;
CommitsList.content.html(data.html);
return history.replaceState({
page: commitsUrl,
CommitsList.content.fadeTo('fast', 1.0);
// Change url so if user reload a page - search results are saved
history.replaceState({
page: commitsUrl,
}, document.title, commitsUrl);
},
error: function () {
})
.catch(() => {
CommitsList.content.fadeTo('fast', 1.0);
CommitsList.lastSearch = null;
},
dataType: 'json',
});
});
};
// Prepare loaded data.
......
......@@ -12,6 +12,7 @@ export default class CreateItemDropdown {
this.fieldName = options.fieldName;
this.onSelect = options.onSelect || (() => {});
this.getDataOption = options.getData;
this.createNewItemFromValueOption = options.createNewItemFromValue;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
......@@ -30,15 +31,15 @@ export default class CreateItemDropdown {
filterable: true,
remote: false,
search: {
fields: ['title'],
fields: ['text'],
},
selectable: true,
toggleLabel(selected) {
return (selected && 'id' in selected) ? selected.title : this.defaultToggleLabel;
return (selected && 'id' in selected) ? _.escape(selected.title) : this.defaultToggleLabel;
},
fieldName: this.fieldName,
text(item) {
return _.escape(item.title);
return _.escape(item.text);
},
id(item) {
return _.escape(item.id);
......@@ -51,6 +52,11 @@ export default class CreateItemDropdown {
});
}
clearDropdown() {
this.$dropdownContainer.find('.dropdown-content').html('');
this.$dropdownContainer.find('.dropdown-input-field').val('');
}
bindEvents() {
this.$createButton.on('click', this.onClickCreateWildcard.bind(this));
}
......@@ -58,9 +64,13 @@ export default class CreateItemDropdown {
onClickCreateWildcard(e) {
e.preventDefault();
this.refreshData();
this.$dropdown.data('glDropdown').selectRowAtIndex();
}
refreshData() {
// Refresh the dropdown's data, which ends up calling `getData`
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex();
}
getData(term, callback) {
......@@ -79,20 +89,28 @@ export default class CreateItemDropdown {
});
}
toggleCreateNewButton(item) {
if (item) {
this.selectedItem = {
title: item,
id: item,
text: item,
};
createNewItemFromValue(newValue) {
if (this.createNewItemFromValueOption) {
return this.createNewItemFromValueOption(newValue);
}
return {
title: newValue,
id: newValue,
text: newValue,
};
}
toggleCreateNewButton(newValue) {
if (newValue) {
this.selectedItem = this.createNewItemFromValue(newValue);
this.$dropdownContainer
.find('.js-dropdown-create-new-item code')
.text(item);
.text(newValue);
}
this.toggleFooter(!item);
this.toggleFooter(!newValue);
}
toggleFooter(toggleState) {
......
......@@ -2,6 +2,7 @@
/* global fuzzaldrinPlus */
import _ from 'underscore';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import axios from './lib/utils/axios_utils';
import { visitUrl } from './lib/utils/url_utility';
import { isObject } from './lib/utils/type_utility';
......@@ -212,25 +213,17 @@ GitLabDropdownRemote = (function() {
};
GitLabDropdownRemote.prototype.fetchData = function() {
return $.ajax({
url: this.dataEndpoint,
dataType: this.options.dataType,
beforeSend: (function(_this) {
return function() {
if (_this.options.beforeSend) {
return _this.options.beforeSend();
}
};
})(this),
success: (function(_this) {
return function(data) {
if (_this.options.success) {
return _this.options.success(data);
}
};
})(this)
});
// Fetch the data through ajax if the data is a string
if (this.options.beforeSend) {
this.options.beforeSend();
}
// Fetch the data through ajax if the data is a string
return axios.get(this.dataEndpoint)
.then(({ data }) => {
if (this.options.success) {
return this.options.success(data);
}
});
};
return GitLabDropdownRemote;
......
import flash from '../flash';
import { __ } from '../locale';
import axios from '../lib/utils/axios_utils';
import ContributorsStatGraph from './stat_graph_contributors';
document.addEventListener('DOMContentLoaded', () => {
$.ajax({
type: 'GET',
url: document.querySelector('.js-graphs-show').dataset.projectGraphPath,
dataType: 'json',
success(data) {
const url = document.querySelector('.js-graphs-show').dataset.projectGraphPath;
axios.get(url)
.then(({ data }) => {
const graph = new ContributorsStatGraph();
graph.init(data);
......@@ -16,6 +18,6 @@ document.addEventListener('DOMContentLoaded', () => {
$('.stat-graph').fadeIn();
$('.loading-graph').hide();
},
});
})
.catch(() => flash(__('Error fetching contributors data.')));
});
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { __ } from './locale';
export default class GroupLabelSubscription {
constructor(container) {
const $container = $(container);
......@@ -13,14 +17,12 @@ export default class GroupLabelSubscription {
event.preventDefault();
const url = this.$unsubscribeButtons.attr('data-url');
$.ajax({
type: 'POST',
url,
}).done(() => {
this.toggleSubscriptionButtons();
this.$unsubscribeButtons.removeAttr('data-url');
});
axios.post(url)
.then(() => {
this.toggleSubscriptionButtons();
this.$unsubscribeButtons.removeAttr('data-url');
})
.catch(() => flash(__('There was an error when unsubscribing from this label.')));
}
subscribe(event) {
......@@ -31,12 +33,9 @@ export default class GroupLabelSubscription {
this.$unsubscribeButtons.attr('data-url', url);
$.ajax({
type: 'POST',
url,
}).done(() => {
this.toggleSubscriptionButtons();
});
axios.post(url)
.then(() => this.toggleSubscriptionButtons())
.catch(() => flash(__('There was an error when subscribing to this label.')));
}
toggleSubscriptionButtons() {
......
......@@ -71,7 +71,7 @@ export const setResizingStatus = ({ commit }, resizing) => {
export const checkCommitStatus = ({ state }) =>
service
.getBranchData(state.currentProjectId, state.currentBranchId)
.then((data) => {
.then(({ data }) => {
const { id } = data.commit;
const selectedBranch =
state.projects[state.currentProjectId].branches[state.currentBranchId];
......@@ -90,7 +90,7 @@ export const commitChanges = (
) =>
service
.commit(state.currentProjectId, payload)
.then((data) => {
.then(({ data }) => {
const { branch } = payload;
if (!data.short_id) {
flash(data.message, 'alert', document, null, false, true);
......@@ -147,8 +147,8 @@ export const commitChanges = (
})
.catch((err) => {
let errMsg = 'Error committing changes. Please try again.';
if (err.responseJSON && err.responseJSON.message) {
errMsg += ` (${stripHtml(err.responseJSON.message)})`;
if (err.response.data && err.response.data.message) {
errMsg += ` (${stripHtml(err.response.data.message)})`;
}
flash(errMsg, 'alert', document, null, false, true);
window.dispatchEvent(new Event('resize'));
......
......@@ -10,7 +10,7 @@ export const getBranchData = (
!state.projects[`${projectId}`].branches[branchId])
|| force) {
service.getBranchData(`${projectId}`, branchId)
.then((data) => {
.then(({ data }) => {
const { id } = data.commit;
commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data });
commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
......
import Flash from '../flash';
import axios from '../lib/utils/axios_utils';
import flash from '../flash';
export default class IntegrationSettingsForm {
constructor(formSelector) {
......@@ -95,29 +96,26 @@ export default class IntegrationSettingsForm {
*/
testSettings(formData) {
this.toggleSubmitBtnState(true);
$.ajax({
type: 'PUT',
url: this.testEndPoint,
data: formData,
})
.done((res) => {
if (res.error) {
new Flash(`${res.message} ${res.service_response}`, 'alert', document, {
title: 'Save anyway',
clickHandler: (e) => {
e.preventDefault();
this.$form.submit();
},
});
} else {
this.$form.submit();
}
})
.fail(() => {
new Flash('Something went wrong on our end.');
})
.always(() => {
this.toggleSubmitBtnState(false);
});
return axios.put(this.testEndPoint, formData)
.then(({ data }) => {
if (data.error) {
flash(`${data.message} ${data.service_response}`, 'alert', document, {
title: 'Save anyway',
clickHandler: (e) => {
e.preventDefault();
this.$form.submit();
},
});
} else {
this.$form.submit();
}
this.toggleSubmitBtnState(false);
})
.catch(() => {
flash('Something went wrong on our end.');
this.toggleSubmitBtnState(false);
});
}
}
/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import Flash from './flash';
export default {
......@@ -22,15 +23,9 @@ export default {
},
submit() {
const _this = this;
const xhr = $.ajax({
url: this.form.attr('action'),
method: this.form.attr('method'),
dataType: 'JSON',
data: this.getFormDataAsObject()
});
xhr.done(() => window.location.reload());
xhr.fail(() => this.onFormSubmitFailure());
axios[this.form.attr('method')](this.form.attr('action'), this.getFormDataAsObject())
.then(() => window.location.reload())
.catch(() => this.onFormSubmitFailure());
},
onFormSubmitFailure() {
......
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { __ } from './locale';
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
......@@ -20,23 +23,24 @@ export default class IssuableIndex {
}
static resetIncomingEmailToken() {
$('.incoming-email-token-reset').on('click', (e) => {
const $resetToken = $('.incoming-email-token-reset');
$resetToken.on('click', (e) => {
e.preventDefault();
$.ajax({
type: 'PUT',
url: $('.incoming-email-token-reset').attr('href'),
dataType: 'json',
success(response) {
$('#issuable_email').val(response.new_address).focus();
},
beforeSend() {
$('.incoming-email-token-reset').text('resetting...');
},
complete() {
$('.incoming-email-token-reset').text('reset it');
},
});
$resetToken.text('resetting...');
axios.put($resetToken.attr('href'))
.then(({ data }) => {
$('#issuable_email').val(data.new_address).focus();
$resetToken.text('reset it');
})
.catch(() => {
flash(__('There was an error when reseting email token.'));
$resetToken.text('reset it');
});
});
}
}
/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
import Sortable from 'vendor/Sortable';
import Flash from './flash';
import flash from './flash';
import axios from './lib/utils/axios_utils';
export default class LabelManager {
constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
......@@ -50,11 +51,12 @@ export default class LabelManager {
if (persistState == null) {
persistState = true;
}
let xhr;
const _this = this;
const url = $label.find('.js-toggle-priority').data('url');
let $target = this.prioritizedLabels;
let $from = this.otherLabels;
const rollbackLabelPosition = this.rollbackLabelPosition.bind(this, $label, action);
if (action === 'remove') {
$target = this.otherLabels;
$from = this.prioritizedLabels;
......@@ -71,40 +73,34 @@ export default class LabelManager {
return;
}
if (action === 'remove') {
xhr = $.ajax({
url,
type: 'DELETE'
});
axios.delete(url)
.catch(rollbackLabelPosition);
// Restore empty message
if (!$from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
} else {
xhr = this.savePrioritySort($label, action);
this.savePrioritySort($label, action)
.catch(rollbackLabelPosition);
}
return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action));
}
onPrioritySortUpdate() {
const xhr = this.savePrioritySort();
return xhr.fail(function() {
return new Flash(this.errorMessage, 'alert');
});
this.savePrioritySort()
.catch(() => flash(this.errorMessage));
}
savePrioritySort() {
return $.post({
url: this.prioritizedLabels.data('url'),
data: {
label_ids: this.getSortedLabelsIds()
}
return axios.post(this.prioritizedLabels.data('url'), {
label_ids: this.getSortedLabelsIds(),
});
}
rollbackLabelPosition($label, originalAction) {
const action = originalAction === 'remove' ? 'add' : 'remove';
this.toggleLabelPriority($label, action, false);
return new Flash(this.errorMessage, 'alert');
flash(this.errorMessage);
}
getSortedLabelsIds() {
......
import { getLocationHash } from './url_utility';
import axios from './axios_utils';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
......@@ -382,22 +383,16 @@ export const resetFavicon = () => {
}
};
export const setCiStatusFavicon = (pageUrl) => {
$.ajax({
url: pageUrl,
dataType: 'json',
success: (data) => {
export const setCiStatusFavicon = pageUrl =>
axios.get(pageUrl)
.then(({ data }) => {
if (data && data.favicon) {
setFavicon(data.favicon);
} else {
resetFavicon();
}
},
error: () => {
resetFavicon();
},
});
};
})
.catch(resetFavicon);
export const spriteIcon = (icon, className = '') => {
const classAttribute = className.length > 0 ? `class="${className}"` : '';
......
......@@ -8,16 +8,16 @@ class UsersCache extends Cache {
}
return Api.users('', { username })
.then((users) => {
if (!users.length) {
.then(({ data }) => {
if (!data.length) {
throw new Error(`User "${username}" could not be found!`);
}
if (users.length > 1) {
if (data.length > 1) {
throw new Error(`Expected username "${username}" to be unique!`);
}
const user = users[0];
const user = data[0];
this.internalStorage[username] = user;
return user;
});
......
......@@ -76,7 +76,13 @@
.then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')),
])
.then(() => { this.showEmptyState = false; })
.then(() => {
if (this.store.groups.length < 1) {
this.state = 'noData';
return;
}
this.showEmptyState = false;
})
.catch(() => { this.state = 'unableToConnect'; });
},
......
......@@ -34,16 +34,23 @@
svgUrl: this.emptyGettingStartedSvgPath,
title: 'Get started with performance monitoring',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`,
of your environment by configuring Prometheus to monitor your deployments.`,
buttonText: 'Configure Prometheus',
},
loading: {
svgUrl: this.emptyLoadingSvgPath,
title: 'Waiting for performance data',
description: `Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`,
If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation',
},
noData: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: 'No data found',
description: `You are connected to the Prometheus server, but there is currently
no data to display.`,
buttonText: 'Configure Prometheus',
},
unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: 'Unable to connect to Prometheus server',
......
......@@ -7,7 +7,12 @@
//
// <code class="js-render-math"></div>
//
// Only load once
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
// Only load once
let katexLoaded = false;
// Loop over all math elements and render math
......@@ -33,19 +38,26 @@ export default function renderMath($els) {
if (katexLoaded) {
renderWithKaTeX($els);
} else {
$.get(gon.katex_css_url, () => {
const css = $('<link>', {
rel: 'stylesheet',
type: 'text/css',
href: gon.katex_css_url,
});
css.appendTo('head');
// Load KaTeX js
$.getScript(gon.katex_js_url, () => {
axios.get(gon.katex_css_url)
.then(() => {
const css = $('<link>', {
rel: 'stylesheet',
type: 'text/css',
href: gon.katex_css_url,
});
css.appendTo('head');
})
.then(() => axios.get(gon.katex_js_url, {
responseType: 'text',
}))
.then(({ data }) => {
// Add katex js to our document
$.globalEval(data);
})
.then(() => {
katexLoaded = true;
renderWithKaTeX($els); // Run KaTeX
});
});
})
.catch(() => flash(__('An error occurred while rendering KaTeX')));
}
}
/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import createFlash from './flash';
import FilesCommentButton from './files_comment_button';
import imageDiffHelper from './image_diff/helpers/index';
import syntaxHighlight from './syntax_highlight';
......@@ -60,30 +63,31 @@ export default class SingleFileDiff {
getContentHTML(cb) {
this.collapsedContent.hide();
this.loadingContent.show();
$.get(this.diffForPath, (function(_this) {
return function(data) {
_this.loadingContent.hide();
axios.get(this.diffForPath)
.then(({ data }) => {
this.loadingContent.hide();
if (data.html) {
_this.content = $(data.html);
syntaxHighlight(_this.content);
this.content = $(data.html);
syntaxHighlight(this.content);
} else {
_this.hasError = true;
_this.content = $(ERROR_HTML);
this.hasError = true;
this.content = $(ERROR_HTML);
}
_this.collapsedContent.after(_this.content);
this.collapsedContent.after(this.content);
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
const $file = $(_this.file);
const $file = $(this.file);
FilesCommentButton.init($file);
const canCreateNote = $file.closest('.files').is('[data-can-create-note]');
imageDiffHelper.initImageDiff($file[0], canCreateNote);
if (cb) cb();
};
})(this));
})
.catch(createFlash(__('An error occurred while retrieving diff')));
}
}
......@@ -98,7 +98,7 @@ export default class ActivityCalendar {
const secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
if (lastColMonth !== secondLastColMonth) {
extraWidthPadding = 3;
extraWidthPadding = 6;
}
return extraWidthPadding;
......
export default {
name: 'MRWidgetRelatedLinks',
props: {
relatedLinks: { type: Object, required: true },
state: { type: String, required: false },
},
computed: {
hasLinks() {
const { closing, mentioned, assignToMe } = this.relatedLinks;
return closing || mentioned || assignToMe;
},
closesText() {
if (this.state === 'merged') {
return 'Closed';
}
if (this.state === 'closed') {
return 'Did not close';
}
return 'Closes';
},
},
template: `
<section
v-if="hasLinks"
class="mr-info-list mr-links">
<p v-if="relatedLinks.closing">
{{closesText}} <span v-html="relatedLinks.closing"></span>
</p>
<p v-if="relatedLinks.mentioned">
Mentions <span v-html="relatedLinks.mentioned"></span>
</p>
<p v-if="relatedLinks.assignToMe">
<span v-html="relatedLinks.assignToMe"></span>
</p>
</section>
`,
};
<script>
import { s__ } from '~/locale';
export default {
name: 'MRWidgetRelatedLinks',
props: {
relatedLinks: {
type: Object,
required: true,
default: () => ({}),
},
state: {
type: String,
required: false,
default: '',
},
},
computed: {
closesText() {
if (this.state === 'merged') {
return s__('mrWidget|Closed');
}
if (this.state === 'closed') {
return s__('mrWidget|Did not close');
}
return s__('mrWidget|Closes');
},
},
};
</script>
<template>
<section class="mr-info-list mr-links">
<p v-if="relatedLinks.closing">
{{ closesText }} <span v-html="relatedLinks.closing"></span>
</p>
<p v-if="relatedLinks.mentioned">
{{ s__("mrWidget|Mentions") }} <span v-html="relatedLinks.mentioned"></span>
</p>
<p v-if="relatedLinks.assignToMe">
<span v-html="relatedLinks.assignToMe"></span>
</p>
</section>
</template>
......@@ -15,7 +15,7 @@ export { default as WidgetHeader } from './components/mr_widget_header';
export { default as WidgetMergeHelp } from './components/mr_widget_merge_help';
export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
export { default as WidgetDeployment } from './components/mr_widget_deployment';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue';
export { default as MergedState } from './components/states/mr_widget_merged.vue';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
export { default as ClosedState } from './components/states/mr_widget_closed.vue';
......
......@@ -257,7 +257,8 @@ export default {
<mr-widget-related-links
v-if="shouldRenderRelatedLinks"
:state="mr.state"
:related-links="mr.relatedLinks" />
:related-links="mr.relatedLinks"
/>
</div>
<div
class="mr-widget-footer"
......
class Admin::GitalyServersController < Admin::ApplicationController
def index
@gitaly_servers = Gitaly::Server.all
end
end
......@@ -12,11 +12,9 @@ module IssuableCollections
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_issuables_index
@issuables = issuables_collection
@issuables = @issuables.page(params[:page])
@issuable_meta_data = issuable_meta_data(@issuables, collection_type)
@total_pages = issuable_page_count
@issuables = issuables_collection
set_pagination
return if redirect_out_of_range(@total_pages)
if params[:label_name].present?
......@@ -35,14 +33,26 @@ module IssuableCollections
@users.push(author) if author
end
end
def set_pagination
return if pagination_disabled?
@issuables = @issuables.page(params[:page])
@issuable_meta_data = issuable_meta_data(@issuables, collection_type)
@total_pages = issuable_page_count
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def pagination_disabled?
false
end
def issuables_collection
finder.execute.preload(preload_for_collection)
end
def redirect_out_of_range(total_pages)
return false if total_pages.zero?
return false if total_pages.nil? || total_pages.zero?
out_of_range = @issuables.current_page > total_pages # rubocop:disable Gitlab/ModuleWithInstanceVariables
......
......@@ -118,10 +118,10 @@ class GroupsController < Groups::ApplicationController
end
def group_params
params.require(:group).permit(group_params_ce)
params.require(:group).permit(group_params_attributes)
end
def group_params_ce
def group_params_attributes
[
:avatar,
:description,
......@@ -150,7 +150,6 @@ class GroupsController < Groups::ApplicationController
@projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user)
.execute
.includes(:namespace)
.page(params[:page])
@events = EventCollection
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
......
......@@ -37,6 +37,8 @@ module DiscussionOnDiff
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true)
return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote)
lines = highlight ? highlighted_diff_lines : diff_lines
initial_line_index = [diff_line.index - NUMBER_OF_TRUNCATED_DIFF_LINES + 1, 0].max
......
......@@ -618,12 +618,12 @@ class MergeRequest < ActiveRecord::Base
can_be_merged? && !should_be_rebased?
end
def mergeable_state?(skip_ci_check: false)
def mergeable_state?(skip_ci_check: false, skip_discussions_check: false)
return false unless open?
return false if work_in_progress?
return false if broken?
return false unless skip_ci_check || mergeable_ci_state?
return false unless mergeable_discussions_state?
return false unless skip_discussions_check || mergeable_discussions_state?
true
end
......
......@@ -43,7 +43,7 @@ class JiraService < IssueTrackerService
username: self.username,
password: self.password,
site: URI.join(url, '/').to_s,
context_path: url.path,
context_path: url.path.chomp('/'),
auth_type: :basic,
read_timeout: 120,
use_cookies: true,
......
......@@ -124,6 +124,12 @@ class ProjectWiki
update_project_activity
end
def page_formatted_data(page)
page_title, page_dir = page_title_and_dir(page.title)
wiki.page_formatted_data(title: page_title, dir: page_dir, version: page.version)
end
def page_title_and_dir(title)
title_array = title.split("/")
title = title_array.pop
......
......@@ -255,6 +255,8 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false)
rescue Gitlab::Git::CommandError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
end
def kept_around?(sha)
......@@ -491,7 +493,7 @@ class Repository
raw_repository.root_ref
else
# When the repo does not exist we raise this error so no data is cached.
raise Rugged::ReferenceError
raise Gitlab::Git::Repository::NoRepository
end
end
cache_method :root_ref
......@@ -525,11 +527,7 @@ class Repository
def commit_count_for_ref(ref)
return 0 unless exists?
begin
cache.fetch(:"commit_count_#{ref}") { raw_repository.commit_count(ref) }
rescue Rugged::ReferenceError
0
end
cache.fetch(:"commit_count_#{ref}") { raw_repository.commit_count(ref) }
end
delegate :branch_names, to: :raw_repository
......@@ -653,26 +651,14 @@ class Repository
end
def last_commit_for_path(sha, path)
raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
if is_enabled
last_commit_for_path_by_gitaly(sha, path)
else
last_commit_for_path_by_rugged(sha, path)
end
end
commit_by(oid: last_commit_id_for_path(sha, path))
end
def last_commit_id_for_path(sha, path)
key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
cache.fetch(key) do
raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
if is_enabled
last_commit_for_path_by_gitaly(sha, path).id
else
last_commit_id_for_path_by_shelling_out(sha, path)
end
end
raw_repository.last_commit_id_for_path(sha, path)
end
end
......@@ -800,16 +786,6 @@ class Repository
with_cache_hooks { raw.multi_action(user, **options) }
end
def can_be_merged?(source_sha, target_branch)
raw_repository.gitaly_migrate(:can_be_merged) do |is_enabled|
if is_enabled
gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
else
rugged_can_be_merged?(source_sha, target_branch)
end
end
end
def merge(user, source_sha, merge_request, message)
with_cache_hooks do
raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
......@@ -876,26 +852,18 @@ class Repository
@root_ref_sha ||= commit(root_ref).sha
end
delegate :merged_branch_names, to: :raw_repository
delegate :merged_branch_names, :can_be_merged?, to: :raw_repository
def merge_base(first_commit_id, second_commit_id)
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
raw_repository.merge_base(first_commit_id, second_commit_id)
rescue Rugged::ReferenceError
nil
end
def ancestor?(ancestor_id, descendant_id)
return false if ancestor_id.nil? || descendant_id.nil?
Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
if is_enabled
raw_repository.ancestor?(ancestor_id, descendant_id)
else
rugged_is_ancestor?(ancestor_id, descendant_id)
end
end
raw_repository.ancestor?(ancestor_id, descendant_id)
end
def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil)
......@@ -983,7 +951,7 @@ class Repository
end
instance_variable_set(ivar, value)
rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
rescue Gitlab::Git::Repository::NoRepository
# Even if the above `#exists?` check passes these errors might still
# occur (for example because of a non-existing HEAD). We want to
# gracefully handle this and not cache anything
......@@ -1077,30 +1045,7 @@ class Repository
Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags))
end
def last_commit_for_path_by_gitaly(sha, path)
c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
commit_by(oid: c)
end
def last_commit_for_path_by_rugged(sha, path)
sha = last_commit_id_for_path_by_shelling_out(sha, path)
commit_by(oid: sha)
end
def last_commit_id_for_path_by_shelling_out(sha, path)
args = %W(rev-list --max-count=1 #{sha} -- #{path})
raw_repository.run_git_with_timeout(args, Gitlab::Git::Popen::FAST_GIT_PROCESS_TIMEOUT).first.strip
end
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki))
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
end
......@@ -107,7 +107,10 @@ class WikiPage
# The processed/formatted content of this page.
def formatted_content
@attributes[:formatted_content] ||= @page&.formatted_data
# Assuming @page exists, nil formatted_data means we didn't load it
# before hand (i.e. page was fetched by Gitaly), so we fetch it separately.
# If the page was fetched by Gollum, formatted_data would've been a String.
@attributes[:formatted_content] ||= @page&.formatted_data || @wiki.page_formatted_data(@page)
end
# The markup format for the page.
......
......@@ -48,7 +48,18 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_ongoing?, as: :merge_ongoing
expose :work_in_progress?, as: :work_in_progress
expose :source_branch_exists?, as: :source_branch_exists
expose :mergeable_discussions_state?, as: :mergeable_discussions_state
expose :mergeable_discussions_state?, as: :mergeable_discussions_state do |merge_request|
# This avoids calling MergeRequest#mergeable_discussions_state without
# considering the state of the MR first. If a MR isn't mergeable, we can
# safely short-circuit it.
if merge_request.mergeable_state?(skip_ci_check: true, skip_discussions_check: true)
merge_request.mergeable_discussions_state?
else
false
end
end
expose :branch_missing?, as: :branch_missing
expose :commits_count
expose :cannot_be_merged?, as: :has_conflicts
......
......@@ -138,19 +138,11 @@
GitLab API
%span.pull-right
= API::API::version
%p
Gitaly
%span.pull-right
= Gitlab::GitalyClient.expected_server_version
- if Gitlab.config.pages.enabled
%p
GitLab Pages
%span.pull-right
= Gitlab::Pages::VERSION
%p
Git
%span.pull-right
= Gitlab::Git.version
%p
Ruby
%span.pull-right
......@@ -163,6 +155,8 @@
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
%p
= link_to "Gitaly Servers", admin_gitaly_servers_path
.row
.col-md-4
.info-well
......
- breadcrumb_title _("Gitaly Servers")
%h3.page-title= _("Gitaly Servers")
%hr
.gitaly_servers
- if @gitaly_servers.any?
.table-holder
%table.table.responsive-table
%thead.hidden-sm.hidden-xs
%tr
%th= _("Storage")
%th= n_("Gitaly|Address")
%th= _("Server version")
%th= _("Git version")
%th= _("Up to date")
- @gitaly_servers.each do |server|
%tr
%td
= server.storage
%td
= server.address
%td
= server.server_version
%td
= server.git_binary_version
%td
= boolean_to_icon(server.up_to_date?)
- else
.empty-state
.text-center
%h4= _("No connection could be made to a Gitaly Server, please check your logs!")
%li.header-new.dropdown
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do
= sprite_icon('plus-square', size: 16)
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
......
......@@ -32,5 +32,5 @@
= icon("pencil")
- if can?(current_user, :admin_project, @project)
= link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do
= link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip prepend-left-10 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do
= icon("trash-o")
require_relative "../lib/gitlab/upgrader"
Gitlab::Upgrader.new.execute
---
title: Login via OAuth now only marks new users as external
merge_request: 16672
author:
type: fixed
---
title: Fix copy/paste on iOS devices due to a bug in webkit
merge_request: 15804
author:
type: fixed
---
title: Reduce the number of Prometheus metrics
merge_request: 16443
author:
type: performance
---
title: Adds spacing between edit and delete tag btn in tag list
merge_request: 16757
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Fix missing "allow users to request access" option in public project permissions
merge_request: 16485
author:
type: fixed
---
title: Fix 500 error when loading a merge request with an invalid comment
merge_request: 16795
author:
type: fixed
---
title: Fix encoding issue when counting commit count
merge_request: 16637
author:
type: fixed
---
title: Fixes destination already exists, and some particular service errors on Import/Export
error
merge_request: 16714
author:
type: fixed
---
title: Update nokogiri to 1.8.2
merge_request: 16807
author:
type: security
---
title: Contribution calendar label was cut off
merge_request:
author: Branka Martinovic
type: fixed
---
title: 'Fix cache clear bug withg using : on Windows'
merge_request: 16740
author:
type: fixed
---
title: Update minimum git version to 2.9.5
merge_request: 16683
author:
type: other
---
title: Stop checking if discussions are in a mergeable state if the MR isn't
merge_request:
author:
type: performance
---
title: Return more consistent values for merge_status on MR APIs
merge_request:
author:
type: fixed
---
title: Use has_table_privilege for TRIGGER on PostgreSQL
title: Fix not all events being shown in group dashboard
merge_request:
author:
type: fixed
---
title: Fix JIRA not working when a trailing slash is included
merge_request:
author:
type: fixed
---
title: Remove N+1 queries with /projects/:project_id/{access_requests,members} API
endpoints
merge_request:
author:
type: performance
---
title: Add Gitaly Servers admin dashboard
merge_request:
author:
type: added
......@@ -6,6 +6,7 @@ Bundler.require(:default, Rails.env)
module Gitlab
class Application < Rails::Application
require_dependency Rails.root.join('lib/gitlab/redis/wrapper')
require_dependency Rails.root.join('lib/gitlab/redis/cache')
require_dependency Rails.root.join('lib/gitlab/redis/queues')
require_dependency Rails.root.join('lib/gitlab/redis/shared_state')
......
raise "Vendored ActiveRecord 5 code! Delete #{__FILE__}!" if ActiveRecord::VERSION::MAJOR >= 5
require 'active_record/connection_adapters/postgresql_adapter'
require 'active_record/connection_adapters/postgresql/schema_statements'
#
# Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330
#
# Updates sequence logic to support PostgreSQL 10.
#
# rubocop:disable all
module ActiveRecord
module ConnectionAdapters
# We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu
# to work. In ActiveRecord 4, it is protected.
# https://github.com/mbleigh/seed-fu/issues/123
class PostgreSQLAdapter
public :postgresql_version
end
if Gitlab::Database.postgresql?
require 'active_record/connection_adapters/postgresql_adapter'
require 'active_record/connection_adapters/postgresql/schema_statements'
#
# Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330
#
# Updates sequence logic to support PostgreSQL 10.
#
# rubocop:disable all
module ActiveRecord
module ConnectionAdapters
# We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu
# to work. In ActiveRecord 4, it is protected.
# https://github.com/mbleigh/seed-fu/issues/123
class PostgreSQLAdapter
public :postgresql_version
end
module PostgreSQL
module SchemaStatements
# Resets the sequence of a table's primary key to the maximum value.
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
unless pk and sequence
default_pk, default_sequence = pk_and_sequence_for(table)
module PostgreSQL
module SchemaStatements
# Resets the sequence of a table's primary key to the maximum value.
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
unless pk and sequence
default_pk, default_sequence = pk_and_sequence_for(table)
pk ||= default_pk
sequence ||= default_sequence
end
pk ||= default_pk
sequence ||= default_sequence
end
if @logger && pk && !sequence
@logger.warn "#{table} has primary key #{pk} with no default sequence"
end
if @logger && pk && !sequence
@logger.warn "#{table} has primary key #{pk} with no default sequence"
end
if pk && sequence
quoted_sequence = quote_table_name(sequence)
max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}")
if max_pk.nil?
if postgresql_version >= 100000
minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass")
else
minvalue = select_value("SELECT min_value FROM #{quoted_sequence}")
if pk && sequence
quoted_sequence = quote_table_name(sequence)
max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}")
if max_pk.nil?
if postgresql_version >= 100000
minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass")
else
minvalue = select_value("SELECT min_value FROM #{quoted_sequence}")
end
end
end
select_value <<-end_sql, 'SCHEMA'
SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})
end_sql
select_value <<-end_sql, 'SCHEMA'
SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})
end_sql
end
end
end
end
end
end
# rubocop:enable all
end
# rubocop:enable all
if defined?(GrapeRouteHelpers)
module GrapeRouteHelpers
module AllRoutes
# Bringing in PR https://github.com/reprah/grape-route-helpers/pull/21 due to abandonment.
#
# Without the following fix, when two helper methods are the same, but have different arguments
# (for example: api_v1_cats_owners_path(id: 1) vs api_v1_cats_owners_path(id: 1, owner_id: 2))
# if the helper method with the least number of arguments is defined first (because the route was defined first)
# then it will shadow the longer route.
#
# The fix is to sort descending by amount of arguments
def decorated_routes
@decorated_routes ||= all_routes
.map { |r| DecoratedRoute.new(r) }
.sort_by { |r| -r.dynamic_path_segments.count }
end
end
class DecoratedRoute
# GrapeRouteHelpers gem tries to parse the versions
# from a string, not supporting Grape `version` array definition.
......
......@@ -7,10 +7,12 @@ if Gitlab::Database.mysql?
require 'peek-mysql2'
PEEK_DB_CLIENT = ::Mysql2::Client
PEEK_DB_VIEW = Peek::Views::Mysql2
else
elsif Gitlab::Database.postgresql?
require 'peek-pg'
PEEK_DB_CLIENT = ::PG::Connection
PEEK_DB_VIEW = Peek::Views::PG
else
raise "Unsupported database adapter for peek!"
end
Peek.into PEEK_DB_VIEW
......
......@@ -24,6 +24,8 @@ namespace :admin do
resource :impersonation, only: :destroy
resources :abuse_reports, only: [:index, :destroy]
resources :gitaly_servers, only: [:index]
resources :spam_logs, only: [:index, :destroy] do
member do
post :mark_as_ham
......
......@@ -114,6 +114,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `sha` (optional) - The commit SHA to download. A tag, branch reference or sha can be used. This defaults to the tip of the default branch if not specified
- `format` (optional) - The archive format. Default is `tar.gz`. Options are `tar.gz`, `tar.bz2`, `tbz`, `tbz2`, `tb2`, `bz2`, `tar`, `zip`
## Compare branches, tags or commits
......
......@@ -68,7 +68,7 @@ Example response:
```json
{
"file_name": "app/project.rb",
"file_path": "app/project.rb",
"branch": "master"
}
```
......@@ -98,7 +98,7 @@ Example response:
```json
{
"file_name": "app/project.rb",
"file_path": "app/project.rb",
"branch": "master"
}
```
......@@ -134,15 +134,6 @@ DELETE /projects/:id/repository/files/:file_path
curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
```
Example response:
```json
{
"file_name": "app/project.rb",
"branch": "master"
}
```
Parameters:
- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
......
......@@ -80,7 +80,7 @@ Make sure you have the right version of Git installed
# Install Git
sudo apt-get install -y git-core
# Make sure Git is version 2.14.3 or higher
# Make sure Git is version 2.9.5 or higher
git --version
Is the system packaged Git too old? Remove it and compile from source.
......@@ -93,9 +93,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.8.4.tar.gz
echo '626e319f8a24fc0866167ea5f6bf3e2f38f69d6cb2e59e150f13709ca3ebf301 git-2.8.4.tar.gz' | shasum -a256 -c - && tar -xzf git-2.8.4.tar.gz
cd git-2.8.4/
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.14.3.tar.gz
echo '023ffff6d3ba8a1bea779dfecc0ed0bb4ad68ab8601d14435dd8c08416f78d7f git-2.14.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.14.3.tar.gz
cd git-2.14.3/
./configure
make prefix=/usr/local all
......
......@@ -18,7 +18,7 @@ When you create a new [project](../../index.md), GitLab sets `master` as the def
branch for your project. You can choose another branch to be your project's
default under your project's **Settings > General**.
The default branch is the branched affected by the
The default branch is the branch affected by the
[issue closing pattern](../../issues/automatic_issue_closing.md),
which means that _an issue will be closed when a merge request is merged to
the **default branch**_.
......
......@@ -24,7 +24,7 @@ module API
access_requesters = AccessRequestsFinder.new(source).execute!(current_user)
access_requesters = paginate(access_requesters.includes(:user))
present access_requesters.map(&:user), with: Entities::AccessRequester, source: source
present access_requesters, with: Entities::AccessRequester
end
desc "Requests access for the authenticated user to a #{source_type}." do
......@@ -36,7 +36,7 @@ module API
access_requester = source.request_access(current_user)
if access_requester.persisted?
present access_requester.user, with: Entities::AccessRequester, access_requester: access_requester
present access_requester, with: Entities::AccessRequester
else
render_validation_error!(access_requester)
end
......@@ -56,7 +56,7 @@ module API
member = ::Members::ApproveAccessRequestService.new(source, current_user, declared_params).execute
status :created
present member.user, with: Entities::Member, member: member
present member, with: Entities::Member
end
desc 'Denies an access request for the given user.' do
......
......@@ -205,22 +205,15 @@ module API
expose :build_artifacts_size, as: :job_artifacts_size
end
class Member < UserBasic
expose :access_level do |user, options|
member = options[:member] || options[:source].members.find_by(user_id: user.id)
member.access_level
end
expose :expires_at do |user, options|
member = options[:member] || options[:source].members.find_by(user_id: user.id)
member.expires_at
end
class Member < Grape::Entity
expose :user, merge: true, using: UserBasic
expose :access_level
expose :expires_at
end
class AccessRequester < UserBasic
expose :requested_at do |user, options|
access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id)
access_requester.requested_at
end
class AccessRequester < Grape::Entity
expose :user, merge: true, using: UserBasic
expose :requested_at
end
class Group < Grape::Entity
......@@ -507,7 +500,15 @@ module API
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_pipeline_succeeds
expose :merge_status
# Ideally we should deprecate `MergeRequest#merge_status` exposure and
# use `MergeRequest#mergeable?` instead (boolean).
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more
# information.
expose :merge_status do |merge_request|
merge_request.check_if_can_be_merged
merge_request.merge_status
end
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
expose :user_notes_count
......
......@@ -21,10 +21,11 @@ module API
get ":id/members" do
source = find_source(source_type, params[:id])
users = source.users
users = users.merge(User.search(params[:query])) if params[:query].present?
members = source.members.where.not(user_id: nil).includes(:user)
members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present?
members = paginate(members)
present paginate(users), with: Entities::Member, source: source
present members, with: Entities::Member
end
desc 'Gets a member of a group or project.' do
......@@ -39,7 +40,7 @@ module API
members = source.members
member = members.find_by!(user_id: params[:user_id])
present member.user, with: Entities::Member, member: member
present member, with: Entities::Member
end
desc 'Adds a member to a group or project.' do
......@@ -62,7 +63,7 @@ module API
if !member
not_allowed! # This currently can only be reached in EE
elsif member.persisted? && member.valid?
present member.user, with: Entities::Member, member: member
present member, with: Entities::Member
else
render_validation_error!(member)
end
......@@ -83,7 +84,7 @@ module API
member = source.members.find_by!(user_id: params.delete(:user_id))
if member.update_attributes(declared_params(include_missing: false))
present member.user, with: Entities::Member, member: member
present member, with: Entities::Member
else
render_validation_error!(member)
end
......
......@@ -22,10 +22,11 @@ module API
get ":id/members" do
source = find_source(source_type, params[:id])
users = source.users
users = users.merge(User.search(params[:query])) if params[:query].present?
members = source.members.where.not(user_id: nil).includes(:user)
members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present?
members = paginate(members)
present paginate(users), with: ::API::Entities::Member, source: source
present members, with: ::API::Entities::Member
end
desc 'Gets a member of a group or project.' do
......@@ -40,7 +41,7 @@ module API
members = source.members
member = members.find_by!(user_id: params[:user_id])
present member.user, with: ::API::Entities::Member, member: member
present member, with: ::API::Entities::Member
end
desc 'Adds a member to a group or project.' do
......@@ -69,7 +70,7 @@ module API
end
if member.persisted? && member.valid?
present member.user, with: ::API::Entities::Member, member: member
present member, with: ::API::Entities::Member
else
# This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0!
......@@ -93,7 +94,7 @@ module API
member = source.members.find_by!(user_id: params.delete(:user_id))
if member.update_attributes(declared_params(include_missing: false))
present member.user, with: ::API::Entities::Member, member: member
present member, with: ::API::Entities::Member
else
# This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0!
......@@ -125,7 +126,7 @@ module API
else
::Members::DestroyService.new(source, current_user, declared_params).execute
present member.user, with: ::API::Entities::Member, member: member
present member, with: ::API::Entities::Member
end
end
end
......
module Gitaly
class Server
def self.all
Gitlab.config.repositories.storages.keys.map { |s| Gitaly::Server.new(s) }
end
attr_reader :storage
def initialize(storage)
@storage = storage
end
def server_version
info.server_version
end
def git_binary_version
info.git_version
end
def up_to_date?
server_version == Gitlab::GitalyClient.expected_server_version
end
def address
Gitlab::GitalyClient.address(@storage)
rescue RuntimeError => e
"Error getting the address: #{e.message}"
end
private
def info
@info ||=
begin
Gitlab::GitalyClient::ServerService.new(@storage).info
rescue GRPC::Unavailable, GRPC::GRPC::DeadlineExceeded
# This will show the server as being out of date
Gitaly::ServerInfoResponse.new(git_version: '', server_version: '')
end
end
end
end
......@@ -5,7 +5,7 @@ module Gitlab
DEFAULT_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
CHECK_DIR = Rails.root.join('ee_compat_check')
IGNORED_FILES_REGEX = /(VERSION|CHANGELOG\.md:\d+)/.freeze
IGNORED_FILES_REGEX = %r{VERSION|CHANGELOG\.md|db/schema\.rb}i.freeze
PLEASE_READ_THIS_BANNER = %Q{
============================================================
===================== PLEASE READ THIS =====================
......
......@@ -44,7 +44,7 @@ module Gitlab
# branch1...branch2) From the git documentation:
# "git diff A...B" is equivalent to "git diff
# $(git-merge-base A B) B"
repo.merge_base_commit(head, base)
repo.merge_base(head, base)
end
options ||= {}
......
......@@ -131,7 +131,10 @@ module Gitlab
oldrev = branch.target
if oldrev == repository.merge_base(newrev, branch.target)
merge_base = repository.merge_base(newrev, branch.target)
raise Gitlab::Git::Repository::InvalidRef unless merge_base
if oldrev == merge_base
oldrev
else
raise Gitlab::Git::CommitError.new('Branch diverged')
......
......@@ -551,29 +551,34 @@ module Gitlab
end
# Returns the SHA of the most recent common ancestor of +from+ and +to+
def merge_base_commit(from, to)
def merge_base(from, to)
gitaly_migrate(:merge_base) do |is_enabled|
if is_enabled
gitaly_repository_client.find_merge_base(from, to)
else
rugged.merge_base(from, to)
rugged_merge_base(from, to)
end
end
end
alias_method :merge_base, :merge_base_commit
# Gitaly note: JV: check gitlab-ee before removing this method.
def rugged_is_ancestor?(ancestor_id, descendant_id)
return false if ancestor_id.nil? || descendant_id.nil?
merge_base_commit(ancestor_id, descendant_id) == ancestor_id
rugged_merge_base(ancestor_id, descendant_id) == ancestor_id
rescue Rugged::OdbError
false
end
# Returns true is +from+ is direct ancestor to +to+, otherwise false
def ancestor?(from, to)
gitaly_commit_client.ancestor?(from, to)
Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
if is_enabled
gitaly_commit_client.ancestor?(from, to)
else
rugged_is_ancestor?(from, to)
end
end
end
def merged_branch_names(branch_names = [])
......@@ -680,11 +685,7 @@ module Gitlab
if is_enabled
gitaly_commit_client.commit_count(ref)
else
walker = Rugged::Walker.new(rugged)
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
oid = rugged.rev_parse_oid(ref)
walker.push(oid)
walker.count
rugged_commit_count(ref)
end
end
end
......@@ -887,16 +888,12 @@ module Gitlab
end
def delete_refs(*ref_names)
instructions = ref_names.map do |ref|
"delete #{ref}\x00\x00"
end
message, status = run_git(%w[update-ref --stdin -z]) do |stdin|
stdin.write(instructions.join)
end
unless status.zero?
raise GitError.new("Could not delete refs #{ref_names}: #{message}")
gitaly_migrate(:delete_refs) do |is_enabled|
if is_enabled
gitaly_delete_refs(*ref_names)
else
git_delete_refs(*ref_names)
end
end
end
......@@ -1105,10 +1102,14 @@ module Gitlab
end
def write_ref(ref_path, ref, old_ref: nil, shell: true)
if shell
shell_write_ref(ref_path, ref, old_ref)
else
rugged_write_ref(ref_path, ref)
ref_path = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{ref_path}" unless ref_path.start_with?("refs/") || ref_path == "HEAD"
gitaly_migrate(:write_ref) do |is_enabled|
if is_enabled
gitaly_repository_client.write_ref(ref_path, ref, old_ref, shell)
else
local_write_ref(ref_path, ref, old_ref: old_ref, shell: shell)
end
end
end
......@@ -1130,13 +1131,6 @@ module Gitlab
target_ref
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def run_git_with_timeout(args, timeout, env: {})
circuit_breaker.perform do
popen_with_timeout([Gitlab.config.git.bin_path, *args], timeout, path, env)
end
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def commit(ref = 'HEAD')
Gitlab::Git::Commit.find(self, ref)
......@@ -1392,6 +1386,16 @@ module Gitlab
run_git(args).first.scrub.split(/^--$/)
end
def can_be_merged?(source_sha, target_branch)
gitaly_migrate(:can_be_merged) do |is_enabled|
if is_enabled
gitaly_can_be_merged?(source_sha, find_branch(target_branch, true).target)
else
rugged_can_be_merged?(source_sha, target_branch)
end
end
end
def search_files_by_name(query, ref)
safe_query = Regexp.escape(query.sub(/^\/*/, ""))
......@@ -1417,8 +1421,36 @@ module Gitlab
output
end
def can_be_merged?(source_sha, target_branch)
gitaly_migrate(:can_be_merged) do |is_enabled|
if is_enabled
gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
else
rugged_can_be_merged?(source_sha, target_branch)
end
end
end
def last_commit_id_for_path(sha, path)
gitaly_migrate(:last_commit_for_path) do |is_enabled|
if is_enabled
last_commit_for_path_by_gitaly(sha, path).id
else
last_commit_id_for_path_by_shelling_out(sha, path)
end
end
end
private
def local_write_ref(ref_path, ref, old_ref: nil, shell: true)
if shell
shell_write_ref(ref_path, ref, old_ref)
else
rugged_write_ref(ref_path, ref)
end
end
def shell_write_ref(ref_path, ref, old_ref)
raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
......@@ -1460,6 +1492,12 @@ module Gitlab
output
end
def run_git_with_timeout(args, timeout, env: {})
circuit_breaker.perform do
popen_with_timeout([Gitlab.config.git.bin_path, *args], timeout, path, env)
end
end
def fresh_worktree?(path)
File.exist?(path) && !clean_stuck_worktree(path)
end
......@@ -2160,7 +2198,7 @@ module Gitlab
source_sha
end
rescue Rugged::ReferenceError
rescue Rugged::ReferenceError, InvalidRef
raise ArgumentError, 'Invalid merge source'
end
......@@ -2172,6 +2210,24 @@ module Gitlab
remote_update(remote_name, url: url)
end
def git_delete_refs(*ref_names)
instructions = ref_names.map do |ref|
"delete #{ref}\x00\x00"
end
message, status = run_git(%w[update-ref --stdin -z]) do |stdin|
stdin.write(instructions.join)
end
unless status.zero?
raise GitError.new("Could not delete refs #{ref_names}: #{message}")
end
end
def gitaly_delete_refs(*ref_names)
gitaly_ref_client.delete_refs(refs: ref_names)
end
def rugged_remove_remote(remote_name)
# When a remote is deleted all its remote refs are deleted too, but in
# the case of mirrors we map its refs (that would usualy go under
......@@ -2234,6 +2290,14 @@ module Gitlab
run_git(['fetch', remote_name], env: env).last.zero?
end
def gitaly_can_be_merged?(their_commit, our_commit)
!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 gitlab_projects_error
raise CommandError, @gitlab_projects.output
end
......@@ -2257,6 +2321,39 @@ module Gitlab
.commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
.map { |c| commit(c) }
end
def gitaly_can_be_merged?(their_commit, our_commit)
!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 last_commit_for_path_by_gitaly(sha, path)
gitaly_commit_client.last_commit_for_path(sha, path)
end
def last_commit_id_for_path_by_shelling_out(sha, path)
args = %W(rev-list --max-count=1 #{sha} -- #{path})
run_git_with_timeout(args, Gitlab::Git::Popen::FAST_GIT_PROCESS_TIMEOUT).first.strip
end
def rugged_merge_base(from, to)
rugged.merge_base(from, to)
rescue Rugged::ReferenceError
nil
end
def rugged_commit_count(ref)
walker = Rugged::Walker.new(rugged)
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
oid = rugged.rev_parse_oid(ref)
walker.push(oid)
walker.count
rescue Rugged::ReferenceError
0
end
end
end
end
......@@ -83,6 +83,8 @@ module Gitlab
commit_id: sha
)
end
rescue Rugged::ReferenceError
[]
end
end
......
......@@ -117,6 +117,20 @@ module Gitlab
page.url_path
end
def page_formatted_data(title:, dir: nil, version: nil)
version = version&.id
@repository.gitaly_migrate(:wiki_page_formatted_data) do |is_enabled|
if is_enabled
gitaly_wiki_client.get_formatted_data(title: title, dir: dir, version: version)
else
# We don't use #page because if wiki_find_page feature is enabled, we would
# get a page without formatted_data.
gollum_find_page(title: title, dir: dir, version: version)&.formatted_data
end
end
end
private
# options:
......
......@@ -6,6 +6,7 @@ require 'grpc/health/v1/health_services_pb'
module Gitlab
module GitalyClient
include Gitlab::Metrics::Methods
module MigrationStatus
DISABLED = 1
OPT_IN = 2
......@@ -33,8 +34,6 @@ module Gitlab
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
MUTEX = Mutex.new
METRICS_MUTEX = Mutex.new
private_constant :MUTEX, :METRICS_MUTEX
class << self
attr_accessor :query_time
......@@ -42,28 +41,14 @@ module Gitlab
self.query_time = 0
def self.migrate_histogram
@migrate_histogram ||=
METRICS_MUTEX.synchronize do
# If a thread was blocked on the mutex, the value was set already
return @migrate_histogram if @migrate_histogram
Gitlab::Metrics.histogram(:gitaly_migrate_call_duration_seconds,
"Gitaly migration call execution timings",
gitaly_enabled: nil, feature: nil)
end
define_histogram :gitaly_migrate_call_duration_seconds do
docstring "Gitaly migration call execution timings"
base_labels gitaly_enabled: nil, feature: nil
end
def self.gitaly_call_histogram
@gitaly_call_histogram ||=
METRICS_MUTEX.synchronize do
# If a thread was blocked on the mutex, the value was set already
return @gitaly_call_histogram if @gitaly_call_histogram
Gitlab::Metrics.histogram(:gitaly_controller_action_duration_seconds,
"Gitaly endpoint histogram by controller and action combination",
Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil))
end
define_histogram :gitaly_controller_action_duration_seconds do
docstring "Gitaly endpoint histogram by controller and action combination"
base_labels Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil)
end
def self.stub(name, storage)
......@@ -145,7 +130,7 @@ module Gitlab
# Keep track, seperately, for the performance bar
self.query_time += duration
gitaly_call_histogram.observe(
gitaly_controller_action_duration_seconds.observe(
current_transaction_labels.merge(gitaly_service: service.to_s, rpc: rpc.to_s),
duration)
end
......@@ -247,7 +232,7 @@ module Gitlab
yield is_enabled
ensure
total_time = Gitlab::Metrics::System.monotonic_time - start
migrate_histogram.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time)
gitaly_migrate_call_duration_seconds.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time)
feature_stack.shift
Thread.current[:gitaly_feature_stack] = nil if feature_stack.empty?
end
......
......@@ -103,7 +103,13 @@ module Gitlab
request_enum.push(Gitaly::UserMergeBranchRequest.new(apply: true))
branch_update = response_enum.next.branch_update
second_response = response_enum.next
if second_response.pre_receive_error.present?
raise Gitlab::Git::HooksService::PreReceiveError, second_response.pre_receive_error
end
branch_update = second_response.branch_update
return if branch_update.nil?
raise Gitlab::Git::CommitError.new('failed to apply merge to branch') unless branch_update.commit_id.present?
......
......@@ -133,13 +133,16 @@ module Gitlab
GitalyClient.call(@repository.storage, :ref_service, :delete_branch, request)
end
def delete_refs(except_with_prefixes:)
def delete_refs(refs: [], except_with_prefixes: [])
request = Gitaly::DeleteRefsRequest.new(
repository: @gitaly_repo,
except_with_prefix: except_with_prefixes
refs: refs.map { |r| encode_binary(r) },
except_with_prefix: except_with_prefixes.map { |r| encode_binary(r) }
)
GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request)
response = GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request)
raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present?
end
private
......
......@@ -203,6 +203,22 @@ module Gitlab
timeout: GitalyClient.default_timeout
)
end
def write_ref(ref_path, ref, old_ref, shell)
request = Gitaly::WriteRefRequest.new(
repository: @gitaly_repo,
ref: ref_path.b,
revision: ref.b,
shell: shell
)
request.old_revision = old_ref.b unless old_ref.nil?
response = GitalyClient.call(@storage, :repository_service, :write_ref, request)
raise Gitlab::Git::CommandError, encode!(response.error) if response.error.present?
true
end
end
end
end
module Gitlab
module GitalyClient
# Meant for extraction of server data, and later maybe to perform misc task
#
# Not meant for connection logic, look in Gitlab::GitalyClient
class ServerService
def initialize(storage)
@storage = storage
end
def info
GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new)
end
end
end
end
......@@ -127,6 +127,18 @@ module Gitlab
wiki_file
end
def get_formatted_data(title:, dir: nil, version: nil)
request = Gitaly::WikiGetFormattedDataRequest.new(
repository: @gitaly_repo,
title: encode_binary(title),
revision: encode_binary(version),
directory: encode_binary(dir)
)
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_formatted_data, request)
response.reduce("") { |memo, msg| memo << msg.data }
end
private
# If a block is given and the yielded value is true, iteration will be
......
module Gitlab
module Metrics
extend Gitlab::Metrics::InfluxDb
extend Gitlab::Metrics::Prometheus
include Gitlab::Metrics::InfluxDb
include Gitlab::Metrics::Prometheus
def self.enabled?
influx_metrics_enabled? || prometheus_metrics_enabled?
......
This diff is collapsed.
......@@ -4,26 +4,15 @@ module Gitlab
module Metrics
# Class for tracking timing information about method calls
class MethodCall
@@measurement_enabled_cache = Concurrent::AtomicBoolean.new(false)
@@measurement_enabled_cache_expires_at = Concurrent::AtomicReference.new(Time.now.to_i)
MUTEX = Mutex.new
include Gitlab::Metrics::Methods
BASE_LABELS = { module: nil, method: nil }.freeze
attr_reader :real_time, :cpu_time, :call_count, :labels
def self.call_duration_histogram
return @call_duration_histogram if @call_duration_histogram
MUTEX.synchronize do
@call_duration_histogram ||= Gitlab::Metrics.histogram(
:gitlab_method_call_duration_seconds,
'Method calls real duration',
Transaction::BASE_LABELS.merge(BASE_LABELS),
[0.01, 0.05, 0.1, 0.5, 1])
end
end
def self.measurement_enabled_cache_expires_at
@@measurement_enabled_cache_expires_at
define_histogram :gitlab_method_call_duration_seconds do
docstring 'Method calls real duration'
base_labels Transaction::BASE_LABELS.merge(BASE_LABELS)
buckets [0.01, 0.05, 0.1, 0.5, 1]
with_feature :prometheus_metrics_method_instrumentation
end
# name - The full name of the method (including namespace) such as
......@@ -53,8 +42,8 @@ module Gitlab
@cpu_time += cpu_time
@call_count += 1
if call_measurement_enabled? && above_threshold?
self.class.call_duration_histogram.observe(@transaction.labels.merge(labels), real_time)
if above_threshold?
self.class.gitlab_method_call_duration_seconds.observe(@transaction.labels.merge(labels), real_time)
end
retval
......@@ -78,17 +67,6 @@ module Gitlab
def above_threshold?
real_time.in_milliseconds >= Metrics.method_call_threshold
end
def call_measurement_enabled?
expires_at = @@measurement_enabled_cache_expires_at.value
if expires_at < Time.now.to_i
if @@measurement_enabled_cache_expires_at.compare_and_set(expires_at, 1.minute.from_now.to_i)
@@measurement_enabled_cache.value = Feature.get(:prometheus_metrics_method_instrumentation).enabled?
end
end
@@measurement_enabled_cache.value
end
end
end
end
# rubocop:disable Style/ClassVars
module Gitlab
module Metrics
module Methods
extend ActiveSupport::Concern
included do
@@_metric_provider_mutex ||= Mutex.new
@@_metrics_provider_cache = {}
end
class_methods do
def reload_metric!(name)
@@_metrics_provider_cache.delete(name)
end
private
def define_metric(type, name, opts = {}, &block)
if respond_to?(name)
raise ArgumentError, "method #{name} already exists"
end
define_singleton_method(name) do
# inlining fetch_metric method to avoid method call overhead when instrumenting hot spots
@@_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
end
end
def fetch_metric(type, name, opts = {}, &block)
@@_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
end
def init_metric(type, name, opts = {}, &block)
options = MetricOptions.new(opts)
options.evaluate(&block)
if disabled_by_feature(options)
synchronized_cache_fill(name) { NullMetric.instance }
else
synchronized_cache_fill(name) { build_metric!(type, name, options) }
end
end
def synchronized_cache_fill(key)
@@_metric_provider_mutex.synchronize do
@@_metrics_provider_cache[key] ||= yield
end
end
def disabled_by_feature(options)
options.with_feature && !Feature.get(options.with_feature).enabled?
end
def build_metric!(type, name, options)
case type
when :gauge
Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode)
when :counter
Gitlab::Metrics.counter(name, options.docstring, options.base_labels)
when :histogram
Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets)
when :summary
raise NotImplementedError, "summary metrics are not currently supported"
else
raise ArgumentError, "uknown metric type #{type}"
end
end
# Fetch and/or initialize counter metric
# @param [Symbol] name
# @param [Hash] opts
def fetch_counter(name, opts = {}, &block)
fetch_metric(:counter, name, opts, &block)
end
# Fetch and/or initialize gauge metric
# @param [Symbol] name
# @param [Hash] opts
def fetch_gauge(name, opts = {}, &block)
fetch_metric(:gauge, name, opts, &block)
end
# Fetch and/or initialize histogram metric
# @param [Symbol] name
# @param [Hash] opts
def fetch_histogram(name, opts = {}, &block)
fetch_metric(:histogram, name, opts, &block)
end
# Fetch and/or initialize summary metric
# @param [Symbol] name
# @param [Hash] opts
def fetch_summary(name, opts = {}, &block)
fetch_metric(:summary, name, opts, &block)
end
# Define metric accessor method for a Counter
# @param [Symbol] name
# @param [Hash] opts
def define_counter(name, opts = {}, &block)
define_metric(:counter, name, opts, &block)
end
# Define metric accessor method for a Gauge
# @param [Symbol] name
# @param [Hash] opts
def define_gauge(name, opts = {}, &block)
define_metric(:gauge, name, opts, &block)
end
# Define metric accessor method for a Histogram
# @param [Symbol] name
# @param [Hash] opts
def define_histogram(name, opts = {}, &block)
define_metric(:histogram, name, opts, &block)
end
# Define metric accessor method for a Summary
# @param [Symbol] name
# @param [Hash] opts
def define_summary(name, opts = {}, &block)
define_metric(:summary, name, opts, &block)
end
end
end
end
end
module Gitlab
module Metrics
module Methods
class MetricOptions
SMALL_NETWORK_BUCKETS = [0.005, 0.01, 0.1, 1, 10].freeze
def initialize(options = {})
@multiprocess_mode = options[:multiprocess_mode] || :all
@buckets = options[:buckets] || SMALL_NETWORK_BUCKETS
@base_labels = options[:base_labels] || {}
@docstring = options[:docstring]
@with_feature = options[:with_feature]
end
# Documentation describing metric in metrics endpoint '/-/metrics'
def docstring(docstring = nil)
@docstring = docstring unless docstring.nil?
@docstring
end
# Gauge aggregation mode for multiprocess metrics
# - :all (default) returns each gauge for every process
# - :livesum all process'es gauges summed up
# - :max maximum value of per process gauges
# - :min minimum value of per process gauges
def multiprocess_mode(mode = nil)
@multiprocess_mode = mode unless mode.nil?
@multiprocess_mode
end
# Measurement buckets for histograms
def buckets(buckets = nil)
@buckets = buckets unless buckets.nil?
@buckets
end
# Base labels are merged with per metric labels
def base_labels(base_labels = nil)
@base_labels = base_labels unless base_labels.nil?
@base_labels
end
# Use feature toggle to control whether certain metric is enabled/disabled
def with_feature(name = nil)
@with_feature = name unless name.nil?
@with_feature
end
def evaluate(&block)
instance_eval(&block) if block_given?
self
end
end
end
end
end
......@@ -2,6 +2,8 @@ module Gitlab
module Metrics
# Mocks ::Prometheus::Client::Metric and all derived metrics
class NullMetric
include Singleton
def method_missing(name, *args, &block)
nil
end
......
......@@ -3,73 +3,77 @@ require 'prometheus/client'
module Gitlab
module Metrics
module Prometheus
include Gitlab::CurrentSettings
include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
REGISTRY_MUTEX = Mutex.new
PROVIDER_MUTEX = Mutex.new
def metrics_folder_present?
multiprocess_files_dir = ::Prometheus::Client.configuration.multiprocess_files_dir
class_methods do
include Gitlab::Utils::StrongMemoize
multiprocess_files_dir &&
::Dir.exist?(multiprocess_files_dir) &&
::File.writable?(multiprocess_files_dir)
end
def metrics_folder_present?
multiprocess_files_dir = ::Prometheus::Client.configuration.multiprocess_files_dir
def prometheus_metrics_enabled?
strong_memoize(:prometheus_metrics_enabled) do
prometheus_metrics_enabled_unmemoized
multiprocess_files_dir &&
::Dir.exist?(multiprocess_files_dir) &&
::File.writable?(multiprocess_files_dir)
end
def prometheus_metrics_enabled?
strong_memoize(:prometheus_metrics_enabled) do
prometheus_metrics_enabled_unmemoized
end
end
end
def registry
strong_memoize(:registry) do
REGISTRY_MUTEX.synchronize do
strong_memoize(:registry) do
::Prometheus::Client.registry
def registry
strong_memoize(:registry) do
REGISTRY_MUTEX.synchronize do
strong_memoize(:registry) do
::Prometheus::Client.registry
end
end
end
end
end
def counter(name, docstring, base_labels = {})
safe_provide_metric(:counter, name, docstring, base_labels)
end
def counter(name, docstring, base_labels = {})
safe_provide_metric(:counter, name, docstring, base_labels)
end
def summary(name, docstring, base_labels = {})
safe_provide_metric(:summary, name, docstring, base_labels)
end
def summary(name, docstring, base_labels = {})
safe_provide_metric(:summary, name, docstring, base_labels)
end
def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
safe_provide_metric(:gauge, name, docstring, base_labels, multiprocess_mode)
end
def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
safe_provide_metric(:gauge, name, docstring, base_labels, multiprocess_mode)
end
def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
safe_provide_metric(:histogram, name, docstring, base_labels, buckets)
end
def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
safe_provide_metric(:histogram, name, docstring, base_labels, buckets)
end
private
private
def safe_provide_metric(method, name, *args)
metric = provide_metric(name)
return metric if metric
def safe_provide_metric(method, name, *args)
metric = provide_metric(name)
return metric if metric
PROVIDER_MUTEX.synchronize do
provide_metric(name) || registry.method(method).call(name, *args)
PROVIDER_MUTEX.synchronize do
provide_metric(name) || registry.method(method).call(name, *args)
end
end
end
def provide_metric(name)
if prometheus_metrics_enabled?
registry.get(name)
else
NullMetric.new
def provide_metric(name)
if prometheus_metrics_enabled?
registry.get(name)
else
NullMetric.instance
end
end
end
def prometheus_metrics_enabled_unmemoized
metrics_folder_present? && current_application_settings[:prometheus_metrics_enabled] || false
def prometheus_metrics_enabled_unmemoized
metrics_folder_present? &&
Gitlab::CurrentSettings.current_application_settings[:prometheus_metrics_enabled] || false
end
end
end
end
......
......@@ -3,6 +3,14 @@ module Gitlab
module Subscribers
# Class for tracking the rendering timings of views.
class ActionView < ActiveSupport::Subscriber
include Gitlab::Metrics::Methods
define_histogram :gitlab_view_rendering_duration_seconds do
docstring 'View rendering time'
base_labels Transaction::BASE_LABELS.merge({ path: nil })
buckets [0.001, 0.01, 0.1, 1, 10.0]
with_feature :prometheus_metrics_view_instrumentation
end
attach_to :action_view
SERIES = 'views'.freeze
......@@ -15,23 +23,11 @@ module Gitlab
private
def metric_view_rendering_duration_seconds
@metric_view_rendering_duration_seconds ||= Gitlab::Metrics.histogram(
:gitlab_view_rendering_duration_seconds,
'View rendering time',
Transaction::BASE_LABELS.merge({ path: nil }),
[0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.500, 2.0, 10.0]
)
end
def track(event)
values = values_for(event)
tags = tags_for(event)
metric_view_rendering_duration_seconds.observe(
current_transaction.labels.merge(tags),
event.duration
)
self.class.gitlab_view_rendering_duration_seconds.observe(current_transaction.labels.merge(tags), event.duration)
current_transaction.increment(:view_duration, event.duration)
current_transaction.add_metric(SERIES, values, tags)
......
......@@ -3,12 +3,13 @@ module Gitlab
module Subscribers
# Class for tracking the total query duration of a transaction.
class ActiveRecord < ActiveSupport::Subscriber
include Gitlab::Metrics::Methods
attach_to :active_record
def sql(event)
return unless current_transaction
metric_sql_duration_seconds.observe(current_transaction.labels, event.duration / 1000.0)
self.class.gitlab_sql_duration_seconds.observe(current_transaction.labels, event.duration / 1000.0)
current_transaction.increment(:sql_duration, event.duration, false)
current_transaction.increment(:sql_count, 1, false)
......@@ -16,17 +17,14 @@ module Gitlab
private
def current_transaction
Transaction.current
define_histogram :gitlab_sql_duration_seconds do
docstring 'SQL time'
base_labels Transaction::BASE_LABELS
buckets [0.001, 0.01, 0.1, 1.0, 10.0]
end
def metric_sql_duration_seconds
@metric_sql_duration_seconds ||= Gitlab::Metrics.histogram(
:gitlab_sql_duration_seconds,
'SQL time',
Transaction::BASE_LABELS,
[0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.500, 2.0, 10.0]
)
def current_transaction
Transaction.current
end
end
end
......
......@@ -2,11 +2,12 @@ module Gitlab
module Metrics
# Class for storing metrics information of a single transaction.
class Transaction
include Gitlab::Metrics::Methods
# base labels shared among all transactions
BASE_LABELS = { controller: nil, action: nil }.freeze
THREAD_KEY = :_gitlab_metrics_transaction
METRICS_MUTEX = Mutex.new
# The series to store events (e.g. Git pushes) in.
EVENT_SERIES = 'events'.freeze
......@@ -54,8 +55,8 @@ module Gitlab
@memory_after = System.memory_usage
@finished_at = System.monotonic_time
self.class.metric_transaction_duration_seconds.observe(labels, duration)
self.class.metric_transaction_allocated_memory_bytes.observe(labels, allocated_memory * 1024.0)
self.class.gitlab_transaction_duration_seconds.observe(labels, duration)
self.class.gitlab_transaction_allocated_memory_bytes.observe(labels, allocated_memory * 1024.0)
Thread.current[THREAD_KEY] = nil
end
......@@ -72,7 +73,7 @@ module Gitlab
# event_name - The name of the event (e.g. "git_push").
# tags - A set of tags to attach to the event.
def add_event(event_name, tags = {})
self.class.metric_event_counter(event_name, tags).increment(tags.merge(labels))
self.class.transaction_metric(event_name, :counter, prefix: 'event_', tags: tags).increment(tags.merge(labels))
@metrics << Metric.new(EVENT_SERIES, { count: 1 }, tags.merge(event: event_name), :event)
end
......@@ -86,12 +87,12 @@ module Gitlab
end
def increment(name, value, use_prometheus = true)
self.class.metric_transaction_counter(name).increment(labels, value) if use_prometheus
self.class.transaction_metric(name, :counter).increment(labels, value) if use_prometheus
@values[name] += value
end
def set(name, value, use_prometheus = true)
self.class.metric_transaction_gauge(name).set(labels, value) if use_prometheus
self.class.transaction_metric(name, :gauge).set(labels, value) if use_prometheus
@values[name] = value
end
......@@ -136,64 +137,28 @@ module Gitlab
"#{labels[:controller]}##{labels[:action]}" if labels && !labels.empty?
end
def self.metric_transaction_duration_seconds
return @metric_transaction_duration_seconds if @metric_transaction_duration_seconds
METRICS_MUTEX.synchronize do
@metric_transaction_duration_seconds ||= Gitlab::Metrics.histogram(
:gitlab_transaction_duration_seconds,
'Transaction duration',
BASE_LABELS,
[0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.500, 2.0, 10.0]
)
end
end
def self.metric_transaction_allocated_memory_bytes
return @metric_transaction_allocated_memory_bytes if @metric_transaction_allocated_memory_bytes
METRICS_MUTEX.synchronize do
@metric_transaction_allocated_memory_bytes ||= Gitlab::Metrics.histogram(
:gitlab_transaction_allocated_memory_bytes,
'Transaction allocated memory bytes',
BASE_LABELS,
[1000, 10000, 20000, 500000, 1000000, 2000000, 5000000, 10000000, 20000000, 100000000]
)
end
define_histogram :gitlab_transaction_duration_seconds do
docstring 'Transaction duration'
base_labels BASE_LABELS
buckets [0.001, 0.01, 0.1, 1.0, 10.0]
end
def self.metric_event_counter(event_name, tags)
return @metric_event_counters[event_name] if @metric_event_counters&.has_key?(event_name)
METRICS_MUTEX.synchronize do
@metric_event_counters ||= {}
@metric_event_counters[event_name] ||= Gitlab::Metrics.counter(
"gitlab_transaction_event_#{event_name}_total".to_sym,
"Transaction event #{event_name} counter",
tags.merge(BASE_LABELS)
)
end
end
def self.metric_transaction_counter(name)
return @metric_transaction_counters[name] if @metric_transaction_counters&.has_key?(name)
METRICS_MUTEX.synchronize do
@metric_transaction_counters ||= {}
@metric_transaction_counters[name] ||= Gitlab::Metrics.counter(
"gitlab_transaction_#{name}_total".to_sym, "Transaction #{name} counter", BASE_LABELS
)
end
define_histogram :gitlab_transaction_allocated_memory_bytes do
docstring 'Transaction allocated memory bytes'
base_labels BASE_LABELS
buckets [100, 1000, 10000, 100000, 1000000, 10000000]
with_feature :prometheus_metrics_transaction_allocated_memory
end
def self.metric_transaction_gauge(name)
return @metric_transaction_gauges[name] if @metric_transaction_gauges&.has_key?(name)
def self.transaction_metric(name, type, prefix: nil, tags: {})
metric_name = "gitlab_transaction_#{prefix}#{name}_total".to_sym
fetch_metric(type, metric_name) do
docstring "Transaction #{prefix}#{name} #{type}"
base_labels tags.merge(BASE_LABELS)
METRICS_MUTEX.synchronize do
@metric_transaction_gauges ||= {}
@metric_transaction_gauges[name] ||= Gitlab::Metrics.gauge(
"gitlab_transaction_#{name}".to_sym, "Transaction gauge #{name}", BASE_LABELS, :livesum
)
if type == :gauge
multiprocess_mode :livesum
end
end
end
end
......
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