Merge branch 'master' into fl-mr-widget-1

* master: (38 commits)
  Added more tests and corrected typos
  Ban Rugged from Repository
  Improve doc/development/automatic_ce_ee_merge.md
  fixed infinite loop crashing tests
  Converted todos.js to axios
  Converted usage_ping.js to use axios
  Converted pager.js to axios
  Converted notifications_form.js to axios
  Converted notes.js to axios
  Converted branch_graph.js to axios
  Converted mini_pipeline_graph_dropdown.js to axios
  Include subgroup issuables on the group page
  Remove grpc and google-protobuf platform-specific version markers in Gemfile.lock
  Replace $.ajax in find file with axios
  Replace $.ajax in activity calendar with axios
  Remove namespaced internationalization import
  Fix subgroup creation docs
  Fix a JSON schema that doesn't include enough fields
  Make user/author use project.creator in most factories
  Enable RuboCop Style/RegexpLiteral
  ...
This commit is contained in:
Filipa Lacerda 2018-02-01 17:53:26 +00:00
commit e03c85b981
181 changed files with 1836 additions and 841 deletions

View File

@ -704,7 +704,9 @@ Style/RedundantSelf:
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Enabled: false
Enabled: true
EnforcedStyle: mixed
AllowInnerSlashes: false
# Offense count: 36
# Cop supports --auto-correct.

View File

@ -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)
googleapis-common-protos-types (1.0.1)
google-protobuf (~> 3.0)
googleauth (0.5.3)

View File

@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this */
import _ from 'underscore';
import Cookies from 'js-cookie';
import { s__ } from './locale';
import { __ } from './locale';
import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils';
import flash from './flash';
import axios from './lib/utils/axios_utils';
@ -451,7 +451,7 @@ class AwardsHandler {
callback();
}
})
.catch(() => flash(s__('Something went wrong on our end.')));
.catch(() => flash(__('Something went wrong on our end.')));
}
}

View File

@ -1,4 +1,5 @@
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}"` : '';

View File

@ -1,5 +1,6 @@
/* eslint-disable no-new */
import Flash from './flash';
import flash from './flash';
import axios from './lib/utils/axios_utils';
/**
* In each pipelines table we have a mini pipeline graph for each pipeline.
@ -78,27 +79,22 @@ export default class MiniPipelineGraph {
const button = e.relatedTarget;
const endpoint = button.dataset.stageEndpoint;
return $.ajax({
dataType: 'json',
type: 'GET',
url: endpoint,
beforeSend: () => {
this.renderBuildsList(button, '');
this.toggleLoading(button);
},
success: (data) => {
this.renderBuildsList(button, '');
this.toggleLoading(button);
axios.get(endpoint)
.then(({ data }) => {
this.toggleLoading(button);
this.renderBuildsList(button, data.html);
this.stopDropdownClickPropagation();
},
error: () => {
})
.catch(() => {
this.toggleLoading(button);
if ($(button).parent().hasClass('open')) {
$(button).dropdown('toggle');
}
new Flash('An error occurred while fetching the builds.', 'alert');
},
});
flash('An error occurred while fetching the builds.', 'alert');
});
}
/**

View File

@ -1,5 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */
import { __ } from '../locale';
import axios from '../lib/utils/axios_utils';
import flash from '../flash';
import Raphael from './raphael';
export default (function() {
@ -26,16 +29,13 @@ export default (function() {
}
BranchGraph.prototype.load = function() {
return $.ajax({
url: this.options.url,
method: "get",
dataType: "json",
success: $.proxy(function(data) {
axios.get(this.options.url)
.then(({ data }) => {
$(".loading", this.element).hide();
this.prepareData(data.days, data.commits);
return this.buildGraph();
}, this)
});
this.buildGraph();
})
.catch(() => __('Error fetching network graph.'));
};
BranchGraph.prototype.prepareData = function(days, commits) {

View File

@ -16,6 +16,7 @@ import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle';
@ -252,26 +253,20 @@ export default class Notes {
return;
}
this.refreshing = true;
return $.ajax({
url: this.notes_url,
headers: { 'X-Last-Fetched-At': this.last_fetched_at },
dataType: 'json',
success: (function(_this) {
return function(data) {
var notes;
notes = data.notes;
_this.last_fetched_at = data.last_fetched_at;
_this.setPollingInterval(data.notes.length);
return $.each(notes, function(i, note) {
_this.renderNote(note);
});
};
})(this)
}).always((function(_this) {
return function() {
return _this.refreshing = false;
};
})(this));
axios.get(this.notes_url, {
headers: {
'X-Last-Fetched-At': this.last_fetched_at,
},
}).then(({ data }) => {
const notes = data.notes;
this.last_fetched_at = data.last_fetched_at;
this.setPollingInterval(data.notes.length);
$.each(notes, (i, note) => this.renderNote(note));
this.refreshing = false;
}).catch(() => {
this.refreshing = false;
});
}
/**

View File

@ -1,3 +1,7 @@
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
export default class NotificationsForm {
constructor() {
this.toggleCheckbox = this.toggleCheckbox.bind(this);
@ -27,24 +31,20 @@ export default class NotificationsForm {
saveEvent($checkbox, $parent) {
const form = $parent.parents('form:first');
return $.ajax({
url: form.attr('action'),
method: form.attr('method'),
dataType: 'json',
data: form.serialize(),
beforeSend: () => {
this.showCheckboxLoadingSpinner($parent);
},
}).done((data) => {
$checkbox.enable();
if (data.saved) {
$parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
setTimeout(() => {
$parent.removeClass('is-loading')
.find('.custom-notification-event-loading')
.toggleClass('fa-spin fa-spinner fa-check is-done');
}, 2000);
}
});
this.showCheckboxLoadingSpinner($parent);
axios[form.attr('method')](form.attr('action'), form.serialize())
.then(({ data }) => {
$checkbox.enable();
if (data.saved) {
$parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
setTimeout(() => {
$parent.removeClass('is-loading')
.find('.custom-notification-event-loading')
.toggleClass('fa-spin fa-spinner fa-check is-done');
}, 2000);
}
})
.catch(() => flash(__('There was an error saving your notification settings.')));
}
}

View File

@ -1,4 +1,5 @@
import { getParameterByName } from '~/lib/utils/common_utils';
import axios from './lib/utils/axios_utils';
import { removeParams } from './lib/utils/url_utility';
const ENDLESS_SCROLL_BOTTOM_PX = 400;
@ -22,24 +23,22 @@ export default {
getOld() {
this.loading.show();
$.ajax({
type: 'GET',
url: this.url,
data: `limit=${this.limit}&offset=${this.offset}`,
dataType: 'json',
error: () => this.loading.hide(),
success: (data) => {
this.append(data.count, this.prepareData(data.html));
this.callback();
// keep loading until we've filled the viewport height
if (!this.disable && !this.isScrollable()) {
this.getOld();
} else {
this.loading.hide();
}
axios.get(this.url, {
params: {
limit: this.limit,
offset: this.offset,
},
});
}).then(({ data }) => {
this.append(data.count, this.prepareData(data.html));
this.callback();
// keep loading until we've filled the viewport height
if (!this.disable && !this.isScrollable()) {
this.getOld();
} else {
this.loading.hide();
}
}).catch(() => this.loading.hide());
},
append(count, html) {

View File

@ -1,12 +1,13 @@
export default function UsagePing() {
const usageDataUrl = $('.usage-data').data('endpoint');
import axios from '../../../lib/utils/axios_utils';
import { __ } from '../../../locale';
import flash from '../../../flash';
$.ajax({
type: 'GET',
url: usageDataUrl,
dataType: 'html',
success(html) {
$('.usage-data').html(html);
},
});
export default function UsagePing() {
const el = document.querySelector('.usage-data');
axios.get(el.dataset.endpoint, {
responseType: 'text',
}).then(({ data }) => {
el.innerHTML = data;
}).catch(() => flash(__('Error fetching usage ping data.')));
}

View File

@ -2,6 +2,9 @@
import { visitUrl } from '~/lib/utils/url_utility';
import UsersSelect from '~/users_select';
import { isMetaClick } from '~/lib/utils/common_utils';
import { __ } from '../../../../locale';
import flash from '../../../../flash';
import axios from '../../../../lib/utils/axios_utils';
export default class Todos {
constructor() {
@ -59,18 +62,12 @@ export default class Todos {
const target = e.target;
target.setAttribute('disabled', true);
target.classList.add('disabled');
$.ajax({
type: 'POST',
url: target.dataset.href,
dataType: 'json',
data: {
'_method': target.dataset.method,
},
success: (data) => {
axios[target.dataset.method](target.dataset.href)
.then(({ data }) => {
this.updateRowState(target);
return this.updateBadges(data);
},
});
this.updateBadges(data);
}).catch(() => flash(__('Error updating todo status.')));
}
updateRowState(target) {
@ -98,19 +95,15 @@ export default class Todos {
e.preventDefault();
const target = e.currentTarget;
const requestData = { '_method': target.dataset.method, ids: this.todo_ids };
target.setAttribute('disabled', true);
target.classList.add('disabled');
$.ajax({
type: 'POST',
url: target.dataset.href,
dataType: 'json',
data: requestData,
success: (data) => {
this.updateAllState(target, data);
return this.updateBadges(data);
},
});
axios[target.dataset.method](target.dataset.href, {
ids: this.todo_ids,
}).then(({ data }) => {
this.updateAllState(target, data);
this.updateBadges(data);
}).catch(() => flash(__('Error updating status for all todos.')));
}
updateAllState(target, data) {

View File

@ -1,3 +1,5 @@
import Vue from 'vue';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import TreeView from '../../../../tree';
import ShortcutsNavigation from '../../../../shortcuts_navigation';
import BlobViewer from '../../../../blob/viewer';
@ -11,5 +13,25 @@ export default () => {
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
$('#tree-slider').waitForImages(() =>
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
const commitPipelineStatusEl = document.getElementById('commit-pipeline-status');
const statusLink = document.querySelector('.commit-actions .ci-status-link');
if (statusLink != null) {
statusLink.remove();
// eslint-disable-next-line no-new
new Vue({
el: commitPipelineStatusEl,
components: {
commitPipelineStatus,
},
render(createElement) {
return createElement('commit-pipeline-status', {
props: {
endpoint: commitPipelineStatusEl.dataset.endpoint,
},
});
},
});
}
};

View File

@ -1,6 +1,9 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
const highlighter = function(element, text, matches) {
@ -72,19 +75,14 @@ export default class ProjectFindFile {
// files pathes load
load(url) {
return $.ajax({
url: url,
method: "get",
dataType: "json",
success: (function(_this) {
return function(data) {
_this.element.find(".loading").hide();
_this.filePaths = data;
_this.findFile();
return _this.element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus();
};
})(this)
});
axios.get(url)
.then(({ data }) => {
this.element.find('.loading').hide();
this.filePaths = data;
this.findFile();
this.element.find('.files-slider tr.tree-item').eq(0).addClass('selected').focus();
})
.catch(() => flash(__('An error occurred while loading filenames')));
}
// render result

View File

@ -0,0 +1,120 @@
<script>
import Visibility from 'visibilityjs';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import Poll from '~/lib/utils/poll';
import Flash from '~/flash';
import { s__, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import CommitPipelineService from '../services/commit_pipeline_service';
export default {
directives: {
tooltip,
},
components: {
ciIcon,
loadingIcon,
},
props: {
endpoint: {
type: String,
required: true,
},
/* This prop can be used to replace some of the `render_commit_status`
used across GitLab, this way we could use this vue component and add a
realtime status where it makes sense
realtime: {
type: Boolean,
required: false,
default: true,
}, */
},
data() {
return {
ciStatus: {},
isLoading: true,
};
},
computed: {
statusTitle() {
return sprintf(s__('Commits|Commit: %{commitText}'), { commitText: this.ciStatus.text });
},
},
mounted() {
this.service = new CommitPipelineService(this.endpoint);
this.initPolling();
},
methods: {
successCallback(res) {
const pipelines = res.data.pipelines;
if (pipelines.length > 0) {
// The pipeline entity always keeps the latest pipeline info on the `details.status`
this.ciStatus = pipelines[0].details.status;
}
this.isLoading = false;
},
errorCallback() {
this.ciStatus = {
text: 'not found',
icon: 'status_notfound',
group: 'notfound',
};
this.isLoading = false;
Flash(s__('Something went wrong on our end'));
},
initPolling() {
this.poll = new Poll({
resource: this.service,
method: 'fetchData',
successCallback: response => this.successCallback(response),
errorCallback: this.errorCallback,
});
if (!Visibility.hidden()) {
this.isLoading = true;
this.poll.makeRequest();
} else {
this.fetchPipelineCommitData();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
},
fetchPipelineCommitData() {
this.service.fetchData()
.then(this.successCallback)
.catch(this.errorCallback);
},
},
destroy() {
this.poll.stop();
},
};
</script>
<template>
<div>
<loading-icon
label="Loading pipeline status"
size="3"
v-if="isLoading"
/>
<a
v-else
:href="ciStatus.details_path"
>
<ci-icon
v-tooltip
:title="statusTitle"
:aria-label="statusTitle"
data-container="body"
:status="ciStatus"
/>
</a>
</div>
</template>

View File

@ -0,0 +1,11 @@
import axios from '~/lib/utils/axios_utils';
export default class CommitPipelineService {
constructor(endpoint) {
this.endpoint = endpoint;
}
fetchData() {
return axios.get(this.endpoint);
}
}

View File

@ -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')));
}
}

View File

@ -1,7 +1,10 @@
import _ from 'underscore';
import { scaleLinear, scaleThreshold } from 'd3-scale';
import { select } from 'd3-selection';
import { getDayName, getDayDifference } from '../lib/utils/datetime_utility';
import { getDayName, getDayDifference } from '~/lib/utils/datetime_utility';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
const d3 = { select, scaleLinear, scaleThreshold };
@ -221,14 +224,16 @@ export default class ActivityCalendar {
this.currentSelectedDate.getDate(),
].join('-');
$.ajax({
url: this.calendarActivitiesPath,
data: { date },
cache: false,
dataType: 'html',
beforeSend: () => $('.user-calendar-activities').html(LOADING_HTML),
success: data => $('.user-calendar-activities').html(data),
});
$('.user-calendar-activities').html(LOADING_HTML);
axios.get(this.calendarActivitiesPath, {
params: {
date,
},
responseType: 'text',
})
.then(({ data }) => $('.user-calendar-activities').html(data))
.catch(() => flash(__('An error occurred while retrieving calendar activity')));
} else {
this.currentSelectedDate = '';
$('.user-calendar-activities').html('');

View File

@ -1,37 +0,0 @@
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>
`,
};

View File

@ -0,0 +1,43 @@
<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>

View File

@ -15,7 +15,7 @@ export { default as WidgetHeader } from './components/mr_widget_header.vue';
export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue';
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';

View File

@ -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"

View File

@ -195,6 +195,18 @@
.commit-actions {
@media (min-width: $screen-sm-min) {
font-size: 0;
div {
display: inline;
}
.fa-spinner {
font-size: 12px;
}
span {
font-size: 6px;
}
}
.ci-status-link {
@ -219,6 +231,11 @@
font-size: 14px;
font-weight: $gl-font-weight-bold;
}
.ci-status-icon {
position: relative;
top: 1px;
}
}
.commit,

View File

@ -0,0 +1,5 @@
class Admin::GitalyServersController < Admin::ApplicationController
def index
@gitaly_servers = Gitaly::Server.all
end
end

View File

@ -94,6 +94,7 @@ module IssuableCollections
@filter_params[:project_id] = @project.id
elsif @group
@filter_params[:group_id] = @group.id
@filter_params[:include_subgroups] = true
else
# TODO: this filter ignore issues/mr created in public or
# internal repos where you are not a member. Enable this filter

View File

@ -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)

View File

@ -5,7 +5,7 @@ class HelpController < ApplicationController
# Taken from Jekyll
# https://github.com/jekyll/jekyll/blob/3.5-stable/lib/jekyll/document.rb#L13
YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m
YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
def index
# Remove YAML frontmatter so that it doesn't look weird

View File

@ -403,6 +403,6 @@ class ProjectsController < Projects::ApplicationController
# to
# localhost/group/project
#
redirect_to request.original_url.sub(/\.git\/?\Z/, '') if params[:format] == 'git'
redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git'
end
end

View File

@ -87,8 +87,17 @@ class GroupProjectsFinder < ProjectsFinder
options.fetch(:only_shared, false)
end
# subgroups are supported only for owned projects not for shared
def include_subgroups?
options.fetch(:include_subgroups, false)
end
def owned_projects
group.projects
if include_subgroups?
Project.where(namespace_id: group.self_and_descendants.select(:id))
else
group.projects
end
end
def shared_projects

View File

@ -43,6 +43,7 @@ class IssuableFinder
search
sort
state
include_subgroups
].freeze
ARRAY_PARAMS = { label_name: [], iids: [], assignee_username: [] }.freeze
@ -148,7 +149,8 @@ class IssuableFinder
if current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects
elsif group
GroupProjectsFinder.new(group: group, current_user: current_user).execute
finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute
else
ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
end

View File

@ -1,12 +1,12 @@
module SidekiqHelper
SIDEKIQ_PS_REGEXP = /\A
SIDEKIQ_PS_REGEXP = %r{\A
(?<pid>\d+)\s+
(?<cpu>[\d\.,]+)\s+
(?<mem>[\d\.,]+)\s+
(?<state>[DIEKNRSTVWXZNLpsl\+<>\/\d]+)\s+
(?<state>[DIEKNRSTVWXZNLpsl\+<>/\d]+)\s+
(?<start>.+?)\s+
(?<command>(?:ruby\d+:\s+)?sidekiq.*\].*)
\z/x
\z}x
def parse_sidekiq_ps(line)
match = line.strip.match(SIDEKIQ_PS_REGEXP)

View File

@ -11,7 +11,7 @@ module SubmoduleHelper
url = File.join(Gitlab.config.gitlab.url, @project.full_path)
end
if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/
if url =~ %r{([^/:]+)/([^/]+(?:\.git)?)\Z}
namespace, project = $1, $2
gitlab_hosts = [Gitlab.config.gitlab.url,
Gitlab.config.gitlab_shell.ssh_path_prefix]
@ -23,7 +23,7 @@ module SubmoduleHelper
end
end
namespace.sub!(/\A\//, '')
namespace.sub!(%r{\A/}, '')
project.rstrip!
project.sub!(/\.git\z/, '')
@ -47,11 +47,11 @@ module SubmoduleHelper
protected
def github_dot_com_url?(url)
url =~ /github\.com[\/:][^\/]+\/[^\/]+\Z/
url =~ %r{github\.com[/:][^/]+/[^/]+\Z}
end
def gitlab_dot_com_url?(url)
url =~ /gitlab\.com[\/:][^\/]+\/[^\/]+\Z/
url =~ %r{gitlab\.com[/:][^/]+/[^/]+\Z}
end
def self_url?(url, namespace, project)
@ -65,7 +65,7 @@ module SubmoduleHelper
def relative_self_url?(url)
# (./)?(../repo.git) || (./)?(../../project/repo.git) )
url =~ /\A((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*(\.git)?\z/ || url =~ /\A((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*(\.git)?\z/
url =~ %r{\A((\./)?(\.\./))(?!(\.\.)|(.*/)).*(\.git)?\z} || url =~ %r{\A((\./)?(\.\./){2})(?!(\.\.))([^/]*)/(?!(\.\.)|(.*/)).*(\.git)?\z}
end
def standard_links(host, namespace, project, commit)

View File

@ -110,7 +110,7 @@ module TreeHelper
# returns the relative path of the first subdir that doesn't have only one directory descendant
def flatten_tree(root_path, tree)
return tree.flat_path.sub(/\A#{root_path}\//, '') if tree.flat_path.present?
return tree.flat_path.sub(%r{\A#{root_path}/}, '') if tree.flat_path.present?
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir?

View File

@ -292,7 +292,7 @@ module Ci
def repo_url
auth = "gitlab-ci-token:#{ensure_token!}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
project.http_url_to_repo.sub(%r{^https?://}) do |prefix|
prefix + auth
end
end

View File

@ -141,7 +141,7 @@ class CommitStatus < ActiveRecord::Base
end
def group_name
name.to_s.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
end
def failed_but_allowed?

View File

@ -9,13 +9,13 @@ require 'task_list/filter'
module Taskable
COMPLETED = 'completed'.freeze
INCOMPLETE = 'incomplete'.freeze
ITEM_PATTERN = /
ITEM_PATTERN = %r{
^
\s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list
\s+ # whitespace prefix has to be always presented for a list item
(\[\s\]|\[[xX]\]) # checkbox
(\s.+) # followed by whitespace and some text.
/x
}x
def self.get_tasks(content)
content.to_s.scan(ITEM_PATTERN).map do |checkbox, label|

View File

@ -115,7 +115,7 @@ class Environment < ActiveRecord::Base
def formatted_external_url
return nil unless external_url
external_url.gsub(/\A.*?:\/\//, '')
external_url.gsub(%r{\A.*?://}, '')
end
def stop_action?

View File

@ -234,7 +234,7 @@ class Project < ActiveRecord::Base
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
validates :ci_config_path,
format: { without: /(\.{2}|\A\/)/,
format: { without: %r{(\.{2}|\A/)},
message: 'cannot include leading slash or directory traversal.' },
length: { maximum: 255 },
allow_blank: true
@ -1338,7 +1338,7 @@ class Project < ActiveRecord::Base
host = "#{subdomain}.#{Settings.pages.host}".downcase
# The host in URL always needs to be downcased
url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix|
url = Gitlab.config.pages.url.sub(%r{^https?://}) do |prefix|
"#{prefix}#{subdomain}."
end.downcase

View File

@ -84,7 +84,7 @@ http://app.asana.com/-/account_api'
# - fix/ed/es/ing
# - close/s/d
# - closing
issue_finder = /(fix\w*|clos[ei]\w*+)?\W*(?:https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)|#(\d+))/i
issue_finder = %r{(fix\w*|clos[ei]\w*+)?\W*(?:https://app\.asana\.com/\d+/\d+/(\d+)|#(\d+))}i
message.scan(issue_finder).each do |tuple|
# tuple will be

View File

@ -10,9 +10,9 @@ class IssueTrackerService < Service
# overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN
def self.reference_pattern(only_long: false)
if only_long
%r{(\b[A-Z][A-Z0-9_]+-)(?<issue>\d+)}
/(\b[A-Z][A-Z0-9_]+-)(?<issue>\d+)/
else
%r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
/(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)/
end
end

View File

@ -19,7 +19,7 @@ class JiraService < IssueTrackerService
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
def self.reference_pattern(only_long: true)
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
@reference_pattern ||= /(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)/
end
def initialize_properties

View File

@ -852,7 +852,7 @@ 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
@ -951,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

View File

@ -842,13 +842,13 @@ class User < ActiveRecord::Base
end
def full_website_url
return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\//
return "http://#{website_url}" if website_url !~ %r{\Ahttps?://}
website_url
end
def short_website_url
website_url.sub(/\Ahttps?:\/\//, '')
website_url.sub(%r{\Ahttps?://}, '')
end
def all_ssh_keys

View File

@ -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

View File

@ -0,0 +1,31 @@
- 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!")

View File

@ -51,6 +51,7 @@
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
#commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
= link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)

View File

@ -0,0 +1,5 @@
---
title: Include subgroup issues and merge requests on the group page
merge_request:
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add realtime ci status for the repository -> files view
merge_request: 16523
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Enable RuboCop Style/RegexpLiteral
merge_request: 16752
author: Takuya Noguchi
type: other

View File

@ -0,0 +1,5 @@
---
title: Update minimum git version to 2.9.5
merge_request: 16683
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Fix not all events being shown in group dashboard
merge_request:
author:
type: fixed

View File

@ -0,0 +1,6 @@
---
title: Remove N+1 queries with /projects/:project_id/{access_requests,members} API
endpoints
merge_request:
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Add Gitaly Servers admin dashboard
merge_request:
author:
type: added

View File

@ -110,7 +110,7 @@ class Settings < Settingslogic
url = "http://#{url}" unless url.start_with?('http')
# Get rid of the path so that we don't even have to encode it
url_without_path = url.sub(%r{(https?://[^\/]+)/?.*}, '\1')
url_without_path = url.sub(%r{(https?://[^/]+)/?.*}, '\1')
URI.parse(url_without_path).host
end
@ -469,10 +469,10 @@ end
# repository_downloads_path value.
#
repositories_storages = Settings.repositories.storages.values
repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(/\/$/, '')
repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(%r{/$}, '')
repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home'])
if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(/\/$/, '')) }
if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(%r{/$}, '')) }
Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive')
end

View File

@ -54,7 +54,7 @@ elsif Gitlab::Database.mysql?
def initialize_type_map(mapping)
super mapping
mapping.register_type(%r(timestamp)i) do |sql_type|
mapping.register_type(/timestamp/i) do |sql_type|
precision = extract_precision(sql_type)
ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlDateTimeWithTimeZone.new(precision: precision)
end

View File

@ -1,5 +1,5 @@
namespace :admin do
resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
resources :users, constraints: { id: %r{[a-zA-Z./0-9_\-]+} } do
resources :keys, only: [:show, :destroy]
resources :identities, except: [:show]
resources :impersonation_tokens, only: [:index, :create] do
@ -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

View File

@ -35,7 +35,7 @@ constraints(GroupUrlConstrainer.new) do
post :toggle_subscription, on: :member
end
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :edit, :update, :new, :create] do
resources :milestones, constraints: { id: %r{[^/]+} }, only: [:index, :show, :edit, :update, :new, :create] do
member do
get :merge_requests
get :participants
@ -52,7 +52,7 @@ constraints(GroupUrlConstrainer.new) do
resources :uploads, only: [:create] do
collection do
get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ }
get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
end
end
end

View File

@ -40,7 +40,7 @@ constraints(ProjectUrlConstrainer.new) do
#
# Templates
#
get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: /[^\/]+/ }
get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: %r{[^/]+} }
resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
@ -55,7 +55,7 @@ constraints(ProjectUrlConstrainer.new) do
end
resource :pages, only: [:show, :destroy] do
resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: /[^\/]+/ }
resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: %r{[^/]+} }
end
resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
@ -65,7 +65,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
resources :services, constraints: { id: %r{[^/]+} }, only: [:index, :edit, :update] do
member do
put :test
end
@ -346,7 +346,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
resources :project_members, except: [:show, :new, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+]+} }, concerns: :access_requestable do
collection do
delete :leave
@ -379,7 +379,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :uploads, only: [:create] do
collection do
get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ }
get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
end
end

View File

@ -2,17 +2,17 @@ scope path: :uploads do
# Note attachments and User/Group/Project avatars
get "-/system/:model/:mounted_as/:id/:filename",
to: "uploads#show",
constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ }
constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: %r{[^/]+} }
# show uploads for models, snippets (notes) available for now
get '-/system/:model/:id/:secret/:filename',
to: 'uploads#show',
constraints: { model: /personal_snippet/, id: /\d+/, filename: /[^\/]+/ }
constraints: { model: /personal_snippet/, id: /\d+/, filename: %r{[^/]+} }
# show temporary uploads
get '-/system/temp/:secret/:filename',
to: 'uploads#show',
constraints: { filename: /[^\/]+/ }
constraints: { filename: %r{[^/]+} }
# Appearance
get "-/system/:model/:mounted_as/:id/:filename",
@ -22,7 +22,7 @@ scope path: :uploads do
# Project markdown uploads
get ":namespace_id/:project_id/:secret/:filename",
to: "projects/uploads#show",
constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ }
constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: %r{[^/]+} }
# create uploads for models, snippets (notes) available for now
post ':model',
@ -34,4 +34,4 @@ end
# Redirect old note attachments path to new uploads path.
get "files/note/:id/:filename",
to: redirect("uploads/note/attachment/%{id}/%{filename}"),
constraints: { filename: /[^\/]+/ }
constraints: { filename: %r{[^/]+} }

View File

@ -5,17 +5,32 @@ Enterprise Edition (look for the [`CE Upstream` merge requests]).
This merge is done automatically in a
[scheduled pipeline](https://gitlab.com/gitlab-org/release-tools/-/jobs/43201679).
If a merge is already in progress, the job [doesn't create a new one](https://gitlab.com/gitlab-org/release-tools/-/jobs/43157687).
**If you are pinged in a `CE Upstream` merge request to resolve a conflict,
please resolve the conflict as soon as possible or ask someone else to do it!**
## What to do if you are pinged in a `CE Upstream` merge request to resolve a conflict?
>**Note:**
It's ok to resolve more conflicts than the one that you are asked to resolve. In
that case, it's a good habit to ask for a double-check on your resolution by
someone who is familiar with the code you touched.
1. Please resolve the conflict as soon as possible or ask someone else to do it
- It's ok to resolve more conflicts than the one that you are asked to resolve.
In that case, it's a good habit to ask for a double-check on your resolution
by someone who is familiar with the code you touched.
1. Once you have resolved your conflicts, push to the branch (no force-push)
1. Assign the merge request to the next person that has to resolve a conflict
1. If all conflicts are resolved after your resolution is pushed, keep the merge
request assigned to you: **you are now responsible for the merge request to be
green**
1. If you need any help, you can ping the current [release managers], or ask in
the `#ce-to-ee` Slack channel
A few notes about the automatic CE->EE merge job:
- If a merge is already in progress, the job
[doesn't create a new one](https://gitlab.com/gitlab-org/release-tools/-/jobs/43157687).
- If there is nothing to merge (i.e. EE is up-to-date with CE), the job doesn't
create a new one
- The job posts messages to the `#ce-to-ee` Slack channel to inform what's the
current CE->EE merge status (e.g. "A new MR has been created", "A MR is still pending")
[`CE Upstream` merge requests]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?label_name%5B%5D=CE+upstream
[release managers]: https://about.gitlab.com/release-managers/
## Always merge EE merge requests before their CE counterparts

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -90,7 +90,8 @@ structure.
To create a subgroup:
1. In the group's dashboard go to the **Subgroups** page and click **New subgroup**.
1. In the group's dashboard expand the **New project** split button, select
**New subgroup** and click the **New subgroup** button.
![Subgroups page](img/create_subgroup_button.png)

View File

@ -193,7 +193,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'The link with text "/ID" should have url "tree/markdownID"' do
find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
find('a', text: %r{^/#id$})['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
end
step 'The link with text "README.mdID" '\
@ -203,7 +203,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'The link with text "d/README.mdID" should have '\
'url "blob/markdown/d/README.mdID"' do
find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
find('a', text: %r{^d/README.md#id$})['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
end
step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
@ -212,7 +212,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
find('a', text: %r{^/#id$})['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
end
# Wiki

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -17,15 +17,15 @@ module API
}
}.freeze
PROJECT_TEMPLATE_REGEX =
/[\<\{\[]
%r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
[\>\}\]]/xi.freeze
[\>\}\]]}xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
FULLNAME_TEMPLATE_REGEX =
/[\<\{\[]
%r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
[\>\}\]]/xi.freeze
[\>\}\]]}xi.freeze
helpers do
def parsed_license_template

View File

@ -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

View File

@ -173,7 +173,7 @@ module API
use :sort_params
use :pagination
end
get "/search/:query", requirements: { query: /[^\/]+/ } do
get "/search/:query", requirements: { query: %r{[^/]+} } do
search_service = Search::GlobalService.new(current_user, search: params[:query]).execute
projects = search_service.objects('projects', params[:page], false)
projects = projects.reorder(params[:order_by] => params[:sort])

View File

@ -16,15 +16,15 @@ module API
}
}.freeze
PROJECT_TEMPLATE_REGEX =
/[\<\{\[]
%r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
[\>\}\]]/xi.freeze
[\>\}\]]}xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
FULLNAME_TEMPLATE_REGEX =
/[\<\{\[]
%r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
[\>\}\]]/xi.freeze
[\>\}\]]}xi.freeze
DEPRECATION_MESSAGE = ' This endpoint is deprecated and has been removed in V4.'.freeze
helpers do

View File

@ -54,9 +54,9 @@ module Banzai
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
@emoji_pattern ||=
/(?<=[^[:alnum:]:]|\n|^)
%r{(?<=[^[:alnum:]:]|\n|^)
:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):
(?=[^[:alnum:]:]|$)/x
(?=[^[:alnum:]:]|$)}x
end
# Build a regexp that matches all valid unicode emojis names.

View File

@ -51,10 +51,10 @@ module Banzai
# See https://github.com/gollum/gollum/wiki
#
# Rubular: http://rubular.com/r/7dQnE5CUCH
TAGS_PATTERN = %r{\[\[(.+?)\]\]}.freeze
TAGS_PATTERN = /\[\[(.+?)\]\]/.freeze
# Pattern to match allowed image extensions
ALLOWED_IMAGE_EXTENSIONS = %r{.+(jpg|png|gif|svg|bmp)\z}i.freeze
ALLOWED_IMAGE_EXTENSIONS = /.+(jpg|png|gif|svg|bmp)\z/i.freeze
def call
search_text_nodes(doc).each do |node|

View File

@ -11,7 +11,7 @@ module ContainerRegistry
private
def default_path
@uri.sub(/^https?:\/\//, '')
@uri.sub(%r{^https?://}, '')
end
end
end

View File

@ -56,7 +56,7 @@ module ExtractsPath
if valid_refs.length == 0
# No exact ref match, so just try our best
pair = id.match(/([^\/]+)(.*)/).captures
pair = id.match(%r{([^/]+)(.*)}).captures
else
# There is a distinct possibility that multiple refs prefix the ID.
# Use the longest match to maximize the chance that we have the
@ -68,7 +68,7 @@ module ExtractsPath
end
# Remove ending slashes from path
pair[1].gsub!(/^\/|\/$/, '')
pair[1].gsub!(%r{^/|/$}, '')
pair
end

43
lib/gitaly/server.rb Normal file
View File

@ -0,0 +1,43 @@
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

View File

@ -12,7 +12,7 @@ module Gitlab
# Ends with /:random_hex/:filename
FILE_UPLOADER_PATH = %r{/\h+/[^/]+\z}
FULL_PATH_CAPTURE = %r{\A(.+)#{FILE_UPLOADER_PATH}}
FULL_PATH_CAPTURE = /\A(.+)#{FILE_UPLOADER_PATH}/
# These regex patterns are tested against a relative path, relative to
# the upload directory.

View File

@ -97,7 +97,7 @@ module Gitlab
end
def total_size
descendant_pattern = %r{^#{Regexp.escape(@path.to_s)}}
descendant_pattern = /^#{Regexp.escape(@path.to_s)}/
entries.sum do |path, entry|
(entry[:size] if path =~ descendant_pattern).to_i
end

View File

@ -11,7 +11,7 @@ module Gitlab
end
def package_url(name)
"https://packagist.org/packages/#{name}" if name =~ %r{\A#{REPO_REGEX}\z}
"https://packagist.org/packages/#{name}" if name =~ /\A#{REPO_REGEX}\z/
end
end
end

View File

@ -15,7 +15,7 @@ module Gitlab
link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/, &method(:github_url))
# Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]}, &:itself)
link_regex(/(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]/, &:itself)
# Link `source "https://rubygems.org"` to https://rubygems.org
link_method_call('source', URL_REGEX, &:itself)

View File

@ -12,7 +12,7 @@ module Gitlab
def link_dependencies
link_method_call('homepage', URL_REGEX, &:itself)
link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]}, &:itself)
link_regex(/(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]/, &:itself)
link_method_call('license', &method(:license_url))
link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url))

View File

@ -43,7 +43,7 @@ module Gitlab
return "" unless decoded
# Certain trigger phrases that means we didn't parse correctly
if decoded =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/
if decoded =~ %r{(Content\-Type\:|multipart/alternative|text/plain)}
return ""
end

View File

@ -6,14 +6,14 @@ module Gitlab
module FileDetector
PATTERNS = {
# Project files
readme: /\Areadme[^\/]*\z/i,
changelog: /\A(changelog|history|changes|news)[^\/]*\z/i,
license: /\A(licen[sc]e|copying)(\.[^\/]+)?\z/i,
contributing: /\Acontributing[^\/]*\z/i,
readme: %r{\Areadme[^/]*\z}i,
changelog: %r{\A(changelog|history|changes|news)[^/]*\z}i,
license: %r{\A(licen[sc]e|copying)(\.[^/]+)?\z}i,
contributing: %r{\Acontributing[^/]*\z}i,
version: 'version',
avatar: /\Alogo\.(png|jpg|gif)\z/,
issue_template: /\A\.gitlab\/issue_templates\/[^\/]+\.md\z/,
merge_request_template: /\A\.gitlab\/merge_request_templates\/[^\/]+\.md\z/,
issue_template: %r{\A\.gitlab/issue_templates/[^/]+\.md\z},
merge_request_template: %r{\A\.gitlab/merge_request_templates/[^/]+\.md\z},
# Configuration files
gitignore: '.gitignore',
@ -22,17 +22,17 @@ module Gitlab
route_map: '.gitlab/route-map.yml',
# Dependency files
cartfile: /\ACartfile[^\/]*\z/,
cartfile: %r{\ACartfile[^/]*\z},
composer_json: 'composer.json',
gemfile: /\A(Gemfile|gems\.rb)\z/,
gemfile_lock: 'Gemfile.lock',
gemspec: /\A[^\/]*\.gemspec\z/,
gemspec: %r{\A[^/]*\.gemspec\z},
godeps_json: 'Godeps.json',
package_json: 'package.json',
podfile: 'Podfile',
podspec_json: /\A[^\/]*\.podspec\.json\z/,
podspec: /\A[^\/]*\.podspec\z/,
requirements_txt: /\A[^\/]*requirements\.txt\z/,
podspec_json: %r{\A[^/]*\.podspec\.json\z},
podspec: %r{\A[^/]*\.podspec\z},
requirements_txt: %r{\A[^/]*requirements\.txt\z},
yarn_lock: 'yarn.lock'
}.freeze

View File

@ -11,7 +11,7 @@ module Gitlab
include Gitlab::EncodingHelper
def ref_name(ref)
encode!(ref).sub(/\Arefs\/(tags|heads|remotes)\//, '')
encode!(ref).sub(%r{\Arefs/(tags|heads|remotes)/}, '')
end
def branch_name(ref)

View File

@ -107,7 +107,7 @@ module Gitlab
def find_entry_by_path(repository, root_id, path)
root_tree = repository.lookup(root_id)
# Strip leading slashes
path[/^\/*/] = ''
path[%r{^/*}] = ''
path_arr = path.split('/')
entry = root_tree.find do |entry|
@ -140,7 +140,7 @@ module Gitlab
def find_by_gitaly(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
return unless path
path = path.sub(/\A\/*/, '')
path = path.sub(%r{\A/*}, '')
path = '/' if path.empty?
name = File.basename(path)

View File

@ -6,7 +6,7 @@ module Gitlab
class << self
def normalize_path(filename)
# Strip all leading slashes so that //foo -> foo
filename[/^\/*/] = ''
filename[%r{^/*}] = ''
# Expand relative paths (e.g. foo/../bar)
filename = Pathname.new(filename)

View File

@ -23,7 +23,7 @@ module Gitlab
# Ex.
# Ref.extract_branch_name('refs/heads/master') #=> 'master'
def self.extract_branch_name(str)
str.gsub(/\Arefs\/heads\//, '')
str.gsub(%r{\Arefs/heads/}, '')
end
# Gitaly: this method will probably be migrated indirectly via its call sites.

View File

@ -462,7 +462,6 @@ module Gitlab
path: nil,
follow: false,
skip_merges: false,
disable_walk: false,
after: nil,
before: nil
}
@ -494,11 +493,7 @@ module Gitlab
return []
end
if log_using_shell?(options)
log_by_shell(sha, options)
else
log_by_walk(sha, options)
end
log_by_shell(sha, options)
end
def count_commits(options)
@ -1386,8 +1381,18 @@ 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(/^\/*/, ""))
safe_query = Regexp.escape(query.sub(%r{^/*}, ""))
return [] if empty? || safe_query.blank?
@ -1635,24 +1640,6 @@ module Gitlab
end
end
def log_using_shell?(options)
options[:path].present? ||
options[:disable_walk] ||
options[:skip_merges] ||
options[:after] ||
options[:before]
end
def log_by_walk(sha, options)
walk_options = {
show: sha,
sort: Rugged::SORT_NONE,
limit: options[:limit],
offset: options[:offset]
}
Rugged::Walker.walk(rugged, walk_options).to_a
end
# Gitaly note: JV: although #log_by_shell shells out to Git I think the
# complexity is such that we should migrate it as Ruby before trying to
# do it in Go.
@ -2015,7 +2002,7 @@ module Gitlab
target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
rescue Rugged::ReferenceError => e
raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/
raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ %r{'refs/heads/#{ref}'}
raise InvalidRef.new("Invalid reference #{start_point}")
end
@ -2280,6 +2267,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

View File

@ -43,7 +43,7 @@ module Gitlab
branches = []
rugged.references.each("refs/remotes/#{remote_name}/*").map do |ref|
name = ref.name.sub(/\Arefs\/remotes\/#{remote_name}\//, '')
name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '')
begin
target_commit = Gitlab::Git::Commit.find(self, ref.target)

View File

@ -83,6 +83,8 @@ module Gitlab
commit_id: sha
)
end
rescue Rugged::ReferenceError
[]
end
end

View File

@ -257,7 +257,7 @@ module Gitlab
offset: options[:offset],
follow: options[:follow],
skip_merges: options[:skip_merges],
disable_walk: options[:disable_walk]
disable_walk: true # This option is deprecated. The 'walk' implementation is being removed.
)
request.after = GitalyClient.timestamp(options[:after]) if options[:after]
request.before = GitalyClient.timestamp(options[:before]) if options[:before]

View File

@ -0,0 +1,16 @@
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

View File

@ -13,7 +13,7 @@ module Gitlab
:diff_hunk, :author, :note, :created_at, :updated_at,
:github_id
NOTEABLE_ID_REGEX = /\/pull\/(?<iid>\d+)/i
NOTEABLE_ID_REGEX = %r{/pull/(?<iid>\d+)}i
# Builds a diff note from a GitHub API response.
#

View File

@ -12,7 +12,7 @@ module Gitlab
expose_attribute :noteable_id, :noteable_type, :author, :note,
:created_at, :updated_at, :github_id
NOTEABLE_TYPE_REGEX = /\/(?<type>(pull|issues))\/(?<iid>\d+)/i
NOTEABLE_TYPE_REGEX = %r{/(?<type>(pull|issues))/(?<iid>\d+)}i
# Builds a note from a GitHub API response.
#

View File

@ -59,7 +59,7 @@ module Gitlab
end
def extracted_files
Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ /.*\/\.{1,2}$/ }
Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ %r{.*/\.{1,2}$} }
end
end
end

View File

@ -34,7 +34,7 @@ module Gitlab
end
def relative_path(path)
path.gsub(/^#{Rails.root.to_s}\/?/, '')
path.gsub(%r{^#{Rails.root.to_s}/?}, '')
end
def values_for(event)

View File

@ -56,12 +56,12 @@ module Gitlab
end
def strip_url(url)
url.gsub(/\Ahttps?:\/\//, '')
url.gsub(%r{\Ahttps?://}, '')
end
def project_path(request)
path_info = request.env["PATH_INFO"]
path_info.sub!(/^\//, '')
path_info.sub!(%r{^/}, '')
project_path_match = "#{path_info}/".match(PROJECT_PATH_REGEX)
return unless project_path_match

View File

@ -1,7 +1,7 @@
module Gitlab
module Middleware
class Static < ActionDispatch::Static
UPLOADS_REGEX = /\A\/uploads(\/|\z)/.freeze
UPLOADS_REGEX = %r{\A/uploads(/|\z)}.freeze
def call(env)
return @app.call(env) if env['PATH_INFO'] =~ UPLOADS_REGEX

View File

@ -9,7 +9,7 @@ module Gitlab
# if date doesn't present return time with current date
# in other cases return nil
class SpendTimeAndDateSeparator
DATE_REGEX = /(\d{2,4}[\/\-.]\d{1,2}[\/\-.]\d{1,2})/
DATE_REGEX = %r{(\d{2,4}[/\-.]\d{1,2}[/\-.]\d{1,2})}
def initialize(spend_command_arg)
@spend_arg = spend_command_arg

View File

@ -30,7 +30,7 @@ module Gitlab
raise NotFoundError.new("No known storage path matches #{repo_path.inspect}")
end
result.sub(/\A\/*/, '')
result.sub(%r{\A/*}, '')
end
def self.find_project(project_path)

View File

@ -31,7 +31,7 @@ module Gitlab
storages << { name: 'test_second_storage', path: Rails.root.join('tmp', 'tests', 'second_storage').to_s }
end
config = { socket_path: address.sub(%r{\Aunix:}, ''), storage: storages }
config = { socket_path: address.sub(/\Aunix:/, ''), storage: storages }
config[:auth] = { token: 'secret' } if Rails.env.test?
config[:'gitaly-ruby'] = { dir: File.join(gitaly_dir, 'ruby') } if gitaly_ruby
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }

Some files were not shown because too many files have changed in this diff Show More